1
2 #include <libwebsockets.h>
3 #include "private-lib-core.h"
4
5 //#define LWS_COOKIE_DEBUG
6
7 #if defined(LWS_COOKIE_DEBUG)
8 #define lwsl_cookie lwsl_notice
9 #else
10 #define lwsl_cookie lwsl_debug
11 #endif
12
13 #define LWS_COOKIE_MAX_CACHE_NAME_LEN 128
14
15 #define lws_tolower(_c) (((_c) >= 'A' && (_c) <= 'Z') ? \
16 (char)((_c) + 'a' - 'A') : \
17 (char)(_c))
18
19 #define LWS_COOKIE_NSC_FORMAT "%.*s\t"\
20 "%s\t"\
21 "%.*s\t"\
22 "%s\t"\
23 "%llu\t"\
24 "%.*s\t"\
25 "%.*s"
26
27 static const char *const mon = "janfebmaraprnayjunjulaugsepoctnovdec";
28
29 enum lws_cookie_nsc_f {
30 LWSC_NSC_DOMAIN,
31 LWSC_NSC_HOSTONLY,
32 LWSC_NSC_PATH,
33 LWSC_NSC_SECURE,
34 LWSC_NSC_EXPIRES,
35 LWSC_NSC_NAME,
36 LWSC_NSC_VALUE,
37
38 LWSC_NSC_COUNT,
39 };
40
41 enum lws_cookie_elements {
42 CE_DOMAIN,
43 CE_PATH,
44 CE_EXPIRES,
45 CE_MAXAGE,
46 CE_NAME,
47 CE_VALUE,
48
49 CE_HOSTONLY, /* these are bool, NULL = 0, non-NULL = 1 */
50 CE_SECURE,
51
52 CE_COUNT
53 };
54
55 struct lws_cookie {
56 const char *f[CE_COUNT];
57 size_t l[CE_COUNT];
58
59 unsigned int httponly:1;
60 };
61
62 static int
lws_cookie_parse_date(const char * d,size_t len,time_t * t)63 lws_cookie_parse_date(const char *d, size_t len, time_t *t)
64 {
65 struct tm date;
66 int offset = 0, i;
67
68 memset(&date, 0, sizeof(date));
69
70 while (len) {
71 if (isalnum((int)*d)) {
72 offset++;
73 goto next;
74 }
75 switch (offset) {
76 case 2:
77 if (*d == ':' && len >= 6) {
78 date.tm_hour = atoi(d - 2);
79 if (date.tm_hour < 0 || date.tm_hour > 23)
80 return -1;
81 date.tm_min = atoi(d + 1);
82 if (date.tm_min < 0 || date.tm_min > 60)
83 return -1;
84 date.tm_sec = atoi(d + 4);
85 if (date.tm_sec < 0 || date.tm_sec > 61)
86 /* leap second */
87 return -1;
88
89 d += 6;
90 len -= 6;
91 offset = 0;
92 continue;
93 }
94
95 if (!date.tm_mday) {
96 date.tm_mday = atoi(d - 2);
97 if (date.tm_mday < 1 || date.tm_mday > 31)
98 return -1;
99 goto next2;
100 }
101
102 if (!date.tm_year) {
103 date.tm_year = atoi(d - 2);
104 if (date.tm_year < 0 || date.tm_year > 99)
105 return -1;
106 if (date.tm_year < 70)
107 date.tm_year += 100;
108 }
109 goto next2;
110
111 case 3:
112 for (i = 0; i < 36; i += 3) {
113 if (lws_tolower(*(d - 3)) == mon[i] &&
114 lws_tolower(*(d - 2)) == mon[i + 1] &&
115 lws_tolower(*(d - 1)) == mon[i + 2]) {
116 date.tm_mon = i / 3;
117 break;
118 }
119 }
120 goto next2;
121
122 case 4:
123 if (!date.tm_year) {
124 date.tm_year = atoi(d - 4);
125 if (date.tm_year < 1601)
126 return -1;
127 date.tm_year -= 1900;
128 }
129 goto next2;
130
131 default:
132 goto next2;
133 }
134
135 next2:
136 offset = 0;
137 next:
138 d++;
139 len--;
140 }
141
142 *t = mktime(&date);
143
144 if (*t < 0)
145 return -1;
146
147 return 0;
148 }
149
150 static void
lws_cookie_rm_sws(const char ** buf_p,size_t * len_p)151 lws_cookie_rm_sws(const char **buf_p, size_t *len_p)
152 {
153 const char *buf;
154 size_t len;
155
156 if (!buf_p || !*buf_p || !len_p || !*len_p) {
157 lwsl_err("%s: false parameter\n", __func__);
158 return;
159 }
160
161 buf = *buf_p;
162 len = *len_p;
163
164 while (buf[0] == ' ' && len > 0) {
165 buf++;
166 len--;
167 }
168
169 while (len && buf[len - 1] == ' ')
170 len--;
171
172 *buf_p = buf;
173 *len_p = len;
174 }
175
176 static int
is_iprefix(const char * h,size_t hl,const char * n,size_t nl)177 is_iprefix(const char *h, size_t hl, const char *n, size_t nl)
178 {
179 if (!h || !n || nl > hl)
180 return 0;
181
182 while (nl) {
183 nl--;
184 if (lws_tolower(h[nl]) != lws_tolower(n[nl]))
185 return 0;
186 }
187 return 1;
188 }
189
190 static int
lws_cookie_compile_cache_name(char * buf,size_t buf_len,struct lws_cookie * c)191 lws_cookie_compile_cache_name(char *buf, size_t buf_len, struct lws_cookie *c)
192 {
193 if (!buf || !c->f[CE_DOMAIN] || !c->f[CE_PATH] || !c->f[CE_NAME] ||
194 c->l[CE_DOMAIN] + c->l[CE_PATH] + c->l[CE_NAME] + 6 > buf_len)
195 return -1;
196
197 memcpy(buf, c->f[CE_DOMAIN], c->l[CE_DOMAIN]);
198 buf += c->l[CE_DOMAIN];
199 *buf++ = '|';
200
201 memcpy(buf, c->f[CE_PATH], c->l[CE_PATH]);
202 buf += c->l[CE_PATH];
203 *buf++ = '|';
204
205 memcpy(buf, c->f[CE_NAME], c->l[CE_NAME]);
206 buf += c->l[CE_NAME];
207 *buf = '\0';
208
209 return 0;
210 }
211
212 static int
lws_cookie_parse_nsc(struct lws_cookie * c,const char * b,size_t l)213 lws_cookie_parse_nsc(struct lws_cookie *c, const char *b, size_t l)
214 {
215 enum lws_cookie_nsc_f state = LWSC_NSC_DOMAIN;
216 size_t n = 0;
217
218 if (!c || !b || l < 13)
219 return -1;
220
221 memset(c, 0, sizeof(*c));
222 lwsl_cookie("%s: parsing (%.*s) \n", __func__, (int)l, b);
223
224 while (l) {
225 l--;
226 if (b[n] != '\t' && l) {
227 n++;
228 continue;
229 }
230 switch (state) {
231 case LWSC_NSC_DOMAIN:
232 c->f[CE_DOMAIN] = b;
233 c->l[CE_DOMAIN] = n;
234 break;
235 case LWSC_NSC_PATH:
236 c->f[CE_PATH] = b;
237 c->l[CE_PATH] = n;
238 break;
239 case LWSC_NSC_EXPIRES:
240 c->f[CE_EXPIRES] = b;
241 c->l[CE_EXPIRES] = n;
242 break;
243 case LWSC_NSC_NAME:
244 c->f[CE_NAME] = b;
245 c->l[CE_NAME] = n;
246 break;
247
248 case LWSC_NSC_HOSTONLY:
249 if (b[0] == 'T') {
250 c->f[CE_HOSTONLY] = b;
251 c->l[CE_HOSTONLY] = 1;
252 }
253 break;
254 case LWSC_NSC_SECURE:
255 if (b[0] == 'T') {
256 c->f[CE_SECURE] = b;
257 c->l[CE_SECURE] = 1;
258 }
259 break;
260
261 case LWSC_NSC_VALUE:
262 c->f[CE_VALUE] = b;
263 c->l[CE_VALUE] = n + 1;
264
265 for (n = 0; n < LWS_ARRAY_SIZE(c->f); n++)
266 lwsl_cookie("%s: %d: %.*s\n", __func__,
267 (int)n, (int)c->l[n], c->f[n]);
268
269 return 0;
270 default:
271 return -1;
272 }
273
274 b += n + 1;
275 n = 0;
276 state++;
277 }
278
279 return -1;
280 }
281
282 static int
lws_cookie_write_nsc(struct lws * wsi,struct lws_cookie * c)283 lws_cookie_write_nsc(struct lws *wsi, struct lws_cookie *c)
284 {
285 char cache_name[LWS_COOKIE_MAX_CACHE_NAME_LEN];
286 const char *ads, *path;
287 struct lws_cache_ttl_lru *l1;
288 struct client_info_stash *stash;
289 char *cookie_string = NULL, *dl;
290 /* 6 tabs + 20 for max time_t + 2 * TRUE/FALSE + null */
291 size_t size = 6 + 20 + 10 + 1;
292 time_t expires = 0;
293 int ret = 0;
294
295 if (!wsi || !c)
296 return -1;
297
298 l1 = wsi->a.context->l1;
299 if (!l1 || !wsi->a.context->nsc)
300 return -1;
301
302 stash = wsi->stash ? wsi->stash : lws_get_network_wsi(wsi)->stash;
303 if (stash) {
304 ads = stash->cis[CIS_ADDRESS];
305 path = stash->cis[CIS_PATH];
306 } else {
307 ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
308 path = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI);
309 }
310 if (!ads || !path)
311 return -1;
312
313 if (!c->f[CE_NAME] || !c->f[CE_VALUE]) {
314 lwsl_err("%s: malformed c\n", __func__);
315
316 return -1;
317 }
318
319 if (!c->f[CE_EXPIRES]) {
320 /*
321 * Currently we just take the approach to reject session cookies
322 */
323 lwsl_warn("%s: reject session cookies\n", __func__);
324
325 return 0;
326 }
327
328 if (!c->f[CE_DOMAIN]) {
329 c->f[CE_HOSTONLY] = "T";
330 c->l[CE_HOSTONLY] = 1;
331 c->f[CE_DOMAIN] = ads;
332 c->l[CE_DOMAIN] = strlen(ads);
333 }
334
335 if (!c->f[CE_PATH]) {
336 c->f[CE_PATH] = path;
337 c->l[CE_PATH] = strlen(path);
338 dl = memchr(c->f[CE_PATH], '?', c->l[CE_PATH]);
339 if (dl)
340 c->l[CE_PATH] = (size_t)(dl - c->f[CE_PATH]);
341 }
342
343 if (lws_cookie_compile_cache_name(cache_name, sizeof(cache_name), c))
344 return -1;
345
346 if (c->f[CE_EXPIRES] &&
347 lws_cookie_parse_date(c->f[CE_EXPIRES], c->l[CE_EXPIRES], &expires)) {
348 lwsl_err("%s: can't parse date %.*s\n", __func__,
349 (int)c->l[CE_EXPIRES], c->f[CE_EXPIRES]);
350 return -1;
351 }
352
353 size += c->l[CE_NAME] + c->l[CE_VALUE] + c->l[CE_DOMAIN] + c->l[CE_PATH];
354 cookie_string = (char *)lws_malloc(size, __func__);
355 if (!cookie_string) {
356 lwsl_err("%s: OOM\n",__func__);
357
358 return -1;
359 }
360
361 lws_snprintf(cookie_string, size, LWS_COOKIE_NSC_FORMAT,
362 (int)c->l[CE_DOMAIN], c->f[CE_DOMAIN],
363 c->f[CE_HOSTONLY] ? "TRUE" : "FALSE",
364 (int)c->l[CE_PATH], c->f[CE_PATH],
365 c->f[CE_SECURE] ? "TRUE" : "FALSE",
366 (unsigned long long)expires,
367 (int)c->l[CE_NAME], c->f[CE_NAME],
368 (int)c->l[CE_VALUE], c->f[CE_VALUE]);
369
370 lwsl_cookie("%s: name %s\n", __func__, cache_name);
371 lwsl_cookie("%s: c %s\n", __func__, cookie_string);
372
373 if (lws_cache_write_through(l1, cache_name,
374 (const uint8_t *)cookie_string,
375 strlen(cookie_string),
376 (lws_usec_t)((unsigned long long)expires *
377 (lws_usec_t)LWS_US_PER_SEC), NULL)) {
378 ret = -1;
379 goto exit;
380 }
381
382 #if defined(LWS_COOKIE_DEBUG)
383 char *po;
384 if (lws_cache_item_get(l1, cache_name, (const void **)&po, &size) ||
385 size != strlen(cookie_string) || memcmp(po, cookie_string, size)) {
386 lwsl_err("%s: L1 '%s' missing\n", __func__, cache_name);
387 }
388
389 if (lws_cache_item_get(wsi->a.context->nsc, cache_name,
390 (const void **)&po, &size) ||
391 size != strlen(cookie_string) ||
392 memcmp(po, cookie_string, size)) {
393 lwsl_err("%s: NSC '%s' missing, size %llu, po %s\n", __func__,
394 cache_name, (unsigned long long)size, po);
395 }
396 #endif
397
398 exit:
399 lws_free(cookie_string);
400
401 return ret;
402 }
403
404 static int
lws_cookie_attach_cookies(struct lws * wsi,char * buf,char * end)405 lws_cookie_attach_cookies(struct lws *wsi, char *buf, char *end)
406 {
407 const char *domain, *path, *dl_domain, *dl_path, *po;
408 char cache_name[LWS_COOKIE_MAX_CACHE_NAME_LEN];
409 size_t domain_len, path_len, size, ret = 0;
410 struct lws_cache_ttl_lru *l1;
411 struct client_info_stash *stash;
412 lws_cache_results_t cr;
413 struct lws_cookie c;
414 int hostdomain = 1;
415 char *p, *p1;
416
417 if (!wsi)
418 return -1;
419
420 stash = wsi->stash ? wsi->stash : lws_get_network_wsi(wsi)->stash;
421 if (!stash || !stash->cis[CIS_ADDRESS] ||
422 !stash->cis[CIS_PATH])
423 return -1;
424
425 l1 = wsi->a.context->l1;
426 if (!l1 || !wsi->a.context->nsc){
427 lwsl_err("%s:no cookiejar\n", __func__);
428 return -1;
429 }
430
431 memset(&c, 0, sizeof(c));
432
433 domain = stash->cis[CIS_ADDRESS];
434 path = stash->cis[CIS_PATH];
435
436 if (!domain || !path)
437 return -1;
438
439 path_len = strlen(path);
440
441 /* remove query string if exist */
442 dl_path = memchr(path, '?', path_len);
443 if (dl_path)
444 path_len = lws_ptr_diff_size_t(dl_path, path);
445
446 /* remove last slash if exist */
447 if (path_len != 1 && path[path_len - 1] == '/')
448 path_len--;
449
450 if (!path_len)
451 return -1;
452
453 lwsl_cookie("%s: path %.*s len %d\n", __func__, (int)path_len, path, (int)path_len);
454
455 /* when dest buf is not provided, we only return size of cookie string */
456 if (!buf || !end)
457 p = NULL;
458 else
459 p = buf;
460
461 /* iterate through domain and path levels to find matching cookies */
462 dl_domain = domain;
463 while (dl_domain) {
464 domain_len = strlen(domain);
465 dl_domain = memchr(domain, '.', domain_len);
466 /* don't match top level domain */
467 if (!dl_domain)
468 break;
469
470 if (domain_len + path_len + 6 > sizeof(cache_name))
471 return -1;
472
473 /* compile key string "[domain]|[path]|*"" */
474 p1 = cache_name;
475 memcpy(p1, domain, domain_len);
476 p1 += domain_len;
477 *p1 = '|';
478 p1++;
479 memcpy(p1, path, path_len);
480 p1 += path_len;
481 *p1 = '|';
482 p1++;
483 *p1 = '*';
484 p1++;
485 *p1 = '\0';
486
487 lwsl_cookie("%s: looking for %s\n", __func__, cache_name);
488
489 if (!lws_cache_lookup(l1, cache_name,
490 (const void **)&cr.ptr, &cr.size)) {
491
492 while (!lws_cache_results_walk(&cr)) {
493 lwsl_cookie(" %s (%d)\n", (const char *)cr.tag,
494 (int)cr.payload_len);
495
496 if (lws_cache_item_get(l1, (const char *)cr.tag,
497 (const void **)&po, &size) ||
498 lws_cookie_parse_nsc(&c, po, size)) {
499 lwsl_err("%s: failed to get c '%s'\n",
500 __func__, cr.tag);
501 break;
502 }
503
504 if (c.f[CE_HOSTONLY] && !hostdomain){
505 lwsl_cookie("%s: not sending this\n",
506 __func__);
507 continue;
508 }
509
510 if (p) {
511 if (ret) {
512 *p = ';';
513 p++;
514 *p = ' ';
515 p++;
516 }
517
518 memcpy(p, c.f[CE_NAME], c.l[CE_NAME]);
519 p += c.l[CE_NAME];
520 *p = '=';
521 p++;
522 memcpy(p, c.f[CE_VALUE], c.l[CE_VALUE]);
523 p += c.l[CE_VALUE];
524 }
525
526 if (ret)
527 ret += 2;
528 ret += c.l[CE_NAME] + 1 + c.l[CE_VALUE];
529
530 }
531 }
532
533 domain = dl_domain + 1;
534 hostdomain = 0;
535 }
536
537 lwsl_notice("%s: c len (%d)\n", __func__, (int)ret);
538
539 return (int)ret;
540 }
541
542 static struct {
543 const char *const name;
544 uint8_t len;
545 } cft[] = {
546 { "domain=", 7 },
547 { "path=", 5 },
548 { "expires=", 8 },
549 { "max-age=", 8 },
550 { "httponly", 8 },
551 { "secure", 6 }
552 };
553
554 int
lws_parse_set_cookie(struct lws * wsi)555 lws_parse_set_cookie(struct lws *wsi)
556 {
557 char *tk_head, *tk_end, *buf_head, *buf_end, *cookiep, *dl;
558 struct lws_cache_ttl_lru *l1;
559 struct lws_cookie c;
560 size_t fl;
561 int f, n;
562
563 if (!wsi)
564 return -1;
565
566 l1 = wsi->a.context->l1;
567 if (!l1)
568 return -1;
569
570 f = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_SET_COOKIE];
571
572 while (f) {
573 cookiep = wsi->http.ah->data + wsi->http.ah->frags[f].offset;
574 fl = wsi->http.ah->frags[f].len;
575 f = wsi->http.ah->frags[f].nfrag;
576
577 if (!cookiep || !fl)
578 continue;
579
580 #if defined(LWS_COOKIE_DEBUG)
581 lwsl_notice("%s:parsing: %.*s\n", __func__, (int)fl, cookiep);
582 #endif
583
584 buf_head = cookiep;
585 buf_end = cookiep + fl - 1;
586 memset(&c, 0, sizeof(struct lws_cookie));
587
588 do {
589 tk_head = buf_head;
590 tk_end = memchr(buf_head, ';',
591 (size_t)(buf_end - buf_head + 1));
592 if (!tk_end) {
593 tk_end = buf_end;
594 buf_head = buf_end;
595 } else {
596 buf_head = tk_end + 1;
597 tk_end--;
598 }
599
600 if (c.f[CE_NAME])
601 goto parse_av;
602
603 /*
604 * find name value, remove leading trailing
605 * WS and DQ for value
606 */
607
608 dl = memchr(tk_head, '=', lws_ptr_diff_size_t(tk_end,
609 tk_head + 1));
610 if (!dl || dl == tk_head)
611 return -1;
612
613 c.f[CE_NAME] = tk_head;
614 c.l[CE_NAME] = lws_ptr_diff_size_t(dl, tk_head);
615 lws_cookie_rm_sws(&c.f[CE_NAME], &c.l[CE_NAME]);
616
617 if (!c.l[CE_NAME])
618 return -1;
619
620 lwsl_cookie("%s: c name l %d v:%.*s\n", __func__,
621 (int)c.l[CE_NAME],
622 (int)c.l[CE_NAME], c.f[CE_NAME]);
623 c.f[CE_VALUE] = dl + 1;
624 c.l[CE_VALUE] = lws_ptr_diff_size_t(tk_end,
625 c.f[CE_VALUE]) + 1;
626
627 lws_cookie_rm_sws(&c.f[CE_VALUE], &c.l[CE_VALUE]);
628 if (c.l[CE_VALUE] >= 2 && c.f[CE_VALUE][0] == '\"') {
629 c.f[CE_VALUE]++;
630 c.l[CE_VALUE] -= 2;
631 }
632 lwsl_cookie("%s: c value l %d v:%.*s\n", __func__,
633 (int)c.l[CE_VALUE], (int)c.l[CE_VALUE],
634 c.f[CE_VALUE]);
635 continue;
636
637 parse_av:
638 while (*tk_head == ' ') {
639 if (tk_head == tk_end)
640 return -1;
641
642 tk_head++;
643 }
644
645 for (n = 0; n < (int)LWS_ARRAY_SIZE(cft); n++) {
646 if (lws_tolower(*tk_head) != cft[n].name[0])
647 continue;
648
649 if (!is_iprefix(tk_head,
650 lws_ptr_diff_size_t(tk_end,
651 tk_head) + 1,
652 cft[n].name, cft[n].len))
653 continue;
654
655 if (n == 4 || n == 5) {
656 c.f[n] = "T";
657 c.l[n] = 1;
658 break;
659 }
660
661 c.f[n] = tk_head + cft[n].len;
662 c.l[n] = lws_ptr_diff_size_t(tk_end, c.f[n]) + 1;
663 lws_cookie_rm_sws(&c.f[n], &c.l[n]);
664
665 if (n == CE_DOMAIN && c.l[0] &&
666 c.f[n][0] == '.'){
667 c.f[n]++;
668 c.l[n]--;
669 }
670
671 lwsl_cookie("%s: %s l %d v:%.*s\n", __func__,
672 cft[n].name, (int)c.l[n],
673 (int)c.l[n], c.f[n]);
674 break;
675 }
676
677 } while (tk_end != buf_end);
678
679 if (lws_cookie_write_nsc(wsi, &c))
680 lwsl_err("%s:failed to write nsc\n", __func__);
681 }
682
683 return 0;
684 }
685
686 int
lws_cookie_send_cookies(struct lws * wsi,char ** pp,char * end)687 lws_cookie_send_cookies(struct lws *wsi, char **pp, char *end)
688 {
689 char *p;
690 int size;
691
692 if (!wsi || !pp || !(*pp) || !end)
693 return -1;
694
695 size = lws_cookie_attach_cookies(wsi, NULL, NULL);
696
697 if (!size)
698 return 0;
699 if (size < 0) {
700 lwsl_err("%s:failed to get cookie string size\n", __func__);
701 return -1;
702 }
703
704 lwsl_notice("%s: size %d\n", __func__, size);
705
706 #if defined(LWS_COOKIE_DEBUG)
707 char *p_dbg = *pp;
708 #endif
709
710 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_COOKIE, NULL, size,
711 (unsigned char **)pp, (unsigned char *)end))
712 return -1;
713
714 #if defined(LWS_COOKIE_DEBUG)
715 lwsl_notice("%s: dummy copy (%.*s) \n", __func__, (int)(*pp - p_dbg), p_dbg);
716 #endif
717
718
719 #ifdef LWS_WITH_HTTP2
720 if (lws_wsi_is_h2(wsi))
721 p = *pp - size;
722 else
723 #endif
724 p = *pp - size - 2;
725
726 if (lws_cookie_attach_cookies(wsi, p, p + size) <= 0) {
727 lwsl_err("%s:failed to attach cookies\n", __func__);
728 return -1;
729 }
730
731 #if defined(LWS_COOKIE_DEBUG)
732 lwsl_notice("%s: real copy (%.*s) total len %d\n", __func__, (int)(*pp - p_dbg), p_dbg, (int)(*pp - p_dbg));
733 lwsl_hexdump_notice(p_dbg, (size_t)(*pp - p_dbg));
734 #endif
735
736 return 0;
737 }
738