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