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**)((ulong)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*)((ulong)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**)((ulong)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**)((ulong)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**)((ulong)&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*)((ulong)&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 if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0) 543 return 1; 544 } 545 546 return 0; 547 } 548 549 /* 550 * RFC2109 4.3.4: 551 * - domain must match 552 * - path in cookie must be a prefix of request path 553 * - cookie must not have expired 554 */ 555 static int 556 iscookiematch(Cookie *c, char *dom, char *path, uint now) 557 { 558 return isdomainmatch(dom, c->dom) 559 && strncmp(c->path, path, strlen(c->path))==0 560 && c->expire >= now; 561 } 562 563 /* 564 * Produce a subjar of matching cookies. 565 * Secure cookies are only included if secure is set. 566 */ 567 static Jar* 568 cookiesearch(Jar *jar, char *dom, char *path, int issecure) 569 { 570 int i; 571 Jar *j; 572 uint now; 573 574 if(cookiedebug) 575 fprint(2, "cookiesearch %s %s %d\n", dom, path, issecure); 576 now = time(0); 577 j = newjar(); 578 for(i=0; i<jar->nc; i++){ 579 if(cookiedebug) 580 fprint(2, "\ttry %s %s %d %s\n", jar->c[i].dom, jar->c[i].path, jar->c[i].secure, jar->c[i].name); 581 if((issecure || !jar->c[i].secure) && iscookiematch(&jar->c[i], dom, path, now)){ 582 if(cookiedebug) 583 fprint(2, "\tmatched\n"); 584 addcookie(j, &jar->c[i]); 585 } 586 } 587 if(j->nc == 0){ 588 closejar(j); 589 werrstr("no cookies found"); 590 return nil; 591 } 592 qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(const void*, const void*))cookiecmp); 593 return j; 594 } 595 596 /* 597 * RFC2109 4.3.2 security checks 598 */ 599 static char* 600 isbadcookie(Cookie *c, char *dom, char *path) 601 { 602 if(strncmp(c->path, path, strlen(c->path)) != 0) 603 return "cookie path is not a prefix of the request path"; 604 605 if(c->explicitdom && c->dom[0] != '.') 606 return "cookie domain doesn't start with dot"; 607 608 if(memchr(c->dom+1, '.', strlen(c->dom)-1-1) == nil) 609 return "cookie domain doesn't have embedded dots"; 610 611 if(!isdomainmatch(dom, c->dom)) 612 return "request host does not match cookie domain"; 613 614 if(strcmp(ipattr(dom), "dom")==0 615 && memchr(dom, '.', strlen(dom)-strlen(c->dom)) != nil) 616 return "request host contains dots before cookie domain"; 617 618 return 0; 619 } 620 621 /* 622 * Sunday, 25-Jan-2002 12:24:36 GMT 623 * Sunday, 25 Jan 2002 12:24:36 GMT 624 * Sun, 25 Jan 02 12:24:36 GMT 625 */ 626 static int 627 isleap(int year) 628 { 629 return year%4==0 && (year%100!=0 || year%400==0); 630 } 631 632 static uint 633 strtotime(char *s) 634 { 635 char *os; 636 int i; 637 Tm tm; 638 639 static int mday[2][12] = { 640 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 641 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 642 }; 643 static char *wday[] = { 644 "Sunday", "Monday", "Tuesday", "Wednesday", 645 "Thursday", "Friday", "Saturday", 646 }; 647 static char *mon[] = { 648 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 649 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 650 }; 651 652 os = s; 653 /* Sunday, */ 654 for(i=0; i<nelem(wday); i++){ 655 if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){ 656 s += strlen(wday[i]); 657 break; 658 } 659 if(cistrncmp(s, wday[i], 3) == 0){ 660 s += 3; 661 break; 662 } 663 } 664 if(i==nelem(wday)){ 665 if(cookiedebug) 666 fprint(2, "bad wday (%s)\n", os); 667 return -1; 668 } 669 if(*s++ != ',' || *s++ != ' '){ 670 if(cookiedebug) 671 fprint(2, "bad wday separator (%s)\n", os); 672 return -1; 673 } 674 675 /* 25- */ 676 if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){ 677 if(cookiedebug) 678 fprint(2, "bad day of month (%s)\n", os); 679 return -1; 680 } 681 tm.mday = strtol(s, 0, 10); 682 s += 3; 683 684 /* Jan- */ 685 for(i=0; i<nelem(mon); i++) 686 if(cistrncmp(s, mon[i], 3) == 0){ 687 tm.mon = i; 688 s += 3; 689 break; 690 } 691 if(i==nelem(mon)){ 692 if(cookiedebug) 693 fprint(2, "bad month (%s)\n", os); 694 return -1; 695 } 696 if(s[0] != '-' && s[0] != ' '){ 697 if(cookiedebug) 698 fprint(2, "bad month separator (%s)\n", os); 699 return -1; 700 } 701 s++; 702 703 /* 2002 */ 704 if(!isdigit(s[0]) || !isdigit(s[1])){ 705 if(cookiedebug) 706 fprint(2, "bad year (%s)\n", os); 707 return -1; 708 } 709 tm.year = strtol(s, 0, 10); 710 s += 2; 711 if(isdigit(s[0]) && isdigit(s[1])) 712 s += 2; 713 else{ 714 if(tm.year <= 68) 715 tm.year += 2000; 716 else 717 tm.year += 1900; 718 } 719 if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){ 720 if(cookiedebug) 721 fprint(2, "invalid day of month (%s)\n", os); 722 return -1; 723 } 724 tm.year -= 1900; 725 if(*s++ != ' '){ 726 if(cookiedebug) 727 fprint(2, "bad year separator (%s)\n", os); 728 return -1; 729 } 730 731 if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':' 732 || !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':' 733 || !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){ 734 if(cookiedebug) 735 fprint(2, "bad time (%s)\n", os); 736 return -1; 737 } 738 739 tm.hour = atoi(s); 740 tm.min = atoi(s+3); 741 tm.sec = atoi(s+6); 742 if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){ 743 if(cookiedebug) 744 fprint(2, "invalid time (%s)\n", os); 745 return -1; 746 } 747 s += 9; 748 749 if(cistrcmp(s, "GMT") != 0){ 750 if(cookiedebug) 751 fprint(2, "time zone not GMT (%s)\n", os); 752 return -1; 753 } 754 strcpy(tm.zone, "GMT"); 755 tm.yday = 0; 756 return tm2sec(&tm); 757 } 758 759 /* 760 * skip linear whitespace. we're a bit more lenient than RFC2616 2.2. 761 */ 762 static char* 763 skipspace(char *s) 764 { 765 while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t') 766 s++; 767 return s; 768 } 769 770 /* 771 * Try to identify old netscape headers. 772 * The old headers: 773 * - didn't allow spaces around the '=' 774 * - used an 'Expires' attribute 775 * - had no 'Version' attribute 776 * - had no quotes 777 * - allowed whitespace in values 778 * - apparently separated attr/value pairs with ';' exclusively 779 */ 780 static int 781 isnetscape(char *hdr) 782 { 783 char *s; 784 785 for(s=hdr; (s=strchr(s, '=')) != nil; s++){ 786 if(isspace(s[1]) || (s > hdr && isspace(s[-1]))) 787 return 0; 788 if(s[1]=='"') 789 return 0; 790 } 791 if(cistrstr(hdr, "version=")) 792 return 0; 793 return 1; 794 } 795 796 /* 797 * Parse HTTP response headers, adding cookies to jar. 798 * Overwrites the headers. May overwrite path. 799 */ 800 static char* parsecookie(Cookie*, char*, char**, int, char*, char*); 801 static int 802 parsehttp(Jar *jar, char *hdr, char *dom, char *path) 803 { 804 static char setcookie[] = "Set-Cookie:"; 805 char *e, *p, *nextp; 806 Cookie c; 807 int isns, n; 808 809 isns = isnetscape(hdr); 810 n = 0; 811 for(p=hdr; p; p=nextp){ 812 p = skipspace(p); 813 if(*p == '\0') 814 break; 815 nextp = strchr(p, '\n'); 816 if(nextp != nil) 817 *nextp++ = '\0'; 818 if(cistrncmp(p, setcookie, strlen(setcookie)) != 0) 819 continue; 820 if(cookiedebug) 821 fprint(2, "%s\n", p); 822 p = skipspace(p+strlen(setcookie)); 823 for(; *p; p=skipspace(p)){ 824 if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){ 825 if(cookiedebug) 826 fprint(2, "parse cookie: %s\n", e); 827 break; 828 } 829 if((e = isbadcookie(&c, dom, path)) != nil){ 830 if(cookiedebug) 831 fprint(2, "reject cookie; %s\n", e); 832 continue; 833 } 834 addcookie(jar, &c); 835 n++; 836 } 837 } 838 return n; 839 } 840 841 static char* 842 skipquoted(char *s) 843 { 844 /* 845 * Sec 2.2 of RFC2616 defines a "quoted-string" as: 846 * 847 * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) 848 * qdtext = <any TEXT except <">> 849 * quoted-pair = "\" CHAR 850 * 851 * TEXT is any octet except CTLs, but including LWS; 852 * LWS is [CR LF] 1*(SP | HT); 853 * CHARs are ASCII octets 0-127; (NOTE: we reject 0's) 854 * CTLs are octets 0-31 and 127; 855 */ 856 if(*s != '"') 857 return s; 858 859 for(s++; 32 <= *s && *s < 127 && *s != '"'; s++) 860 if(*s == '\\' && *(s+1) != '\0') 861 s++; 862 return s; 863 } 864 865 static char* 866 skiptoken(char *s) 867 { 868 /* 869 * Sec 2.2 of RFC2616 defines a "token" as 870 * 1*<any CHAR except CTLs or separators>; 871 * CHARs are ASCII octets 0-127; 872 * CTLs are octets 0-31 and 127; 873 * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9) 874 */ 875 while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil) 876 s++; 877 878 return s; 879 } 880 881 static char* 882 skipvalue(char *s, int isns) 883 { 884 char *t; 885 886 /* 887 * An RFC2109 value is an HTTP token or an HTTP quoted string. 888 * Netscape servers ignore the spec and rely on semicolons, apparently. 889 */ 890 if(isns){ 891 if((t = strchr(s, ';')) == nil) 892 t = s+strlen(s); 893 return t; 894 } 895 if(*s == '"') 896 return skipquoted(s); 897 return skiptoken(s); 898 } 899 900 /* 901 * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT; 902 * path=/; domain=.nytimes.com 903 */ 904 static char* 905 parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path) 906 { 907 int i, done; 908 char *t, *u, *attr, *val; 909 910 c->expire = ~0; 911 memset(c, 0, sizeof *c); 912 913 /* NAME=VALUE */ 914 t = skiptoken(p); 915 c->name = p; 916 p = skipspace(t); 917 if(*p != '='){ 918 Badname: 919 return "malformed cookie: no NAME=VALUE"; 920 } 921 *t = '\0'; 922 p = skipspace(p+1); 923 t = skipvalue(p, isns); 924 if(*t) 925 *t++ = '\0'; 926 c->value = p; 927 p = skipspace(t); 928 if(c->name[0]=='\0' || c->value[0]=='\0') 929 goto Badname; 930 931 done = 0; 932 for(; *p && !done; p=skipspace(p)){ 933 attr = p; 934 t = skiptoken(p); 935 u = skipspace(t); 936 switch(*u){ 937 case '\0': 938 *t = '\0'; 939 val = p = u; 940 break; 941 case ';': 942 *t = '\0'; 943 val = ""; 944 p = u+1; 945 break; 946 case '=': 947 *t = '\0'; 948 val = skipspace(u+1); 949 p = skipvalue(val, isns); 950 if(*p==',') 951 done = 1; 952 if(*p) 953 *p++ = '\0'; 954 break; 955 case ',': 956 if(!isns){ 957 val = ""; 958 p = u; 959 *p++ = '\0'; 960 done = 1; 961 break; 962 } 963 default: 964 if(cookiedebug) 965 fprint(2, "syntax: %s\n", p); 966 return "syntax error"; 967 } 968 for(i=0; i<nelem(stab); i++) 969 if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0) 970 *(char**)((ulong)c+stab[i].offset) = val; 971 if(cistrcmp(attr, "expires") == 0){ 972 if(!isns) 973 return "non-netscape cookie has Expires tag"; 974 if(!val[0]) 975 return "bad expires tag"; 976 c->expire = strtotime(val); 977 if(c->expire == ~0) 978 return "cannot parse netscape expires tag"; 979 } 980 if(cistrcmp(attr, "max-age") == 0) 981 c->expire = time(0)+atoi(val); 982 if(cistrcmp(attr, "secure") == 0) 983 c->secure = 1; 984 } 985 986 if(c->dom) 987 c->explicitdom = 1; 988 else 989 c->dom = dom; 990 if(c->path) 991 c->explicitpath = 1; 992 else{ 993 c->path = path; 994 if((t = strchr(c->path, '?')) != 0) 995 *t = '\0'; 996 if((t = strrchr(c->path, '/')) != 0) 997 *t = '\0'; 998 } 999 c->netscapestyle = isns; 1000 *e = p; 1001 1002 return nil; 1003 } 1004 1005 Jar *jar; 1006 1007 typedef struct Aux Aux; 1008 struct Aux 1009 { 1010 char *dom; 1011 char *path; 1012 char *inhttp; 1013 char *outhttp; 1014 char *ctext; 1015 int rdoff; 1016 }; 1017 enum 1018 { 1019 AuxBuf = 4096, 1020 MaxCtext = 16*1024*1024, 1021 }; 1022 1023 void 1024 cookieopen(Req *r) 1025 { 1026 char *s, *es; 1027 int i, sz; 1028 Aux *a; 1029 1030 syncjar(jar); 1031 a = emalloc9p(sizeof(Aux)); 1032 r->fid->aux = a; 1033 if(r->ifcall.mode&OTRUNC){ 1034 a->ctext = emalloc9p(1); 1035 a->ctext[0] = '\0'; 1036 }else{ 1037 sz = 256*jar->nc+1024; /* BUG should do better */ 1038 a->ctext = emalloc9p(sz); 1039 a->ctext[0] = '\0'; 1040 s = a->ctext; 1041 es = s+sz; 1042 for(i=0; i<jar->nc; i++) 1043 s = seprint(s, es, "%K\n", &jar->c[i]); 1044 } 1045 respond(r, nil); 1046 } 1047 1048 void 1049 cookieread(Req *r) 1050 { 1051 Aux *a; 1052 1053 a = r->fid->aux; 1054 readstr(r, a->ctext); 1055 respond(r, nil); 1056 } 1057 1058 void 1059 cookiewrite(Req *r) 1060 { 1061 Aux *a; 1062 int sz; 1063 1064 a = r->fid->aux; 1065 sz = r->ifcall.count+r->ifcall.offset; 1066 if(sz > strlen(a->ctext)){ 1067 if(sz >= MaxCtext){ 1068 respond(r, "cookie file too large"); 1069 return; 1070 } 1071 a->ctext = erealloc9p(a->ctext, sz+1); 1072 a->ctext[sz] = '\0'; 1073 } 1074 memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count); 1075 r->ofcall.count = r->ifcall.count; 1076 respond(r, nil); 1077 } 1078 1079 void 1080 cookieclunk(Fid *fid) 1081 { 1082 char *p, *nextp; 1083 Aux *a; 1084 int i; 1085 1086 a = fid->aux; 1087 if(a == nil) 1088 return; 1089 for(i=0; i<jar->nc; i++) 1090 jar->c[i].mark = 1; 1091 for(p=a->ctext; *p; p=nextp){ 1092 if((nextp = strchr(p, '\n')) != nil) 1093 *nextp++ = '\0'; 1094 else 1095 nextp = ""; 1096 addtojar(jar, p, 0); 1097 } 1098 for(i=0; i<jar->nc; i++) 1099 if(jar->c[i].mark) 1100 delcookie(jar, &jar->c[i]); 1101 syncjar(jar); 1102 free(a->dom); 1103 free(a->path); 1104 free(a->inhttp); 1105 free(a->outhttp); 1106 free(a->ctext); 1107 free(a); 1108 } 1109 1110 void 1111 closecookies(void) 1112 { 1113 closejar(jar); 1114 } 1115 1116 void 1117 initcookies(char *file) 1118 { 1119 char *home; 1120 1121 fmtinstall('J', jarfmt); 1122 fmtinstall('K', cookiefmt); 1123 1124 if(file == nil){ 1125 home = getenv("home"); 1126 if(home == nil) 1127 sysfatal("no cookie file specified and no $home"); 1128 file = emalloc9p(strlen(home)+30); 1129 strcpy(file, home); 1130 strcat(file, "/lib/webcookies"); 1131 } 1132 jar = readjar(file); 1133 if(jar == nil) 1134 sysfatal("readjar: %r"); 1135 } 1136 1137 void 1138 httpsetcookie(char *hdr, char *dom, char *path) 1139 { 1140 if(path == nil) 1141 path = "/"; 1142 1143 parsehttp(jar, hdr, dom, path); 1144 syncjar(jar); 1145 } 1146 1147 char* 1148 httpcookies(char *dom, char *path, int issecure) 1149 { 1150 char buf[1024]; 1151 Jar *j; 1152 1153 syncjar(jar); 1154 j = cookiesearch(jar, dom, path, issecure); 1155 snprint(buf, sizeof buf, "%J", j); 1156 closejar(j); 1157 return estrdup(buf); 1158 } 1159