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 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 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 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 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 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 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 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 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 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 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* 322 newjar(void) 323 { 324 Jar *jar; 325 326 jar = emalloc9p(sizeof(Jar)); 327 return jar; 328 } 329 330 static int 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 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 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* 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 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 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 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* 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* 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 642 isleap(int year) 643 { 644 return year%4==0 && (year%100!=0 || year%400==0); 645 } 646 647 static uint 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* 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 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 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* 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* 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* 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* 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 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 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 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 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 1126 closecookies(void) 1127 { 1128 closejar(jar); 1129 } 1130 1131 void 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 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* 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