xref: /haiku/src/kits/network/libnetservices/NetworkCookie.cpp (revision 70b63f18b32a7ae90e4ff49659ba3844ce4c9aa5)
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