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