1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <ndb.h>
5 #include <fcall.h>
6 #include <thread.h>
7 #include <9p.h>
8 #include <ctype.h>
9 #include "dat.h"
10 #include "fns.h"
11
12 int cookiedebug;
13
14 typedef struct Cookie Cookie;
15 typedef struct Jar Jar;
16
17 struct Cookie
18 {
19 /* external info */
20 char* name;
21 char* value;
22 char* dom; /* starts with . */
23 char* path;
24 char* version;
25 char* comment; /* optional, may be nil */
26
27 uint expire; /* time of expiration: ~0 means when webcookies dies */
28 int secure;
29 int explicitdom; /* dom was explicitly set */
30 int explicitpath; /* path was explicitly set */
31 int netscapestyle;
32
33 /* internal info */
34 int deleted;
35 int mark;
36 int ondisk;
37 };
38
39 struct Jar
40 {
41 Cookie *c;
42 int nc;
43 int mc;
44
45 Qid qid;
46 int dirty;
47 char *file;
48 char *lockfile;
49 };
50
51 struct {
52 char *s;
53 int offset;
54 int ishttp;
55 } stab[] = {
56 "domain", offsetof(Cookie, dom), 1,
57 "path", offsetof(Cookie, path), 1,
58 "name", offsetof(Cookie, name), 0,
59 "value", offsetof(Cookie, value), 0,
60 "comment", offsetof(Cookie, comment), 1,
61 "version", offsetof(Cookie, version), 1,
62 };
63
64 struct {
65 char *s;
66 int offset;
67 } itab[] = {
68 "expire", offsetof(Cookie, expire),
69 "secure", offsetof(Cookie, secure),
70 "explicitdomain", offsetof(Cookie, explicitdom),
71 "explicitpath", offsetof(Cookie, explicitpath),
72 "netscapestyle", offsetof(Cookie, netscapestyle),
73 };
74
75 #pragma varargck type "J" Jar*
76 #pragma varargck type "K" Cookie*
77
78 /* HTTP format */
79 static int
jarfmt(Fmt * fp)80 jarfmt(Fmt *fp)
81 {
82 int i;
83 Jar *jar;
84
85 jar = va_arg(fp->args, Jar*);
86
87 if(jar == nil || jar->nc == 0)
88 return 0;
89
90 fmtstrcpy(fp, "Cookie: ");
91 if(jar->c[0].version)
92 fmtprint(fp, "$Version=%s; ", jar->c[0].version);
93 for(i=0; i<jar->nc; i++)
94 fmtprint(fp, "%s%s=%s", i ? "; ": "", jar->c[i].name, jar->c[i].value);
95 fmtstrcpy(fp, "\r\n");
96 return 0;
97 }
98
99 /* individual cookie */
100 static int
cookiefmt(Fmt * fp)101 cookiefmt(Fmt *fp)
102 {
103 int j, k, first;
104 char *t;
105 Cookie *c;
106
107 c = va_arg(fp->args, Cookie*);
108
109 first = 1;
110 for(j=0; j<nelem(stab); j++){
111 t = *(char**)((uintptr)c+stab[j].offset);
112 if(t == nil)
113 continue;
114 if(first)
115 first = 0;
116 else
117 fmtstrcpy(fp, " ");
118 fmtprint(fp, "%s=%q", stab[j].s, t);
119 }
120 for(j=0; j<nelem(itab); j++){
121 k = *(int*)((uintptr)c+itab[j].offset);
122 if(k == 0)
123 continue;
124 if(first)
125 first = 0;
126 else
127 fmtstrcpy(fp, " ");
128 fmtprint(fp, "%s=%ud", itab[j].s, k);
129 }
130 return 0;
131 }
132
133 /*
134 * sort cookies:
135 * - alpha by name
136 * - alpha by domain
137 * - longer paths first, then alpha by path (RFC2109 4.3.4)
138 */
139 static int
cookiecmp(Cookie * a,Cookie * b)140 cookiecmp(Cookie *a, Cookie *b)
141 {
142 int i;
143
144 if((i = strcmp(a->name, b->name)) != 0)
145 return i;
146 if((i = cistrcmp(a->dom, b->dom)) != 0)
147 return i;
148 if((i = strlen(b->path) - strlen(a->path)) != 0)
149 return i;
150 if((i = strcmp(a->path, b->path)) != 0)
151 return i;
152 return 0;
153 }
154
155 static int
exactcookiecmp(Cookie * a,Cookie * b)156 exactcookiecmp(Cookie *a, Cookie *b)
157 {
158 int i;
159
160 if((i = cookiecmp(a, b)) != 0)
161 return i;
162 if((i = strcmp(a->value, b->value)) != 0)
163 return i;
164 if(a->version || b->version){
165 if(!a->version)
166 return -1;
167 if(!b->version)
168 return 1;
169 if((i = strcmp(a->version, b->version)) != 0)
170 return i;
171 }
172 if(a->comment || b->comment){
173 if(!a->comment)
174 return -1;
175 if(!b->comment)
176 return 1;
177 if((i = strcmp(a->comment, b->comment)) != 0)
178 return i;
179 }
180 if((i = b->expire - a->expire) != 0)
181 return i;
182 if((i = b->secure - a->secure) != 0)
183 return i;
184 if((i = b->explicitdom - a->explicitdom) != 0)
185 return i;
186 if((i = b->explicitpath - a->explicitpath) != 0)
187 return i;
188 if((i = b->netscapestyle - a->netscapestyle) != 0)
189 return i;
190
191 return 0;
192 }
193
194 static void
freecookie(Cookie * c)195 freecookie(Cookie *c)
196 {
197 int i;
198
199 for(i=0; i<nelem(stab); i++)
200 free(*(char**)((uintptr)c+stab[i].offset));
201 }
202
203 static void
copycookie(Cookie * c)204 copycookie(Cookie *c)
205 {
206 int i;
207 char **ps;
208
209 for(i=0; i<nelem(stab); i++){
210 ps = (char**)((uintptr)c+stab[i].offset);
211 if(*ps)
212 *ps = estrdup9p(*ps);
213 }
214 }
215
216 static void
delcookie(Jar * j,Cookie * c)217 delcookie(Jar *j, Cookie *c)
218 {
219 int i;
220
221 j->dirty = 1;
222 i = c - j->c;
223 if(i < 0 || i >= j->nc)
224 abort();
225 c->deleted = 1;
226 }
227
228 static void
addcookie(Jar * j,Cookie * c)229 addcookie(Jar *j, Cookie *c)
230 {
231 int i;
232
233 if(!c->name || !c->value || !c->path || !c->dom){
234 fprint(2, "not adding incomplete cookie\n");
235 return;
236 }
237
238 if(cookiedebug)
239 fprint(2, "add %K\n", c);
240
241 for(i=0; i<j->nc; i++)
242 if(cookiecmp(&j->c[i], c) == 0){
243 if(cookiedebug)
244 fprint(2, "cookie %K matches %K\n", &j->c[i], c);
245 if(exactcookiecmp(&j->c[i], c) == 0){
246 if(cookiedebug)
247 fprint(2, "\texactly\n");
248 j->c[i].mark = 0;
249 return;
250 }
251 delcookie(j, &j->c[i]);
252 }
253
254 j->dirty = 1;
255 if(j->nc == j->mc){
256 j->mc += 16;
257 j->c = erealloc9p(j->c, j->mc*sizeof(Cookie));
258 }
259 j->c[j->nc] = *c;
260 copycookie(&j->c[j->nc]);
261 j->nc++;
262 }
263
264 static void
purgejar(Jar * j)265 purgejar(Jar *j)
266 {
267 int i;
268
269 for(i=j->nc-1; i>=0; i--){
270 if(!j->c[i].deleted)
271 continue;
272 freecookie(&j->c[i]);
273 --j->nc;
274 j->c[i] = j->c[j->nc];
275 }
276 }
277
278 static void
addtojar(Jar * jar,char * line,int ondisk)279 addtojar(Jar *jar, char *line, int ondisk)
280 {
281 Cookie c;
282 int i, j, nf, *pint;
283 char *f[20], *attr, *val, **pstr;
284
285 memset(&c, 0, sizeof c);
286 c.expire = ~0;
287 c.ondisk = ondisk;
288 nf = tokenize(line, f, nelem(f));
289 for(i=0; i<nf; i++){
290 attr = f[i];
291 if((val = strchr(attr, '=')) != nil)
292 *val++ = '\0';
293 else
294 val = "";
295 /* string attributes */
296 for(j=0; j<nelem(stab); j++){
297 if(strcmp(stab[j].s, attr) == 0){
298 pstr = (char**)((uintptr)&c+stab[j].offset);
299 *pstr = val;
300 }
301 }
302 /* integer attributes */
303 for(j=0; j<nelem(itab); j++){
304 if(strcmp(itab[j].s, attr) == 0){
305 pint = (int*)((uintptr)&c+itab[j].offset);
306 if(val[0]=='\0')
307 *pint = 1;
308 else
309 *pint = strtoul(val, 0, 0);
310 }
311 }
312 }
313 if(c.name==nil || c.value==nil || c.dom==nil || c.path==nil){
314 if(cookiedebug)
315 fprint(2, "ignoring fractional cookie %K\n", &c);
316 return;
317 }
318 addcookie(jar, &c);
319 }
320
321 static Jar*
newjar(void)322 newjar(void)
323 {
324 Jar *jar;
325
326 jar = emalloc9p(sizeof(Jar));
327 return jar;
328 }
329
330 static int
expirejar(Jar * jar,int exiting)331 expirejar(Jar *jar, int exiting)
332 {
333 int i, n;
334 uint now;
335
336 now = time(0);
337 n = 0;
338 for(i=0; i<jar->nc; i++){
339 if(jar->c[i].expire < now || (exiting && jar->c[i].expire==~0)){
340 delcookie(jar, &jar->c[i]);
341 n++;
342 }
343 }
344 return n;
345 }
346
347 static void
dumpjar(Jar * jar,char * desc)348 dumpjar(Jar *jar, char *desc)
349 {
350 int i;
351 Biobuf *b;
352 char *s;
353
354 print("%s\n", desc);
355 print("\tin memory:\n");
356
357 for(i=0; i<jar->nc; i++)
358 print("\t%K%s%s%s\n", &jar->c[i],
359 jar->c[i].ondisk ? " ondisk" : "",
360 jar->c[i].deleted ? " deleted" : "",
361 jar->c[i].mark ? " mark" : "");
362 print("\n\ton disk:\n");
363 if((b = Bopen(jar->file, OREAD)) == nil){
364 print("\tno file\n");
365 }else{
366 while((s = Brdstr(b, '\n', 1)) != nil){
367 print("\t%s\n", s);
368 free(s);
369 }
370 Bterm(b);
371 }
372 print("\n");
373 }
374
375 static int
syncjar(Jar * jar)376 syncjar(Jar *jar)
377 {
378 int i, fd;
379 char *line;
380 Dir *d;
381 Biobuf *b;
382 Qid q;
383
384 if(jar->file==nil)
385 return 0;
386
387 memset(&q, 0, sizeof q);
388 if((d = dirstat(jar->file)) != nil){
389 q = d->qid;
390 if(d->qid.path != jar->qid.path || d->qid.vers != jar->qid.vers)
391 jar->dirty = 1;
392 free(d);
393 }
394
395 if(jar->dirty == 0)
396 return 0;
397
398 fd = -1;
399 for(i=0; i<50; i++){
400 if((fd = create(jar->lockfile, OWRITE, DMEXCL|0666)) < 0){
401 sleep(100);
402 continue;
403 }
404 break;
405 }
406 if(fd < 0){
407 if(cookiedebug)
408 fprint(2, "open %s: %r", jar->lockfile);
409 werrstr("cannot acquire jar lock: %r");
410 return -1;
411 }
412
413 for(i=0; i<jar->nc; i++) /* mark is cleared by addcookie */
414 jar->c[i].mark = jar->c[i].ondisk;
415
416 if((b = Bopen(jar->file, OREAD)) == nil){
417 if(cookiedebug)
418 fprint(2, "Bopen %s: %r", jar->file);
419 werrstr("cannot read cookie file %s: %r", jar->file);
420 close(fd);
421 return -1;
422 }
423 for(; (line = Brdstr(b, '\n', 1)) != nil; free(line)){
424 if(*line == '#')
425 continue;
426 addtojar(jar, line, 1);
427 }
428 Bterm(b);
429
430 for(i=0; i<jar->nc; i++)
431 if(jar->c[i].mark && jar->c[i].expire != ~0)
432 delcookie(jar, &jar->c[i]);
433
434 purgejar(jar);
435
436 b = Bopen(jar->file, OWRITE);
437 if(b == nil){
438 if(cookiedebug)
439 fprint(2, "Bopen write %s: %r", jar->file);
440 close(fd);
441 return -1;
442 }
443 Bprint(b, "# webcookies cookie jar\n");
444 Bprint(b, "# comments and non-standard fields will be lost\n");
445 for(i=0; i<jar->nc; i++){
446 if(jar->c[i].expire == ~0)
447 continue;
448 Bprint(b, "%K\n", &jar->c[i]);
449 jar->c[i].ondisk = 1;
450 }
451 Bterm(b);
452
453 jar->dirty = 0;
454 close(fd);
455 if((d = dirstat(jar->file)) != nil){
456 jar->qid = d->qid;
457 free(d);
458 }
459 return 0;
460 }
461
462 static Jar*
readjar(char * file)463 readjar(char *file)
464 {
465 char *lock, *p;
466 Jar *jar;
467
468 jar = newjar();
469 lock = emalloc9p(strlen(file)+10);
470 strcpy(lock, file);
471 if((p = strrchr(lock, '/')) != nil)
472 p++;
473 else
474 p = lock;
475 memmove(p+2, p, strlen(p)+1);
476 p[0] = 'L';
477 p[1] = '.';
478 jar->lockfile = lock;
479 jar->file = file;
480 jar->dirty = 1;
481
482 if(syncjar(jar) < 0){
483 free(jar->file);
484 free(jar->lockfile);
485 free(jar);
486 return nil;
487 }
488 return jar;
489 }
490
491 static void
closejar(Jar * jar)492 closejar(Jar *jar)
493 {
494 int i;
495
496 if(jar == nil)
497 return;
498 expirejar(jar, 0);
499 if(syncjar(jar) < 0)
500 fprint(2, "warning: cannot rewrite cookie jar: %r\n");
501
502 for(i=0; i<jar->nc; i++)
503 freecookie(&jar->c[i]);
504
505 free(jar->file);
506 free(jar);
507 }
508
509 /*
510 * Domain name matching is per RFC2109, section 2:
511 *
512 * Hosts names can be specified either as an IP address or a FQHN
513 * string. Sometimes we compare one host name with another. Host A's
514 * name domain-matches host B's if
515 *
516 * * both host names are IP addresses and their host name strings match
517 * exactly; or
518 *
519 * * both host names are FQDN strings and their host name strings match
520 * exactly; or
521 *
522 * * A is a FQDN string and has the form NB, where N is a non-empty name
523 * string, B has the form .B', and B' is a FQDN string. (So, x.y.com
524 * domain-matches .y.com but not y.com.)
525 *
526 * Note that domain-match is not a commutative operation: a.b.c.com
527 * domain-matches .c.com, but not the reverse.
528 *
529 * (This does not verify that IP addresses and FQDN's are well-formed.)
530 */
531 static int
isdomainmatch(char * name,char * pattern)532 isdomainmatch(char *name, char *pattern)
533 {
534 int lname, lpattern;
535
536 if(cistrcmp(name, pattern)==0)
537 return 1;
538
539 if(strcmp(ipattr(name), "dom")==0 && pattern[0]=='.'){
540 lname = strlen(name);
541 lpattern = strlen(pattern);
542 /* e.g., name: www.google.com && pattern: .google.com */
543 if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0)
544 return 1;
545 /* e.g., name: google.com && pattern: .google.com */
546 if(lpattern > lname &&
547 cistrcmp(pattern+lpattern-lname, name) == 0)
548 return 1;
549 }
550 return 0;
551 }
552
553 /*
554 * RFC2109 4.3.4:
555 * - domain must match
556 * - path in cookie must be a prefix of request path
557 * - cookie must not have expired
558 */
559 static int
iscookiematch(Cookie * c,char * dom,char * path,uint now)560 iscookiematch(Cookie *c, char *dom, char *path, uint now)
561 {
562 return isdomainmatch(dom, c->dom)
563 && strncmp(c->path, path, strlen(c->path))==0
564 && (c->expire == 0 || c->expire >= now);
565 }
566
567 /*
568 * Produce a subjar of matching cookies.
569 * Secure cookies are only included if secure is set.
570 */
571 static Jar*
cookiesearch(Jar * jar,char * dom,char * path,int issecure)572 cookiesearch(Jar *jar, char *dom, char *path, int issecure)
573 {
574 int i;
575 Jar *j;
576 uint now;
577
578 if(cookiedebug)
579 fprint(2, "cookiesearch %s %s %d\n", dom, path, issecure);
580 now = time(0);
581 j = newjar();
582 for(i=0; i<jar->nc; i++){
583 if(cookiedebug)
584 fprint(2, "\ttry %s %s %d %s\n", jar->c[i].dom,
585 jar->c[i].path, jar->c[i].secure,
586 jar->c[i].name);
587 if((issecure || !jar->c[i].secure) &&
588 iscookiematch(&jar->c[i], dom, path, now)){
589 if(cookiedebug)
590 fprint(2, "\tmatched\n");
591 addcookie(j, &jar->c[i]);
592 }
593 }
594 if(j->nc == 0){
595 closejar(j);
596 werrstr("no cookies found");
597 return nil;
598 }
599 qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(void*, void*))cookiecmp);
600 return j;
601 }
602
603 /*
604 * RFC2109 4.3.2 security checks
605 */
606 static char*
isbadcookie(Cookie * c,char * dom,char * path)607 isbadcookie(Cookie *c, char *dom, char *path)
608 {
609 int lcdom, ldom;
610
611 if(strncmp(c->path, path, strlen(c->path)) != 0)
612 return "cookie path is not a prefix of the request path";
613
614 /*
615 * fgb says omitting this test is necessary to get some sites to work,
616 * but it seems dubious.
617 */
618 if(c->explicitdom && c->dom[0] != '.')
619 return "cookie domain doesn't start with dot";
620
621 lcdom = strlen(c->dom);
622 if(memchr(c->dom+1, '.', lcdom-1-1) == nil)
623 return "cookie domain doesn't have embedded dots";
624
625 if(!isdomainmatch(dom, c->dom))
626 return "request host does not match cookie domain";
627
628 ldom = strlen(dom);
629 if(strcmp(ipattr(dom), "dom")==0 && lcdom > ldom &&
630 memchr(dom, '.', lcdom - ldom) != nil)
631 return "request host contains dots before cookie domain";
632
633 return 0;
634 }
635
636 /*
637 * Sunday, 25-Jan-2002 12:24:36 GMT
638 * Sunday, 25 Jan 2002 12:24:36 GMT
639 * Sun, 25 Jan 02 12:24:36 GMT
640 */
641 static int
isleap(int year)642 isleap(int year)
643 {
644 return year%4==0 && (year%100!=0 || year%400==0);
645 }
646
647 static uint
strtotime(char * s)648 strtotime(char *s)
649 {
650 char *os;
651 int i;
652 Tm tm;
653
654 static int mday[2][12] = {
655 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
656 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
657 };
658 static char *wday[] = {
659 "Sunday", "Monday", "Tuesday", "Wednesday",
660 "Thursday", "Friday", "Saturday",
661 };
662 static char *mon[] = {
663 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
664 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
665 };
666
667 os = s;
668 /* Sunday, */
669 for(i=0; i<nelem(wday); i++){
670 if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){
671 s += strlen(wday[i]);
672 break;
673 }
674 if(cistrncmp(s, wday[i], 3) == 0){
675 s += 3;
676 break;
677 }
678 }
679 if(i==nelem(wday)){
680 if(cookiedebug)
681 fprint(2, "bad wday (%s)\n", os);
682 return -1;
683 }
684 if(*s++ != ',' || *s++ != ' '){
685 if(cookiedebug)
686 fprint(2, "bad wday separator (%s)\n", os);
687 return -1;
688 }
689
690 /* 25- */
691 if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){
692 if(cookiedebug)
693 fprint(2, "bad day of month (%s)\n", os);
694 return -1;
695 }
696 tm.mday = strtol(s, 0, 10);
697 s += 3;
698
699 /* Jan- */
700 for(i=0; i<nelem(mon); i++)
701 if(cistrncmp(s, mon[i], 3) == 0){
702 tm.mon = i;
703 s += 3;
704 break;
705 }
706 if(i==nelem(mon)){
707 if(cookiedebug)
708 fprint(2, "bad month (%s)\n", os);
709 return -1;
710 }
711 if(s[0] != '-' && s[0] != ' '){
712 if(cookiedebug)
713 fprint(2, "bad month separator (%s)\n", os);
714 return -1;
715 }
716 s++;
717
718 /* 2002 */
719 if(!isdigit(s[0]) || !isdigit(s[1])){
720 if(cookiedebug)
721 fprint(2, "bad year (%s)\n", os);
722 return -1;
723 }
724 tm.year = strtol(s, 0, 10);
725 s += 2;
726 if(isdigit(s[0]) && isdigit(s[1]))
727 s += 2;
728 else{
729 if(tm.year <= 68)
730 tm.year += 2000;
731 else
732 tm.year += 1900;
733 }
734 if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){
735 if(cookiedebug)
736 fprint(2, "invalid day of month (%s)\n", os);
737 return -1;
738 }
739 tm.year -= 1900;
740 if(*s++ != ' '){
741 if(cookiedebug)
742 fprint(2, "bad year separator (%s)\n", os);
743 return -1;
744 }
745
746 if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':'
747 || !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':'
748 || !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){
749 if(cookiedebug)
750 fprint(2, "bad time (%s)\n", os);
751 return -1;
752 }
753
754 tm.hour = atoi(s);
755 tm.min = atoi(s+3);
756 tm.sec = atoi(s+6);
757 if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){
758 if(cookiedebug)
759 fprint(2, "invalid time (%s)\n", os);
760 return -1;
761 }
762 s += 9;
763
764 if(cistrcmp(s, "GMT") != 0){
765 if(cookiedebug)
766 fprint(2, "time zone not GMT (%s)\n", os);
767 return -1;
768 }
769 strcpy(tm.zone, "GMT");
770 tm.yday = 0;
771 return tm2sec(&tm);
772 }
773
774 /*
775 * skip linear whitespace. we're a bit more lenient than RFC2616 2.2.
776 */
777 static char*
skipspace(char * s)778 skipspace(char *s)
779 {
780 while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t')
781 s++;
782 return s;
783 }
784
785 /*
786 * Try to identify old netscape headers.
787 * The old headers:
788 * - didn't allow spaces around the '='
789 * - used an 'Expires' attribute
790 * - had no 'Version' attribute
791 * - had no quotes
792 * - allowed whitespace in values
793 * - apparently separated attr/value pairs with ';' exclusively
794 */
795 static int
isnetscape(char * hdr)796 isnetscape(char *hdr)
797 {
798 char *s;
799
800 for(s=hdr; (s=strchr(s, '=')) != nil; s++){
801 if(isspace(s[1]) || (s > hdr && isspace(s[-1])))
802 return 0;
803 if(s[1]=='"')
804 return 0;
805 }
806 if(cistrstr(hdr, "version="))
807 return 0;
808 return 1;
809 }
810
811 /*
812 * Parse HTTP response headers, adding cookies to jar.
813 * Overwrites the headers. May overwrite path.
814 */
815 static char* parsecookie(Cookie*, char*, char**, int, char*, char*);
816 static int
parsehttp(Jar * jar,char * hdr,char * dom,char * path)817 parsehttp(Jar *jar, char *hdr, char *dom, char *path)
818 {
819 static char setcookie[] = "Set-Cookie:";
820 char *e, *p, *nextp;
821 Cookie c;
822 int isns, n;
823
824 isns = isnetscape(hdr);
825 n = 0;
826 for(p=hdr; p; p=nextp){
827 p = skipspace(p);
828 if(*p == '\0')
829 break;
830 nextp = strchr(p, '\n');
831 if(nextp != nil)
832 *nextp++ = '\0';
833 if(cistrncmp(p, setcookie, strlen(setcookie)) != 0)
834 continue;
835 if(cookiedebug)
836 fprint(2, "%s\n", p);
837 p = skipspace(p+strlen(setcookie));
838 for(; *p; p=skipspace(p)){
839 if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){
840 if(cookiedebug)
841 fprint(2, "parse cookie: %s\n", e);
842 break;
843 }
844 if((e = isbadcookie(&c, dom, path)) != nil){
845 if(cookiedebug)
846 fprint(2, "reject cookie; %s\n", e);
847 continue;
848 }
849 addcookie(jar, &c);
850 n++;
851 }
852 }
853 return n;
854 }
855
856 static char*
skipquoted(char * s)857 skipquoted(char *s)
858 {
859 /*
860 * Sec 2.2 of RFC2616 defines a "quoted-string" as:
861 *
862 * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
863 * qdtext = <any TEXT except <">>
864 * quoted-pair = "\" CHAR
865 *
866 * TEXT is any octet except CTLs, but including LWS;
867 * LWS is [CR LF] 1*(SP | HT);
868 * CHARs are ASCII octets 0-127; (NOTE: we reject 0's)
869 * CTLs are octets 0-31 and 127;
870 */
871 if(*s != '"')
872 return s;
873
874 for(s++; 32 <= *s && *s < 127 && *s != '"'; s++)
875 if(*s == '\\' && *(s+1) != '\0')
876 s++;
877 return s;
878 }
879
880 static char*
skiptoken(char * s)881 skiptoken(char *s)
882 {
883 /*
884 * Sec 2.2 of RFC2616 defines a "token" as
885 * 1*<any CHAR except CTLs or separators>;
886 * CHARs are ASCII octets 0-127;
887 * CTLs are octets 0-31 and 127;
888 * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9)
889 */
890 while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil)
891 s++;
892
893 return s;
894 }
895
896 static char*
skipvalue(char * s,int isns)897 skipvalue(char *s, int isns)
898 {
899 char *t;
900
901 /*
902 * An RFC2109 value is an HTTP token or an HTTP quoted string.
903 * Netscape servers ignore the spec and rely on semicolons, apparently.
904 */
905 if(isns){
906 if((t = strchr(s, ';')) == nil)
907 t = s+strlen(s);
908 return t;
909 }
910 if(*s == '"')
911 return skipquoted(s);
912 return skiptoken(s);
913 }
914
915 /*
916 * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT;
917 * path=/; domain=.nytimes.com
918 */
919 static char*
parsecookie(Cookie * c,char * p,char ** e,int isns,char * dom,char * path)920 parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path)
921 {
922 int i, done;
923 char *t, *u, *attr, *val;
924
925 c->expire = ~0;
926 memset(c, 0, sizeof *c);
927
928 /* NAME=VALUE */
929 t = skiptoken(p);
930 c->name = p;
931 p = skipspace(t);
932 if(*p != '='){
933 Badname:
934 return "malformed cookie: no NAME=VALUE";
935 }
936 *t = '\0';
937 p = skipspace(p+1);
938 t = skipvalue(p, isns);
939 if(*t)
940 *t++ = '\0';
941 c->value = p;
942 p = skipspace(t);
943 if(c->name[0]=='\0' || c->value[0]=='\0')
944 goto Badname;
945
946 done = 0;
947 for(; *p && !done; p=skipspace(p)){
948 attr = p;
949 t = skiptoken(p);
950 u = skipspace(t);
951 switch(*u){
952 case '\0':
953 *t = '\0';
954 val = p = u;
955 break;
956 case ';':
957 *t = '\0';
958 val = "";
959 p = u+1;
960 break;
961 case '=':
962 *t = '\0';
963 val = skipspace(u+1);
964 p = skipvalue(val, isns);
965 if(*p==',')
966 done = 1;
967 if(*p)
968 *p++ = '\0';
969 break;
970 case ',':
971 if(!isns){
972 val = "";
973 p = u;
974 *p++ = '\0';
975 done = 1;
976 break;
977 }
978 default:
979 if(cookiedebug)
980 fprint(2, "syntax: %s\n", p);
981 return "syntax error";
982 }
983 for(i=0; i<nelem(stab); i++)
984 if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0)
985 *(char**)((uintptr)c+stab[i].offset) = val;
986 if(cistrcmp(attr, "expires") == 0){
987 if(!isns)
988 return "non-netscape cookie has Expires tag";
989 if(!val[0])
990 return "bad expires tag";
991 c->expire = strtotime(val);
992 if(c->expire == ~0)
993 return "cannot parse netscape expires tag";
994 }
995 if(cistrcmp(attr, "max-age") == 0)
996 c->expire = time(0)+atoi(val);
997 if(cistrcmp(attr, "secure") == 0)
998 c->secure = 1;
999 }
1000
1001 if(c->dom)
1002 c->explicitdom = 1;
1003 else
1004 c->dom = dom;
1005 if(c->path)
1006 c->explicitpath = 1;
1007 else{
1008 c->path = path;
1009 if((t = strchr(c->path, '?')) != 0)
1010 *t = '\0';
1011 if((t = strrchr(c->path, '/')) != 0)
1012 *t = '\0';
1013 }
1014 c->netscapestyle = isns;
1015 *e = p;
1016
1017 return nil;
1018 }
1019
1020 Jar *jar;
1021
1022 typedef struct Aux Aux;
1023 struct Aux
1024 {
1025 char *dom;
1026 char *path;
1027 char *inhttp;
1028 char *outhttp;
1029 char *ctext;
1030 int rdoff;
1031 };
1032 enum
1033 {
1034 AuxBuf = 4096,
1035 MaxCtext = 16*1024*1024,
1036 };
1037
1038 void
cookieopen(Req * r)1039 cookieopen(Req *r)
1040 {
1041 char *s, *es;
1042 int i, sz;
1043 Aux *a;
1044
1045 syncjar(jar);
1046 a = emalloc9p(sizeof(Aux));
1047 r->fid->aux = a;
1048 if(r->ifcall.mode&OTRUNC){
1049 a->ctext = emalloc9p(1);
1050 a->ctext[0] = '\0';
1051 }else{
1052 sz = 256*jar->nc+1024; /* BUG should do better */
1053 a->ctext = emalloc9p(sz);
1054 a->ctext[0] = '\0';
1055 s = a->ctext;
1056 es = s+sz;
1057 for(i=0; i<jar->nc; i++)
1058 s = seprint(s, es, "%K\n", &jar->c[i]);
1059 }
1060 respond(r, nil);
1061 }
1062
1063 void
cookieread(Req * r)1064 cookieread(Req *r)
1065 {
1066 Aux *a;
1067
1068 a = r->fid->aux;
1069 readstr(r, a->ctext);
1070 respond(r, nil);
1071 }
1072
1073 void
cookiewrite(Req * r)1074 cookiewrite(Req *r)
1075 {
1076 Aux *a;
1077 int sz;
1078
1079 a = r->fid->aux;
1080 sz = r->ifcall.count+r->ifcall.offset;
1081 if(sz > strlen(a->ctext)){
1082 if(sz >= MaxCtext){
1083 respond(r, "cookie file too large");
1084 return;
1085 }
1086 a->ctext = erealloc9p(a->ctext, sz+1);
1087 a->ctext[sz] = '\0';
1088 }
1089 memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
1090 r->ofcall.count = r->ifcall.count;
1091 respond(r, nil);
1092 }
1093
1094 void
cookieclunk(Fid * fid)1095 cookieclunk(Fid *fid)
1096 {
1097 char *p, *nextp;
1098 Aux *a;
1099 int i;
1100
1101 a = fid->aux;
1102 if(a == nil)
1103 return;
1104 for(i=0; i<jar->nc; i++)
1105 jar->c[i].mark = 1;
1106 for(p=a->ctext; *p; p=nextp){
1107 if((nextp = strchr(p, '\n')) != nil)
1108 *nextp++ = '\0';
1109 else
1110 nextp = "";
1111 addtojar(jar, p, 0);
1112 }
1113 for(i=0; i<jar->nc; i++)
1114 if(jar->c[i].mark)
1115 delcookie(jar, &jar->c[i]);
1116 syncjar(jar);
1117 free(a->dom);
1118 free(a->path);
1119 free(a->inhttp);
1120 free(a->outhttp);
1121 free(a->ctext);
1122 free(a);
1123 }
1124
1125 void
closecookies(void)1126 closecookies(void)
1127 {
1128 closejar(jar);
1129 }
1130
1131 void
initcookies(char * file)1132 initcookies(char *file)
1133 {
1134 char *home;
1135
1136 fmtinstall('J', jarfmt);
1137 fmtinstall('K', cookiefmt);
1138
1139 if(file == nil){
1140 home = getenv("home");
1141 if(home == nil)
1142 sysfatal("no cookie file specified and no $home");
1143 file = emalloc9p(strlen(home)+30);
1144 strcpy(file, home);
1145 strcat(file, "/lib/webcookies");
1146 }
1147 jar = readjar(file);
1148 if(jar == nil)
1149 sysfatal("readjar: %r");
1150 }
1151
1152 void
httpsetcookie(char * hdr,char * dom,char * path)1153 httpsetcookie(char *hdr, char *dom, char *path)
1154 {
1155 if(path == nil)
1156 path = "/";
1157
1158 parsehttp(jar, hdr, dom, path);
1159 syncjar(jar);
1160 }
1161
1162 char*
httpcookies(char * dom,char * path,int issecure)1163 httpcookies(char *dom, char *path, int issecure)
1164 {
1165 char buf[1024];
1166 Jar *j;
1167
1168 syncjar(jar);
1169 j = cookiesearch(jar, dom, path, issecure);
1170 snprint(buf, sizeof buf, "%J", j);
1171 closejar(j);
1172 return estrdup(buf);
1173 }
1174