1/* 2 * Copyright 2010 Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Christophe Huriaux, c.huriaux@gmail.com 7 */ 8 9 10#include <cstdlib> 11#include <ctime> 12#include <new> 13 14#include <HttpTime.h> 15#include <NetworkCookie.h> 16 17#include <cstdio> 18#define PRINT(x) printf x; 19 20using BPrivate::BHttpTime; 21 22static const char* kArchivedCookieComment = "be:cookie.comment"; 23static const char* kArchivedCookieCommentUrl = "be:cookie.commenturl"; 24static const char* kArchivedCookieDiscard = "be:cookie.discard"; 25static const char* kArchivedCookieDomain = "be:cookie.domain"; 26static const char* kArchivedCookieExpirationDate = "be:cookie.expiredate"; 27static const char* kArchivedCookiePath = "be:cookie.path"; 28static const char* kArchivedCookieSecure = "be:cookie.secure"; 29static const char* kArchivedCookieVersion = "be:cookie.version"; 30static const char* kArchivedCookieName = "be:cookie.name"; 31static const char* kArchivedCookieValue = "be:cookie.value"; 32 33 34BNetworkCookie::BNetworkCookie(const char* name, const char* value) 35 : 36 fDiscard(false), 37 fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), 38 fVersion(0), 39 fName(name), 40 fValue(value), 41 fSessionCookie(true) 42{ 43 _Reset(); 44} 45 46 47BNetworkCookie::BNetworkCookie(const BNetworkCookie& other) 48 : 49 BArchivable(), 50 fDiscard(false), 51 fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), 52 fVersion(0), 53 fSessionCookie(true) 54{ 55 _Reset(); 56 *this = other; 57} 58 59 60BNetworkCookie::BNetworkCookie(const BString& cookieString) 61 : 62 fDiscard(false), 63 fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), 64 fVersion(0), 65 fSessionCookie(true) 66{ 67 _Reset(); 68 ParseCookieString(cookieString); 69} 70 71 72BNetworkCookie::BNetworkCookie(const BString& cookieString, 73 const BUrl& url) 74 : 75 fDiscard(false), 76 fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), 77 fVersion(0), 78 fSessionCookie(true) 79{ 80 _Reset(); 81 ParseCookieStringFromUrl(cookieString, url); 82} 83 84 85BNetworkCookie::BNetworkCookie(BMessage* archive) 86 : 87 fDiscard(false), 88 fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), 89 fVersion(0), 90 fSessionCookie(true) 91{ 92 _Reset(); 93 94 archive->FindString(kArchivedCookieName, &fName); 95 archive->FindString(kArchivedCookieValue, &fValue); 96 97 archive->FindString(kArchivedCookieComment, &fComment); 98 archive->FindString(kArchivedCookieCommentUrl, &fCommentUrl); 99 archive->FindString(kArchivedCookieDomain, &fDomain); 100 archive->FindString(kArchivedCookiePath, &fPath); 101 archive->FindBool(kArchivedCookieSecure, &fSecure); 102 103 if (archive->FindBool(kArchivedCookieDiscard, &fDiscard) == B_OK) 104 fHasDiscard = true; 105 106 if (archive->FindInt8(kArchivedCookieVersion, &fVersion) == B_OK) 107 fHasVersion = true; 108 109 int32 expiration; 110 if (archive->FindInt32(kArchivedCookieExpirationDate, &expiration) 111 == B_OK) { 112 SetExpirationDate((time_t)expiration); 113 } 114} 115 116 117BNetworkCookie::BNetworkCookie() 118 : 119 fDiscard(false), 120 fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)), 121 fPath("/"), 122 fVersion(0), 123 fSessionCookie(true) 124{ 125 _Reset(); 126} 127 128 129BNetworkCookie::~BNetworkCookie() 130{ 131} 132 133 134// #pragma mark String to cookie fields 135 136 137BNetworkCookie& 138BNetworkCookie::ParseCookieStringFromUrl(const BString& string, 139 const BUrl& url) 140{ 141 BString cookieString(string); 142 int16 index = 0; 143 144 _Reset(); 145 146 // Default values from url 147 SetDomain(url.Host()); 148 SetPath(url.Path()); 149 150 _ExtractNameValuePair(cookieString, &index); 151 152 while (index < cookieString.Length()) 153 _ExtractNameValuePair(cookieString, &index, true); 154 155 return *this; 156} 157 158 159BNetworkCookie& 160BNetworkCookie::ParseCookieString(const BString& string) 161{ 162 BUrl url; 163 ParseCookieStringFromUrl(string, url); 164 return *this; 165} 166 167 168// #pragma mark Cookie fields modification 169 170 171BNetworkCookie& 172BNetworkCookie::SetComment(const BString& comment) 173{ 174 fComment = comment; 175 fRawFullCookieValid = false; 176 return *this; 177} 178 179 180BNetworkCookie& 181BNetworkCookie::SetCommentUrl(const BString& commentUrl) 182{ 183 fCommentUrl = commentUrl; 184 fRawFullCookieValid = false; 185 return *this; 186} 187 188 189BNetworkCookie& 190BNetworkCookie::SetDiscard(bool discard) 191{ 192 fDiscard = discard; 193 fHasDiscard = true; 194 fRawFullCookieValid = false; 195 return *this; 196} 197 198 199BNetworkCookie& 200BNetworkCookie::SetDomain(const BString& domain) 201{ 202 fDomain = domain; 203 204 // We always use pre-dotted domains for tail matching 205 if (fDomain.ByteAt(0) != '.') 206 fDomain.Prepend("."); 207 208 fRawFullCookieValid = false; 209 return *this; 210} 211 212 213BNetworkCookie& 214BNetworkCookie::SetMaxAge(int32 maxAge) 215{ 216 BDateTime expiration = BDateTime::CurrentDateTime(B_GMT_TIME); 217 expiration.Time().AddSeconds(maxAge); 218 return SetExpirationDate(expiration); 219} 220 221 222BNetworkCookie& 223BNetworkCookie::SetExpirationDate(time_t expireDate) 224{ 225 BDateTime expiration; 226 expiration.SetTime_t(expireDate); 227 return SetExpirationDate(expiration); 228} 229 230 231BNetworkCookie& 232BNetworkCookie::SetExpirationDate(BDateTime& expireDate) 233{ 234 if (expireDate.Time_t() <= 0) { 235 fExpiration.SetTime_t(0); 236 fSessionCookie = true; 237 fExpirationStringValid = false; 238 fRawFullCookieValid = false; 239 fHasExpirationDate = false; 240 } else { 241 fExpiration = expireDate; 242 fSessionCookie = false; 243 fExpirationStringValid = false; 244 fRawFullCookieValid = false; 245 fHasExpirationDate = true; 246 } 247 248 return *this; 249} 250 251 252BNetworkCookie& 253BNetworkCookie::SetPath(const BString& path) 254{ 255 fPath = path; 256 fRawFullCookieValid = false; 257 return *this; 258} 259 260 261BNetworkCookie& 262BNetworkCookie::SetSecure(bool secure) 263{ 264 fSecure = secure; 265 fRawFullCookieValid = false; 266 return *this; 267} 268 269 270BNetworkCookie& 271BNetworkCookie::SetVersion(int8 version) 272{ 273 fVersion = version; 274 fHasVersion = true; 275 fRawCookieValid = false; 276 return *this; 277} 278 279 280BNetworkCookie& 281BNetworkCookie::SetName(const BString& name) 282{ 283 fName = name; 284 fRawFullCookieValid = false; 285 fRawCookieValid = false; 286 return *this; 287} 288 289 290BNetworkCookie& 291BNetworkCookie::SetValue(const BString& value) 292{ 293 fValue = value; 294 fRawFullCookieValid = false; 295 fRawCookieValid = false; 296 return *this; 297} 298 299 300// #pragma mark Cookie fields access 301 302 303const BString& 304BNetworkCookie::Comment() const 305{ 306 return fComment; 307} 308 309 310const BString& 311BNetworkCookie::CommentUrl() const 312{ 313 return fCommentUrl; 314} 315 316 317bool 318BNetworkCookie::Discard() const 319{ 320 return fDiscard; 321} 322 323 324const BString& 325BNetworkCookie::Domain() const 326{ 327 return fDomain; 328} 329 330 331int32 332BNetworkCookie::MaxAge() const 333{ 334 return fExpiration.Time_t() - BDateTime::CurrentDateTime(B_GMT_TIME).Time_t(); 335} 336 337 338time_t 339BNetworkCookie::ExpirationDate() const 340{ 341 return fExpiration.Time_t(); 342} 343 344 345const BString& 346BNetworkCookie::ExpirationString() const 347{ 348 BHttpTime date(ExpirationDate()); 349 350 if (!fExpirationStringValid) { 351 fExpirationString = date.ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE); 352 fExpirationStringValid = true; 353 } 354 355 return fExpirationString; 356} 357 358 359const BString& 360BNetworkCookie::Path() const 361{ 362 return fPath; 363} 364 365 366bool 367BNetworkCookie::Secure() const 368{ 369 return fSecure; 370} 371 372 373int8 374BNetworkCookie::Version() const 375{ 376 return fVersion; 377} 378 379 380const BString& 381BNetworkCookie::Name() const 382{ 383 return fName; 384} 385 386 387const BString& 388BNetworkCookie::Value() const 389{ 390 return fValue; 391} 392 393 394const BString& 395BNetworkCookie::RawCookie(bool full) const 396{ 397 if (full && !fRawFullCookieValid) { 398 fRawFullCookie.Truncate(0); 399 fRawFullCookieValid = true; 400 401 fRawFullCookie << fName << "=" << fValue; 402 403 if (HasCommentUrl()) 404 fRawFullCookie << "; Comment-Url=" << fCommentUrl; 405 if (HasComment()) 406 fRawFullCookie << "; Comment=" << fComment; 407 if (HasDiscard()) 408 fRawFullCookie << "; Discard=" << (fDiscard?"true":"false"); 409 if (HasDomain()) 410 fRawFullCookie << "; Domain=" << fDomain; 411 if (HasExpirationDate()) 412 fRawFullCookie << "; Max-Age=" << MaxAge(); 413// fRawFullCookie << "; Expires=" << ExpirationString(); 414 if (HasPath()) 415 fRawFullCookie << "; Path=" << fPath; 416 if (Secure() && fSecure) 417 fRawFullCookie << "; Secure=" << (fSecure?"true":"false"); 418 if (HasVersion()) 419 fRawFullCookie << ", Version=" << fVersion; 420 421 } else if (!full && !fRawCookieValid) { 422 fRawCookie.Truncate(0); 423 fRawCookieValid = true; 424 425 fRawCookie << fName << "=" << fValue; 426 } 427 428 return full?fRawFullCookie:fRawCookie; 429} 430 431 432// #pragma mark Cookie test 433 434 435bool 436BNetworkCookie::IsSessionCookie() const 437{ 438 return fSessionCookie; 439} 440 441 442bool 443BNetworkCookie::IsValid(bool strict) const 444{ 445 return HasName() && HasValue() && (!strict || HasVersion()); 446} 447 448 449bool 450BNetworkCookie::IsValidForUrl(const BUrl& url) const 451{ 452 BString urlHost = url.Host(); 453 BString urlPath = url.Path(); 454 455 return IsValidForDomain(urlHost) && IsValidForPath(urlPath); 456} 457 458 459bool 460BNetworkCookie::IsValidForDomain(const BString& domain) const 461{ 462 if (fDomain.Length() > domain.Length()) 463 return false; 464 465 return domain.FindLast(fDomain) == (domain.Length() - fDomain.Length()); 466} 467 468 469bool 470BNetworkCookie::IsValidForPath(const BString& path) const 471{ 472 if (fPath.Length() > path.Length()) 473 return false; 474 475 return path.FindFirst(fPath) == 0; 476} 477 478 479// #pragma mark Cookie fields existence tests 480 481 482bool 483BNetworkCookie::HasCommentUrl() const 484{ 485 return fCommentUrl.Length() > 0; 486} 487 488 489bool 490BNetworkCookie::HasComment() const 491{ 492 return fComment.Length() > 0; 493} 494 495 496bool 497BNetworkCookie::HasDiscard() const 498{ 499 return fHasDiscard; 500} 501 502 503bool 504BNetworkCookie::HasDomain() const 505{ 506 return fDomain.Length() > 0; 507} 508 509 510bool 511BNetworkCookie::HasPath() const 512{ 513 return fPath.Length() > 0; 514} 515 516 517bool 518BNetworkCookie::HasVersion() const 519{ 520 return fHasVersion; 521} 522 523 524bool 525BNetworkCookie::HasName() const 526{ 527 return fName.Length() > 0; 528} 529 530 531bool 532BNetworkCookie::HasValue() const 533{ 534 return fValue.Length() > 0; 535} 536 537 538bool 539BNetworkCookie::HasExpirationDate() const 540{ 541 return fHasExpirationDate; 542} 543 544 545// #pragma mark Cookie delete test 546 547 548bool 549BNetworkCookie::ShouldDeleteAtExit() const 550{ 551 return (HasDiscard() && Discard()) 552 || (!IsSessionCookie() && ShouldDeleteNow()) 553 || IsSessionCookie(); 554} 555 556 557bool 558BNetworkCookie::ShouldDeleteNow() const 559{ 560 if (!IsSessionCookie() && HasExpirationDate()) 561 return (BDateTime::CurrentDateTime(B_GMT_TIME) > fExpiration); 562 563 return false; 564} 565 566 567// #pragma mark BArchivable members 568 569 570status_t 571BNetworkCookie::Archive(BMessage* into, bool deep) const 572{ 573 status_t error = BArchivable::Archive(into, deep); 574 575 if (error != B_OK) 576 return error; 577 578 error = into->AddString(kArchivedCookieName, fName); 579 if (error != B_OK) 580 return error; 581 582 error = into->AddString(kArchivedCookieValue, fValue); 583 if (error != B_OK) 584 return error; 585 586 587 // We add optional fields only if they're defined 588 if (HasComment()) { 589 error = into->AddString(kArchivedCookieComment, fComment); 590 if (error != B_OK) 591 return error; 592 } 593 594 if (HasCommentUrl()) { 595 error = into->AddString(kArchivedCookieCommentUrl, fCommentUrl); 596 if (error != B_OK) 597 return error; 598 } 599 600 if (HasDiscard()) { 601 error = into->AddBool(kArchivedCookieDiscard, fDiscard); 602 if (error != B_OK) 603 return error; 604 } 605 606 if (HasDomain()) { 607 error = into->AddString(kArchivedCookieDomain, fDomain); 608 if (error != B_OK) 609 return error; 610 } 611 612 if (fHasExpirationDate) { 613 error = into->AddInt32(kArchivedCookieExpirationDate, 614 fExpiration.Time_t()); 615 if (error != B_OK) 616 return error; 617 } 618 619 if (HasPath()) { 620 error = into->AddString(kArchivedCookiePath, fPath); 621 if (error != B_OK) 622 return error; 623 } 624 625 if (Secure()) { 626 error = into->AddBool(kArchivedCookieSecure, fSecure); 627 if (error != B_OK) 628 return error; 629 } 630 631 if (HasVersion()) { 632 error = into->AddInt8(kArchivedCookieVersion, fVersion); 633 if (error != B_OK) 634 return error; 635 } 636 637 return B_OK; 638} 639 640 641/*static*/ BArchivable* 642BNetworkCookie::Instantiate(BMessage* archive) 643{ 644 if (archive->HasString(kArchivedCookieName) 645 && archive->HasString(kArchivedCookieValue)) 646 return new(std::nothrow) BNetworkCookie(archive); 647 648 return NULL; 649} 650 651 652// #pragma mark Overloaded operators 653 654 655BNetworkCookie& 656BNetworkCookie::operator=(const BNetworkCookie& other) 657{ 658 // Should we prefer to discard the cache ? 659 fRawCookie = other.fRawCookie; 660 fRawCookieValid = other.fRawCookieValid; 661 fRawFullCookie = other.fRawFullCookie; 662 fRawFullCookieValid = other.fRawFullCookieValid; 663 fExpirationString = other.fExpirationString; 664 fExpirationStringValid = other.fExpirationStringValid; 665 666 fComment = other.fComment; 667 fCommentUrl = other.fCommentUrl; 668 fDiscard = other.fDiscard; 669 fDomain = other.fDomain; 670 fExpiration = other.fExpiration; 671 fPath = other.fPath; 672 fSecure = other.fSecure; 673 fVersion = other.fVersion; 674 fName = other.fName; 675 fValue = other.fValue; 676 677 fHasDiscard = other.fHasDiscard; 678 fHasExpirationDate = other.fHasExpirationDate; 679 fSessionCookie = other.fSessionCookie; 680 fHasVersion = other.fHasVersion; 681 682 return *this; 683} 684 685 686BNetworkCookie& 687BNetworkCookie::operator=(const char* string) 688{ 689 return ParseCookieString(string); 690} 691 692 693bool 694BNetworkCookie::operator==(const BNetworkCookie& other) 695{ 696 // Equality : name and values equals 697 return fName == other.fName && fValue == other.fValue; 698} 699 700 701bool 702BNetworkCookie::operator!=(const BNetworkCookie& other) 703{ 704 return !(*this == other); 705} 706 707 708void 709BNetworkCookie::_Reset() 710{ 711 fComment.Truncate(0); 712 fCommentUrl.Truncate(0); 713 fDomain.Truncate(0); 714 fPath.Truncate(0); 715 fName.Truncate(0); 716 fValue.Truncate(0); 717 fDiscard = false; 718 fSecure = false; 719 fVersion = 0; 720 fExpiration = 0; 721 722 fHasDiscard = false; 723 fHasExpirationDate = false; 724 fSessionCookie = true; 725 fHasVersion = false; 726 727 fRawCookieValid = false; 728 fRawFullCookieValid = false; 729 fExpirationStringValid = false; 730} 731 732 733void 734BNetworkCookie::_ExtractNameValuePair(const BString& cookieString, 735 int16* index, bool parseField) 736{ 737 // Skip whitespaces 738 while (cookieString.ByteAt(*index) == ' ' 739 && *index < cookieString.Length()) 740 (*index)++; 741 742 if (*index >= cookieString.Length()) 743 return; 744 745 746 // Look for a name=value pair 747 int16 firstSemiColon = cookieString.FindFirst(";", *index); 748 int16 firstEqual = cookieString.FindFirst("=", *index); 749 750 BString name; 751 BString value; 752 753 if (firstSemiColon == -1) { 754 if (firstEqual != -1) { 755 cookieString.CopyInto(name, *index, firstEqual - *index); 756 cookieString.CopyInto(value, firstEqual + 1, 757 cookieString.Length() - firstEqual - 1); 758 } else 759 cookieString.CopyInto(value, *index, 760 cookieString.Length() - *index); 761 762 *index = cookieString.Length() + 1; 763 } else { 764 if (firstEqual != -1 && firstEqual < firstSemiColon) { 765 cookieString.CopyInto(name, *index, firstEqual - *index); 766 cookieString.CopyInto(value, firstEqual + 1, 767 firstSemiColon - firstEqual - 1); 768 } else 769 cookieString.CopyInto(value, *index, firstSemiColon - *index); 770 771 *index = firstSemiColon + 1; 772 } 773 774 // Cookie name/value pair 775 if (!parseField) { 776 SetName(name); 777 SetValue(value); 778 return; 779 } 780 781 name.ToLower(); 782 name.Trim(); 783 value.Trim(); 784 785 // Cookie comment 786 if (name == "comment") 787 SetComment(value); 788 // Cookie comment URL 789 else if (name == "comment-url") 790 SetCommentUrl(value); 791 // Cookie discard flag 792 else if (name == "discard") 793 SetDiscard(value.Length() == 0 || value.ToLower() == "true"); 794 // Cookie max-age 795 else if (name == "maxage") 796 SetMaxAge(atoi(value.String())); 797 // Cookie expiration date 798 else if (name == "expires") { 799 BHttpTime date(value); 800 SetExpirationDate(date.Parse()); 801 // Cookie valid domain 802 } else if (name == "domain") 803 SetDomain(value); 804 // Cookie valid path 805 else if (name == "path") 806 SetPath(value); 807 // Cookie secure flag 808 else if (name == "secure") 809 SetSecure(value.Length() == 0 || value.ToLower() == "true"); 810 // Cookie version 811 else if (name == "version") 812 SetVersion(atoi(value.String())); 813} 814