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