1 /*
2 **  Licensed to the Apache Software Foundation (ASF) under one or more
3 ** contributor license agreements.  See the NOTICE file distributed with
4 ** this work for additional information regarding copyright ownership.
5 ** The ASF licenses this file to You under the Apache License, Version 2.0
6 ** (the "License"); you may not use this file except in compliance with
7 ** the License.  You may obtain a copy of the License at
8 **
9 **      http://www.apache.org/licenses/LICENSE-2.0
10 **
11 **  Unless required by applicable law or agreed to in writing, software
12 **  distributed under the License is distributed on an "AS IS" BASIS,
13 **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 **  See the License for the specific language governing permissions and
15 **  limitations under the License.
16 */
17 
18 #include "apreq_cookie.h"
19 #include "apreq_error.h"
20 #include "apreq_util.h"
21 #include "apr_strings.h"
22 #include "apr_lib.h"
23 #include "apr_date.h"
24 
25 
26 #define RFC      1
27 #define NETSCAPE 0
28 
29 #define ADD_COOKIE(j,c) apreq_value_table_add(&c->v, j)
30 
apreq_cookie_expires(apreq_cookie_t * c,const char * time_str)31 APREQ_DECLARE(void) apreq_cookie_expires(apreq_cookie_t *c,
32                                          const char *time_str)
33 {
34     if (time_str == NULL) {
35         c->max_age = -1;
36         return;
37     }
38 
39     if (!strcasecmp(time_str, "now"))
40         c->max_age = 0;
41     else {
42         c->max_age = apr_date_parse_rfc(time_str);
43         if (c->max_age == APR_DATE_BAD)
44             c->max_age = apr_time_from_sec(apreq_atoi64t(time_str));
45         else
46             c->max_age -= apr_time_now();
47     }
48 }
49 
apreq_cookie_attr(apr_pool_t * p,apreq_cookie_t * c,const char * attr,apr_size_t alen,const char * val,apr_size_t vlen)50 static apr_status_t apreq_cookie_attr(apr_pool_t *p,
51                                       apreq_cookie_t *c,
52                                       const char *attr,
53                                       apr_size_t alen,
54                                       const char *val,
55                                       apr_size_t vlen)
56 {
57     if (alen < 2)
58         return APR_EBADARG;
59 
60     if ( attr[0] ==  '-' || attr[0] == '$' ) {
61         ++attr;
62         --alen;
63     }
64 
65     switch (apr_tolower(*attr)) {
66 
67     case 'n': /* name is not an attr */
68         return APR_ENOTIMPL;
69 
70     case 'v': /* version; value is not an attr */
71         if (alen == 5 && strncasecmp(attr,"value", 5) == 0)
72             return APR_ENOTIMPL;
73 
74         while (!apr_isdigit(*val)) {
75             if (vlen == 0)
76                 return APREQ_ERROR_BADSEQ;
77             ++val;
78             --vlen;
79         }
80         apreq_cookie_version_set(c, *val - '0');
81         return APR_SUCCESS;
82 
83     case 'e': case 'm': /* expires, max-age */
84         apreq_cookie_expires(c, val);
85         return APR_SUCCESS;
86 
87     case 'd':
88         c->domain = apr_pstrmemdup(p,val,vlen);
89         return APR_SUCCESS;
90 
91     case 'p':
92         if (alen != 4)
93             break;
94         if (!strncasecmp("port", attr, 4)) {
95             c->port = apr_pstrmemdup(p,val,vlen);
96             return APR_SUCCESS;
97         }
98         else if (!strncasecmp("path", attr, 4)) {
99             c->path = apr_pstrmemdup(p,val,vlen);
100             return APR_SUCCESS;
101         }
102         break;
103 
104     case 'c':
105         if (!strncasecmp("commentURL", attr, 10)) {
106             c->commentURL = apr_pstrmemdup(p,val,vlen);
107             return APR_SUCCESS;
108         }
109         else if (!strncasecmp("comment", attr, 7)) {
110             c->comment = apr_pstrmemdup(p,val,vlen);
111             return APR_SUCCESS;
112         }
113         break;
114 
115     case 's':
116         if (vlen > 0 && *val != '0' && strncasecmp("off",val,vlen))
117             apreq_cookie_secure_on(c);
118         else
119             apreq_cookie_secure_off(c);
120         return APR_SUCCESS;
121 
122     case 'h': /* httponly */
123         if (vlen > 0 && *val != '0' && strncasecmp("off",val,vlen))
124             apreq_cookie_httponly_on(c);
125         else
126             apreq_cookie_httponly_off(c);
127         return APR_SUCCESS;
128 
129     };
130 
131     return APR_ENOTIMPL;
132 }
133 
apreq_cookie_make(apr_pool_t * p,const char * name,const apr_size_t nlen,const char * value,const apr_size_t vlen)134 APREQ_DECLARE(apreq_cookie_t *) apreq_cookie_make(apr_pool_t *p,
135                                                   const char *name,
136                                                   const apr_size_t nlen,
137                                                   const char *value,
138                                                   const apr_size_t vlen)
139 {
140     apreq_cookie_t *c;
141     apreq_value_t *v;
142 
143     c = apr_palloc(p, nlen + vlen + 1 + sizeof *c);
144 
145     if (c == NULL)
146         return NULL;
147 
148     *(const apreq_value_t **)&v = &c->v;
149 
150     if (vlen > 0 && value != NULL)
151         memcpy(v->data, value, vlen);
152     v->data[vlen] = 0;
153     v->dlen = vlen;
154     v->name = v->data + vlen + 1;
155     if (nlen && name != NULL)
156         memcpy(v->name, name, nlen);
157     v->name[nlen] = 0;
158     v->nlen = nlen;
159 
160     c->path = NULL;
161     c->domain = NULL;
162     c->port = NULL;
163     c->comment = NULL;
164     c->commentURL = NULL;
165     c->max_age = -1;    /* session cookie is the default */
166     c->flags = 0;
167 
168 
169     return c;
170 }
171 
172 static APR_INLINE
get_pair(apr_pool_t * p,const char ** data,const char ** n,apr_size_t * nlen,const char ** v,apr_size_t * vlen,unsigned unquote)173 apr_status_t get_pair(apr_pool_t *p, const char **data,
174                       const char **n, apr_size_t *nlen,
175                       const char **v, apr_size_t *vlen, unsigned unquote)
176 {
177     const char *hdr, *key, *val;
178     int nlen_set = 0;
179     hdr = *data;
180 
181     while (apr_isspace(*hdr) || *hdr == '=')
182         ++hdr;
183 
184     key = hdr;
185     *n = hdr;
186 
187  scan_name:
188 
189     switch (*hdr) {
190 
191     case 0:
192     case ';':
193     case ',':
194         if (!nlen_set)
195             *nlen = hdr - key;
196         *v = hdr;
197         *vlen = 0;
198         *data = hdr;
199         return *nlen ? APREQ_ERROR_NOTOKEN : APREQ_ERROR_BADCHAR;
200 
201     case '=':
202         if (!nlen_set) {
203             *nlen = hdr - key;
204             nlen_set = 1;
205         }
206         break;
207 
208     case ' ':
209     case '\t':
210     case '\r':
211     case '\n':
212         if (!nlen_set) {
213             *nlen = hdr - key;
214             nlen_set = 1;
215         }
216         /* fall thru */
217 
218     default:
219         ++hdr;
220         goto scan_name;
221     }
222 
223     val = hdr + 1;
224 
225     while (apr_isspace(*val))
226         ++val;
227 
228     if (*val == '"') {
229         unsigned saw_backslash = 0;
230         for (*v = (unquote) ? ++val : val++; *val; ++val) {
231             switch (*val) {
232             case '"':
233                 *data = val + 1;
234 
235                 if (!unquote) {
236                     *vlen = (val - *v) + 1;
237                 }
238                 else if (!saw_backslash) {
239                     *vlen = val - *v;
240                 }
241                 else {
242                     char *dest = apr_palloc(p, val - *v), *d = dest;
243                     const char *s = *v;
244                     while (s < val) {
245                         if (*s == '\\')
246                             ++s;
247                         *d++ = *s++;
248                     }
249 
250                     *vlen = d - dest;
251                     *v = dest;
252                 }
253 
254                 return APR_SUCCESS;
255             case '\\':
256                 saw_backslash = 1;
257                 if (val[1] != 0)
258                     ++val;
259             default:
260                 break;
261             }
262         }
263         /* bad sequence: no terminating quote found */
264         *data = val;
265         return APREQ_ERROR_BADSEQ;
266     }
267     else {
268         /* value is not wrapped in quotes */
269         for (*v = val; *val; ++val) {
270             switch (*val) {
271             case ';':
272             case ',':
273             case ' ':
274             case '\t':
275             case '\r':
276             case '\n':
277                 *data = val;
278                 *vlen = val - *v;
279                 return APR_SUCCESS;
280             default:
281                 break;
282             }
283         }
284     }
285 
286     *data = val;
287     *vlen = val - *v;
288 
289     return APR_SUCCESS;
290 }
291 
292 
293 
apreq_parse_cookie_header(apr_pool_t * p,apr_table_t * j,const char * hdr)294 APREQ_DECLARE(apr_status_t)apreq_parse_cookie_header(apr_pool_t *p,
295                                                      apr_table_t *j,
296                                                      const char *hdr)
297 {
298     apreq_cookie_t *c;
299     unsigned version;
300     apr_status_t rv = APR_SUCCESS;
301 
302  parse_cookie_header:
303 
304     c = NULL;
305     version = NETSCAPE;
306 
307     while (apr_isspace(*hdr))
308         ++hdr;
309 
310 
311     if (*hdr == '$' && strncasecmp(hdr, "$Version", 8) == 0) {
312         /* XXX cheat: assume "$Version" => RFC Cookie header */
313         version = RFC;
314     skip_version_string:
315         switch (*hdr++) {
316         case 0:
317             return rv;
318         case ',':
319             goto parse_cookie_header;
320         case ';':
321             break;
322         default:
323             goto skip_version_string;
324         }
325     }
326 
327     for (;;) {
328         apr_status_t status;
329         const char *name, *value;
330         apr_size_t nlen, vlen;
331 
332         while (*hdr == ';' || apr_isspace(*hdr))
333             ++hdr;
334 
335         switch (*hdr) {
336 
337         case 0:
338             /* this is the normal exit point */
339             if (c != NULL) {
340                 ADD_COOKIE(j, c);
341             }
342             return rv;
343 
344         case ',':
345             ++hdr;
346             if (c != NULL) {
347                 ADD_COOKIE(j, c);
348             }
349             goto parse_cookie_header;
350 
351         case '$':
352             ++hdr;
353             if (c == NULL) {
354                 rv = APREQ_ERROR_BADCHAR;
355                 goto parse_cookie_error;
356             }
357             else if (version == NETSCAPE) {
358                 rv = APREQ_ERROR_MISMATCH;
359             }
360 
361             status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 1);
362             if (status != APR_SUCCESS) {
363                 rv = status;
364                 goto parse_cookie_error;
365             }
366 
367             status = apreq_cookie_attr(p, c, name, nlen, value, vlen);
368 
369             switch (status) {
370 
371             case APR_ENOTIMPL:
372                 rv = APREQ_ERROR_BADATTR;
373                 /* fall thru */
374 
375             case APR_SUCCESS:
376                 break;
377 
378             default:
379                 rv = status;
380                 goto parse_cookie_error;
381             }
382 
383             break;
384 
385         default:
386             if (c != NULL) {
387                 ADD_COOKIE(j, c);
388             }
389 
390             status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 0);
391 
392             if (status != APR_SUCCESS) {
393                 c = NULL;
394                 rv = status;
395                 goto parse_cookie_error;
396             }
397 
398             c = apreq_cookie_make(p, name, nlen, value, vlen);
399             apreq_cookie_tainted_on(c);
400             if (version != NETSCAPE)
401                 apreq_cookie_version_set(c, version);
402         }
403     }
404 
405  parse_cookie_error:
406 
407     switch (*hdr) {
408 
409     case 0:
410         return rv;
411 
412     case ',':
413     case ';':
414         if (c != NULL)
415             ADD_COOKIE(j, c);
416         ++hdr;
417         goto parse_cookie_header;
418 
419     default:
420         ++hdr;
421         goto parse_cookie_error;
422     }
423 
424     /* not reached */
425     return rv;
426 }
427 
428 
apreq_cookie_serialize(const apreq_cookie_t * c,char * buf,apr_size_t len)429 APREQ_DECLARE(int) apreq_cookie_serialize(const apreq_cookie_t *c,
430                                           char *buf, apr_size_t len)
431 {
432     /*  The format string must be large enough to accomodate all
433      *  of the cookie attributes.  The current attributes sum to
434      *  ~90 characters (w/ 6-8 padding chars per attr), so anything
435      *  over 100 should be fine.
436      */
437 
438     unsigned version = apreq_cookie_version(c);
439     char format[128] = "%s=%s";
440     char *f = format + strlen(format);
441 
442     /* XXX protocol enforcement (for debugging, anyway) ??? */
443 
444     if (c->v.name == NULL)
445         return -1;
446 
447 #define NULL2EMPTY(attr) (attr ? attr : "")
448 
449 
450     if (version == NETSCAPE) {
451         char expires[APR_RFC822_DATE_LEN] = {0};
452 
453 #define ADD_NS_ATTR(name) do {                  \
454     if (c->name != NULL)                        \
455         strcpy(f, "; " #name "=%s");            \
456     else                                        \
457         strcpy(f, "%0.s");                      \
458     f += strlen(f);                             \
459 } while (0)
460 
461         ADD_NS_ATTR(path);
462         ADD_NS_ATTR(domain);
463 
464         if (c->max_age != -1) {
465             strcpy(f, "; expires=%s");
466             apr_rfc822_date(expires, c->max_age + apr_time_now());
467             expires[7] = '-';
468             expires[11] = '-';
469         }
470         else
471             strcpy(f, "");
472 
473         f += strlen(f);
474 
475         if (apreq_cookie_is_secure(c))
476             strcpy(f, "; secure");
477 
478         f += strlen(f);
479 
480         if (apreq_cookie_is_httponly(c))
481             strcpy(f, "; HttpOnly");
482 
483         return apr_snprintf(buf, len, format, c->v.name, c->v.data,
484            NULL2EMPTY(c->path), NULL2EMPTY(c->domain), expires);
485     }
486 
487     /* c->version == RFC */
488 
489     strcpy(f,"; Version=%u");
490     f += strlen(f);
491 
492 /* ensure RFC attributes are always quoted */
493 #define ADD_RFC_ATTR(name) do {                 \
494     if (c->name != NULL)                        \
495         if (*c->name == '"')                    \
496             strcpy(f, "; " #name "=%s");        \
497         else                                    \
498             strcpy(f, "; " #name "=\"%s\"");    \
499     else                                        \
500         strcpy(f, "%0.s");                      \
501     f += strlen (f);                            \
502 } while (0)
503 
504     ADD_RFC_ATTR(path);
505     ADD_RFC_ATTR(domain);
506     ADD_RFC_ATTR(port);
507     ADD_RFC_ATTR(comment);
508     ADD_RFC_ATTR(commentURL);
509 
510     strcpy(f, c->max_age != -1 ? "; max-age=%" APR_TIME_T_FMT : "");
511 
512     f += strlen(f);
513 
514     if (apreq_cookie_is_secure(c))
515         strcpy(f, "; secure");
516 
517     f += strlen(f);
518 
519     if (apreq_cookie_is_httponly(c))
520         strcpy(f, "; HttpOnly");
521 
522     return apr_snprintf(buf, len, format, c->v.name, c->v.data, version,
523                         NULL2EMPTY(c->path), NULL2EMPTY(c->domain),
524                         NULL2EMPTY(c->port), NULL2EMPTY(c->comment),
525                         NULL2EMPTY(c->commentURL), apr_time_sec(c->max_age));
526 }
527 
528 
apreq_cookie_as_string(const apreq_cookie_t * c,apr_pool_t * p)529 APREQ_DECLARE(char*) apreq_cookie_as_string(const apreq_cookie_t *c,
530                                             apr_pool_t *p)
531 {
532     int n = apreq_cookie_serialize(c, NULL, 0);
533     char *s = apr_palloc(p, n + 1);
534     apreq_cookie_serialize(c, s, n + 1);
535     return s;
536 }
537 
538