1 /*
2 * Copyright 2010-2014 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Adrien Destugues, pulkomandy@pulkomandy.tk
7 * Christophe Huriaux, c.huriaux@gmail.com
8 * Hamish Morrison, hamishm53@gmail.com
9 */
10
11
12 #include <new>
13
14 #include <errno.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <time.h>
18
19 #include <Debug.h>
20 #include <HttpTime.h>
21 #include <NetworkCookie.h>
22
23 using namespace BPrivate::Network;
24
25
26 static const char* kArchivedCookieName = "be:cookie.name";
27 static const char* kArchivedCookieValue = "be:cookie.value";
28 static const char* kArchivedCookieDomain = "be:cookie.domain";
29 static const char* kArchivedCookiePath = "be:cookie.path";
30 static const char* kArchivedCookieExpirationDate = "be:cookie.expirationdate";
31 static const char* kArchivedCookieSecure = "be:cookie.secure";
32 static const char* kArchivedCookieHttpOnly = "be:cookie.httponly";
33 static const char* kArchivedCookieHostOnly = "be:cookie.hostonly";
34
35
BNetworkCookie(const char * name,const char * value,const BUrl & url)36 BNetworkCookie::BNetworkCookie(const char* name, const char* value,
37 const BUrl& url)
38 {
39 _Reset();
40 fName = name;
41 fValue = value;
42
43 SetDomain(url.Host());
44
45 if (url.Protocol() == "file" && url.Host().Length() == 0) {
46 SetDomain("localhost");
47 // make sure cookies set from a file:// URL are stored somewhere.
48 }
49
50 SetPath(_DefaultPathForUrl(url));
51 }
52
53
BNetworkCookie(const BString & cookieString,const BUrl & url)54 BNetworkCookie::BNetworkCookie(const BString& cookieString, const BUrl& url)
55 {
56 _Reset();
57 fInitStatus = ParseCookieString(cookieString, url);
58 }
59
60
BNetworkCookie(BMessage * archive)61 BNetworkCookie::BNetworkCookie(BMessage* archive)
62 {
63 _Reset();
64
65 archive->FindString(kArchivedCookieName, &fName);
66 archive->FindString(kArchivedCookieValue, &fValue);
67
68 archive->FindString(kArchivedCookieDomain, &fDomain);
69 archive->FindString(kArchivedCookiePath, &fPath);
70 archive->FindBool(kArchivedCookieSecure, &fSecure);
71 archive->FindBool(kArchivedCookieHttpOnly, &fHttpOnly);
72 archive->FindBool(kArchivedCookieHostOnly, &fHostOnly);
73
74 // We store the expiration date as a string, which should not overflow.
75 // But we still parse the old archive format, where an int32 was used.
76 BString expirationString;
77 int32 expiration;
78 if (archive->FindString(kArchivedCookieExpirationDate, &expirationString)
79 == B_OK) {
80 BDateTime time = BHttpTime(expirationString).Parse();
81 SetExpirationDate(time);
82 } else if (archive->FindInt32(kArchivedCookieExpirationDate, &expiration)
83 == B_OK) {
84 SetExpirationDate((time_t)expiration);
85 }
86 }
87
88
BNetworkCookie()89 BNetworkCookie::BNetworkCookie()
90 {
91 _Reset();
92 }
93
94
~BNetworkCookie()95 BNetworkCookie::~BNetworkCookie()
96 {
97 }
98
99
100 // #pragma mark String to cookie fields
101
102
103 status_t
ParseCookieString(const BString & string,const BUrl & url)104 BNetworkCookie::ParseCookieString(const BString& string, const BUrl& url)
105 {
106 _Reset();
107
108 // Set default values (these can be overriden later on)
109 SetPath(_DefaultPathForUrl(url));
110 SetDomain(url.Host());
111 fHostOnly = true;
112 if (url.Protocol() == "file" && url.Host().Length() == 0) {
113 fDomain = "localhost";
114 // make sure cookies set from a file:// URL are stored somewhere.
115 // not going through SetDomain as it requires at least one '.'
116 // in the domain (to avoid setting cookies on TLDs).
117 }
118
119 BString name;
120 BString value;
121 int32 index = 0;
122
123 // Parse the name and value of the cookie
124 index = _ExtractNameValuePair(string, name, value, index);
125 if (index == -1 || value.Length() > 4096) {
126 // The set-cookie-string is not valid
127 return B_BAD_DATA;
128 }
129
130 SetName(name);
131 SetValue(value);
132
133 // Note on error handling: even if there are parse errors, we will continue
134 // and try to parse as much from the cookie as we can.
135 status_t result = B_OK;
136
137 // Parse the remaining cookie attributes.
138 while (index < string.Length()) {
139 ASSERT(string[index] == ';');
140 index++;
141
142 index = _ExtractAttributeValuePair(string, name, value, index);
143
144 if (name.ICompare("secure") == 0)
145 SetSecure(true);
146 else if (name.ICompare("httponly") == 0)
147 SetHttpOnly(true);
148
149 // The following attributes require a value.
150
151 if (name.ICompare("max-age") == 0) {
152 if (value.IsEmpty()) {
153 result = B_BAD_VALUE;
154 continue;
155 }
156 // Validate the max-age value.
157 char* end = NULL;
158 errno = 0;
159 long maxAge = strtol(value.String(), &end, 10);
160 if (*end == '\0')
161 SetMaxAge((int)maxAge);
162 else if (errno == ERANGE && maxAge == LONG_MAX)
163 SetMaxAge(INT_MAX);
164 else
165 SetMaxAge(-1); // cookie will expire immediately
166 } else if (name.ICompare("expires") == 0) {
167 if (value.IsEmpty()) {
168 // Will be a session cookie.
169 continue;
170 }
171 BDateTime parsed = BHttpTime(value).Parse();
172 SetExpirationDate(parsed);
173 } else if (name.ICompare("domain") == 0) {
174 if (value.IsEmpty()) {
175 result = B_BAD_VALUE;
176 continue;
177 }
178
179 status_t domainResult = SetDomain(value);
180 // Do not reset the result to B_OK if something else already failed
181 if (result == B_OK)
182 result = domainResult;
183 } else if (name.ICompare("path") == 0) {
184 if (value.IsEmpty()) {
185 result = B_BAD_VALUE;
186 continue;
187 }
188 status_t pathResult = SetPath(value);
189 if (result == B_OK)
190 result = pathResult;
191 }
192 }
193
194 if (!_CanBeSetFromUrl(url))
195 result = B_NOT_ALLOWED;
196
197 if (result != B_OK)
198 _Reset();
199
200 return result;
201 }
202
203
204 // #pragma mark Cookie fields modification
205
206
207 BNetworkCookie&
SetName(const BString & name)208 BNetworkCookie::SetName(const BString& name)
209 {
210 fName = name;
211 fRawFullCookieValid = false;
212 fRawCookieValid = false;
213 return *this;
214 }
215
216
217 BNetworkCookie&
SetValue(const BString & value)218 BNetworkCookie::SetValue(const BString& value)
219 {
220 fValue = value;
221 fRawFullCookieValid = false;
222 fRawCookieValid = false;
223 return *this;
224 }
225
226
227 status_t
SetPath(const BString & to)228 BNetworkCookie::SetPath(const BString& to)
229 {
230 fPath.Truncate(0);
231 fRawFullCookieValid = false;
232
233 // Limit the path to 4096 characters to not let the cookie jar grow huge.
234 if (to[0] != '/' || to.Length() > 4096)
235 return B_BAD_DATA;
236
237 // Check that there aren't any "." or ".." segments in the path.
238 if (to.EndsWith("/.") || to.EndsWith("/.."))
239 return B_BAD_DATA;
240 if (to.FindFirst("/../") >= 0 || to.FindFirst("/./") >= 0)
241 return B_BAD_DATA;
242
243 fPath = to;
244 return B_OK;
245 }
246
247
248 status_t
SetDomain(const BString & domain)249 BNetworkCookie::SetDomain(const BString& domain)
250 {
251 // TODO: canonicalize the domain
252 BString newDomain = domain;
253
254 // RFC 2109 (legacy) support: domain string may start with a dot,
255 // meant to indicate the cookie should also be used for subdomains.
256 // RFC 6265 makes all cookies work for subdomains, unless the domain is
257 // not specified at all (in this case it has to exactly match the Url of
258 // the page that set the cookie). In any case, we don't need to handle
259 // dot-cookies specifically anymore, so just remove the extra dot.
260 if (newDomain[0] == '.')
261 newDomain.Remove(0, 1);
262
263 // check we're not trying to set a cookie on a TLD or empty domain
264 if (newDomain.FindLast('.') <= 0)
265 return B_BAD_DATA;
266
267 fDomain = newDomain.ToLower();
268
269 fHostOnly = false;
270
271 fRawFullCookieValid = false;
272 return B_OK;
273 }
274
275
276 BNetworkCookie&
SetMaxAge(int32 maxAge)277 BNetworkCookie::SetMaxAge(int32 maxAge)
278 {
279 BDateTime expiration = BDateTime::CurrentDateTime(B_LOCAL_TIME);
280
281 // Compute the expiration date (watch out for overflows)
282 int64_t date = expiration.Time_t();
283 date += (int64_t)maxAge;
284 if (date > INT_MAX)
285 date = INT_MAX;
286
287 expiration.SetTime_t(date);
288
289 return SetExpirationDate(expiration);
290 }
291
292
293 BNetworkCookie&
SetExpirationDate(time_t expireDate)294 BNetworkCookie::SetExpirationDate(time_t expireDate)
295 {
296 BDateTime expiration;
297 expiration.SetTime_t(expireDate);
298 return SetExpirationDate(expiration);
299 }
300
301
302 BNetworkCookie&
SetExpirationDate(BDateTime & expireDate)303 BNetworkCookie::SetExpirationDate(BDateTime& expireDate)
304 {
305 if (!expireDate.IsValid()) {
306 fExpiration.SetTime_t(0);
307 fSessionCookie = true;
308 } else {
309 fExpiration = expireDate;
310 fSessionCookie = false;
311 }
312
313 fExpirationStringValid = false;
314 fRawFullCookieValid = false;
315
316 return *this;
317 }
318
319
320 BNetworkCookie&
SetSecure(bool secure)321 BNetworkCookie::SetSecure(bool secure)
322 {
323 fSecure = secure;
324 fRawFullCookieValid = false;
325 return *this;
326 }
327
328
329 BNetworkCookie&
SetHttpOnly(bool httpOnly)330 BNetworkCookie::SetHttpOnly(bool httpOnly)
331 {
332 fHttpOnly = httpOnly;
333 fRawFullCookieValid = false;
334 return *this;
335 }
336
337
338 // #pragma mark Cookie fields access
339
340
341 const BString&
Name() const342 BNetworkCookie::Name() const
343 {
344 return fName;
345 }
346
347
348 const BString&
Value() const349 BNetworkCookie::Value() const
350 {
351 return fValue;
352 }
353
354
355 const BString&
Domain() const356 BNetworkCookie::Domain() const
357 {
358 return fDomain;
359 }
360
361
362 const BString&
Path() const363 BNetworkCookie::Path() const
364 {
365 return fPath;
366 }
367
368
369 time_t
ExpirationDate() const370 BNetworkCookie::ExpirationDate() const
371 {
372 return fExpiration.Time_t();
373 }
374
375
376 const BString&
ExpirationString() const377 BNetworkCookie::ExpirationString() const
378 {
379 BHttpTime date(fExpiration);
380
381 if (!fExpirationStringValid) {
382 fExpirationString = date.ToString(B_HTTP_TIME_FORMAT_COOKIE);
383 fExpirationStringValid = true;
384 }
385
386 return fExpirationString;
387 }
388
389
390 bool
Secure() const391 BNetworkCookie::Secure() const
392 {
393 return fSecure;
394 }
395
396
397 bool
HttpOnly() const398 BNetworkCookie::HttpOnly() const
399 {
400 return fHttpOnly;
401 }
402
403
404 const BString&
RawCookie(bool full) const405 BNetworkCookie::RawCookie(bool full) const
406 {
407 if (!fRawCookieValid) {
408 fRawCookie.Truncate(0);
409 fRawCookieValid = true;
410
411 fRawCookie << fName << "=" << fValue;
412 }
413
414 if (!full)
415 return fRawCookie;
416
417 if (!fRawFullCookieValid) {
418 fRawFullCookie = fRawCookie;
419 fRawFullCookieValid = true;
420
421 if (HasDomain())
422 fRawFullCookie << "; Domain=" << fDomain;
423 if (HasExpirationDate())
424 fRawFullCookie << "; Expires=" << ExpirationString();
425 if (HasPath())
426 fRawFullCookie << "; Path=" << fPath;
427 if (Secure())
428 fRawFullCookie << "; Secure";
429 if (HttpOnly())
430 fRawFullCookie << "; HttpOnly";
431
432 }
433
434 return fRawFullCookie;
435 }
436
437
438 // #pragma mark Cookie test
439
440
441 bool
IsHostOnly() const442 BNetworkCookie::IsHostOnly() const
443 {
444 return fHostOnly;
445 }
446
447
448 bool
IsSessionCookie() const449 BNetworkCookie::IsSessionCookie() const
450 {
451 return fSessionCookie;
452 }
453
454
455 bool
IsValid() const456 BNetworkCookie::IsValid() const
457 {
458 return fInitStatus == B_OK && HasName() && HasDomain();
459 }
460
461
462 bool
IsValidForUrl(const BUrl & url) const463 BNetworkCookie::IsValidForUrl(const BUrl& url) const
464 {
465 if (Secure() && url.Protocol() != "https")
466 return false;
467
468 if (url.Protocol() == "file")
469 return Domain() == "localhost" && IsValidForPath(url.Path());
470
471 return IsValidForDomain(url.Host()) && IsValidForPath(url.Path());
472 }
473
474
475 bool
IsValidForDomain(const BString & domain) const476 BNetworkCookie::IsValidForDomain(const BString& domain) const
477 {
478 // TODO: canonicalize both domains
479 const BString& cookieDomain = Domain();
480
481 int32 difference = domain.Length() - cookieDomain.Length();
482 // If the cookie domain is longer than the domain string it cannot
483 // be valid.
484 if (difference < 0)
485 return false;
486
487 // If the cookie is host-only the domains must match exactly.
488 if (IsHostOnly())
489 return domain == cookieDomain;
490
491 // FIXME do not do substring matching on IP addresses. The RFCs disallow it.
492
493 // Otherwise, the domains must match exactly, or the domain must have a dot
494 // character just before the common suffix.
495 const char* suffix = domain.String() + difference;
496 return (strcmp(suffix, cookieDomain.String()) == 0 && (difference == 0
497 || domain[difference - 1] == '.'));
498 }
499
500
501 bool
IsValidForPath(const BString & path) const502 BNetworkCookie::IsValidForPath(const BString& path) const
503 {
504 const BString& cookiePath = Path();
505 BString normalizedPath = path;
506 int slashPos = normalizedPath.FindLast('/');
507 if (slashPos != normalizedPath.Length() - 1)
508 normalizedPath.Truncate(slashPos + 1);
509
510 if (normalizedPath.Length() < cookiePath.Length())
511 return false;
512
513 // The cookie path must be a prefix of the path string
514 return normalizedPath.Compare(cookiePath, cookiePath.Length()) == 0;
515 }
516
517
518 bool
_CanBeSetFromUrl(const BUrl & url) const519 BNetworkCookie::_CanBeSetFromUrl(const BUrl& url) const
520 {
521 if (url.Protocol() == "file")
522 return Domain() == "localhost" && _CanBeSetFromPath(url.Path());
523
524 return _CanBeSetFromDomain(url.Host()) && _CanBeSetFromPath(url.Path());
525 }
526
527
528 bool
_CanBeSetFromDomain(const BString & domain) const529 BNetworkCookie::_CanBeSetFromDomain(const BString& domain) const
530 {
531 // TODO: canonicalize both domains
532 const BString& cookieDomain = Domain();
533
534 int32 difference = domain.Length() - cookieDomain.Length();
535 if (difference < 0) {
536 // Setting a cookie on a subdomain is allowed.
537 const char* suffix = cookieDomain.String() + difference;
538 return (strcmp(suffix, domain.String()) == 0 && (difference == 0
539 || cookieDomain[difference - 1] == '.'));
540 }
541
542 // If the cookie is host-only the domains must match exactly.
543 if (IsHostOnly())
544 return domain == cookieDomain;
545
546 // FIXME prevent supercookies with a domain of ".com" or similar
547 // This is NOT as straightforward as relying on the last dot in the domain.
548 // Here's a list of TLD:
549 // https://github.com/rsimoes/Mozilla-PublicSuffix/blob/master/effective_tld_names.dat
550
551 // FIXME do not do substring matching on IP addresses. The RFCs disallow it.
552
553 // Otherwise, the domains must match exactly, or the domain must have a dot
554 // character just before the common suffix.
555 const char* suffix = domain.String() + difference;
556 return (strcmp(suffix, cookieDomain.String()) == 0 && (difference == 0
557 || domain[difference - 1] == '.'));
558 }
559
560
561 bool
_CanBeSetFromPath(const BString & path) const562 BNetworkCookie::_CanBeSetFromPath(const BString& path) const
563 {
564 BString normalizedPath = path;
565 int slashPos = normalizedPath.FindLast('/');
566 normalizedPath.Truncate(slashPos);
567
568 if (Path().Compare(normalizedPath, normalizedPath.Length()) == 0)
569 return true;
570 else if (normalizedPath.Compare(Path(), Path().Length()) == 0)
571 return true;
572 return false;
573 }
574
575
576 // #pragma mark Cookie fields existence tests
577
578
579 bool
HasName() const580 BNetworkCookie::HasName() const
581 {
582 return fName.Length() > 0;
583 }
584
585
586 bool
HasValue() const587 BNetworkCookie::HasValue() const
588 {
589 return fValue.Length() > 0;
590 }
591
592
593 bool
HasDomain() const594 BNetworkCookie::HasDomain() const
595 {
596 return fDomain.Length() > 0;
597 }
598
599
600 bool
HasPath() const601 BNetworkCookie::HasPath() const
602 {
603 return fPath.Length() > 0;
604 }
605
606
607 bool
HasExpirationDate() const608 BNetworkCookie::HasExpirationDate() const
609 {
610 return !IsSessionCookie();
611 }
612
613
614 // #pragma mark Cookie delete test
615
616
617 bool
ShouldDeleteAtExit() const618 BNetworkCookie::ShouldDeleteAtExit() const
619 {
620 return IsSessionCookie() || ShouldDeleteNow();
621 }
622
623
624 bool
ShouldDeleteNow() const625 BNetworkCookie::ShouldDeleteNow() const
626 {
627 if (HasExpirationDate())
628 return (BDateTime::CurrentDateTime(B_GMT_TIME) > fExpiration);
629
630 return false;
631 }
632
633
634 // #pragma mark BArchivable members
635
636
637 status_t
Archive(BMessage * into,bool deep) const638 BNetworkCookie::Archive(BMessage* into, bool deep) const
639 {
640 status_t error = BArchivable::Archive(into, deep);
641
642 if (error != B_OK)
643 return error;
644
645 error = into->AddString(kArchivedCookieName, fName);
646 if (error != B_OK)
647 return error;
648
649 error = into->AddString(kArchivedCookieValue, fValue);
650 if (error != B_OK)
651 return error;
652
653
654 // We add optional fields only if they're defined
655 if (HasDomain()) {
656 error = into->AddString(kArchivedCookieDomain, fDomain);
657 if (error != B_OK)
658 return error;
659 }
660
661 if (HasExpirationDate()) {
662 error = into->AddString(kArchivedCookieExpirationDate,
663 BHttpTime(fExpiration).ToString());
664 if (error != B_OK)
665 return error;
666 }
667
668 if (HasPath()) {
669 error = into->AddString(kArchivedCookiePath, fPath);
670 if (error != B_OK)
671 return error;
672 }
673
674 if (Secure()) {
675 error = into->AddBool(kArchivedCookieSecure, fSecure);
676 if (error != B_OK)
677 return error;
678 }
679
680 if (HttpOnly()) {
681 error = into->AddBool(kArchivedCookieHttpOnly, fHttpOnly);
682 if (error != B_OK)
683 return error;
684 }
685
686 if (IsHostOnly()) {
687 error = into->AddBool(kArchivedCookieHostOnly, true);
688 if (error != B_OK)
689 return error;
690 }
691
692 return B_OK;
693 }
694
695
696 /*static*/ BArchivable*
Instantiate(BMessage * archive)697 BNetworkCookie::Instantiate(BMessage* archive)
698 {
699 if (archive->HasString(kArchivedCookieName)
700 && archive->HasString(kArchivedCookieValue))
701 return new(std::nothrow) BNetworkCookie(archive);
702
703 return NULL;
704 }
705
706
707 // #pragma mark Overloaded operators
708
709
710 bool
operator ==(const BNetworkCookie & other)711 BNetworkCookie::operator==(const BNetworkCookie& other)
712 {
713 // Equality : name and values equals
714 return fName == other.fName && fValue == other.fValue;
715 }
716
717
718 bool
operator !=(const BNetworkCookie & other)719 BNetworkCookie::operator!=(const BNetworkCookie& other)
720 {
721 return !(*this == other);
722 }
723
724
725 void
_Reset()726 BNetworkCookie::_Reset()
727 {
728 fInitStatus = false;
729
730 fName.Truncate(0);
731 fValue.Truncate(0);
732 fDomain.Truncate(0);
733 fPath.Truncate(0);
734 fExpiration = BDateTime();
735 fSecure = false;
736 fHttpOnly = false;
737
738 fSessionCookie = true;
739 fHostOnly = true;
740
741 fRawCookieValid = false;
742 fRawFullCookieValid = false;
743 fExpirationStringValid = false;
744 }
745
746
747 int32
skip_whitespace_forward(const BString & string,int32 index)748 skip_whitespace_forward(const BString& string, int32 index)
749 {
750 while (index < string.Length() && (string[index] == ' '
751 || string[index] == '\t'))
752 index++;
753 return index;
754 }
755
756
757 int32
skip_whitespace_backward(const BString & string,int32 index)758 skip_whitespace_backward(const BString& string, int32 index)
759 {
760 while (index >= 0 && (string[index] == ' ' || string[index] == '\t'))
761 index--;
762 return index;
763 }
764
765
766 int32
_ExtractNameValuePair(const BString & cookieString,BString & name,BString & value,int32 index)767 BNetworkCookie::_ExtractNameValuePair(const BString& cookieString,
768 BString& name, BString& value, int32 index)
769 {
770 // Find our name-value-pair and the delimiter.
771 int32 firstEquals = cookieString.FindFirst('=', index);
772 int32 nameValueEnd = cookieString.FindFirst(';', index);
773
774 // If the set-cookie-string lacks a semicolon, the name-value-pair
775 // is the whole string.
776 if (nameValueEnd == -1)
777 nameValueEnd = cookieString.Length();
778
779 // If the name-value-pair lacks an equals, the parse should fail.
780 if (firstEquals == -1 || firstEquals > nameValueEnd)
781 return -1;
782
783 int32 first = skip_whitespace_forward(cookieString, index);
784 int32 last = skip_whitespace_backward(cookieString, firstEquals - 1);
785
786 // If we lack a name, fail to parse.
787 if (first > last)
788 return -1;
789
790 cookieString.CopyInto(name, first, last - first + 1);
791
792 first = skip_whitespace_forward(cookieString, firstEquals + 1);
793 last = skip_whitespace_backward(cookieString, nameValueEnd - 1);
794 if (first <= last)
795 cookieString.CopyInto(value, first, last - first + 1);
796 else
797 value.SetTo("");
798
799 return nameValueEnd;
800 }
801
802
803 int32
_ExtractAttributeValuePair(const BString & cookieString,BString & attribute,BString & value,int32 index)804 BNetworkCookie::_ExtractAttributeValuePair(const BString& cookieString,
805 BString& attribute, BString& value, int32 index)
806 {
807 // Find the end of our cookie-av.
808 int32 cookieAVEnd = cookieString.FindFirst(';', index);
809
810 // If the unparsed-attributes lacks a semicolon, then the cookie-av is the
811 // whole string.
812 if (cookieAVEnd == -1)
813 cookieAVEnd = cookieString.Length();
814
815 int32 attributeNameEnd = cookieString.FindFirst('=', index);
816 // If the cookie-av has no equals, the attribute-name is the entire
817 // cookie-av and the attribute-value is empty.
818 if (attributeNameEnd == -1 || attributeNameEnd > cookieAVEnd)
819 attributeNameEnd = cookieAVEnd;
820
821 int32 first = skip_whitespace_forward(cookieString, index);
822 int32 last = skip_whitespace_backward(cookieString, attributeNameEnd - 1);
823
824 if (first <= last)
825 cookieString.CopyInto(attribute, first, last - first + 1);
826 else
827 attribute.SetTo("");
828
829 if (attributeNameEnd == cookieAVEnd) {
830 value.SetTo("");
831 return cookieAVEnd;
832 }
833
834 first = skip_whitespace_forward(cookieString, attributeNameEnd + 1);
835 last = skip_whitespace_backward(cookieString, cookieAVEnd - 1);
836 if (first <= last)
837 cookieString.CopyInto(value, first, last - first + 1);
838 else
839 value.SetTo("");
840
841 // values may (or may not) have quotes around them.
842 if (value[0] == '"' && value[value.Length() - 1] == '"') {
843 value.Remove(0, 1);
844 value.Remove(value.Length() - 1, 1);
845 }
846
847 return cookieAVEnd;
848 }
849
850
851 BString
_DefaultPathForUrl(const BUrl & url)852 BNetworkCookie::_DefaultPathForUrl(const BUrl& url)
853 {
854 const BString& path = url.Path();
855 if (path.IsEmpty() || path.ByteAt(0) != '/')
856 return "";
857
858 int32 index = path.FindLast('/');
859 if (index == 0)
860 return "";
861
862 BString newPath = path;
863 newPath.Truncate(index);
864 return newPath;
865 }
866