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