xref: /haiku/src/kits/network/libnetservices/NetworkCookieJar.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  *		Christophe Huriaux, c.huriaux@gmail.com
7  *		Hamish Morrison, hamishm53@gmail.com
8  */
9 
10 
11 #include <new>
12 #include <stdio.h>
13 
14 #include <HashMap.h>
15 #include <HashString.h>
16 #include <Message.h>
17 #include <NetworkCookieJar.h>
18 
19 #include "NetworkCookieJarPrivate.h"
20 
21 using namespace BPrivate::Network;
22 
23 
24 // #define TRACE_COOKIE
25 #ifdef TRACE_COOKIE
26 #	define TRACE(x...) printf(x)
27 #else
28 #	define TRACE(x...) ;
29 #endif
30 
31 
32 const char* kArchivedCookieMessageName = "be:cookie";
33 
34 
BNetworkCookieJar()35 BNetworkCookieJar::BNetworkCookieJar()
36 	:
37 	fCookieHashMap(new(std::nothrow) PrivateHashMap())
38 {
39 }
40 
41 
BNetworkCookieJar(const BNetworkCookieJar & other)42 BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieJar& other)
43 	:
44 	fCookieHashMap(new(std::nothrow) PrivateHashMap())
45 {
46 	*this = other;
47 }
48 
49 
BNetworkCookieJar(const BNetworkCookieList & otherList)50 BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieList& otherList)
51 	:
52 	fCookieHashMap(new(std::nothrow) PrivateHashMap())
53 {
54 	AddCookies(otherList);
55 }
56 
57 
BNetworkCookieJar(BMessage * archive)58 BNetworkCookieJar::BNetworkCookieJar(BMessage* archive)
59 	:
60 	fCookieHashMap(new(std::nothrow) PrivateHashMap())
61 {
62 	BMessage extractedCookie;
63 
64 	for (int32 i = 0; archive->FindMessage(kArchivedCookieMessageName, i,
65 			&extractedCookie) == B_OK; i++) {
66 		BNetworkCookie* heapCookie
67 			= new(std::nothrow) BNetworkCookie(&extractedCookie);
68 
69 		if (heapCookie == NULL)
70 			break;
71 
72 		if (AddCookie(heapCookie) != B_OK) {
73 			delete heapCookie;
74 			continue;
75 		}
76 	}
77 }
78 
79 
~BNetworkCookieJar()80 BNetworkCookieJar::~BNetworkCookieJar()
81 {
82 	for (Iterator it = GetIterator(); it.Next() != NULL;)
83 		delete it.Remove();
84 
85 	fCookieHashMap->Lock();
86 
87 	PrivateHashMap::Iterator it = fCookieHashMap->GetIterator();
88 	while (it.HasNext()) {
89 		BNetworkCookieList* list = it.Next().value;
90 		list->LockForWriting();
91 		delete list;
92 	}
93 
94 	delete fCookieHashMap;
95 }
96 
97 
98 // #pragma mark Add cookie to cookie jar
99 
100 
101 status_t
AddCookie(const BNetworkCookie & cookie)102 BNetworkCookieJar::AddCookie(const BNetworkCookie& cookie)
103 {
104 	BNetworkCookie* heapCookie = new(std::nothrow) BNetworkCookie(cookie);
105 	if (heapCookie == NULL)
106 		return B_NO_MEMORY;
107 
108 	status_t result = AddCookie(heapCookie);
109 	if (result != B_OK)
110 		delete heapCookie;
111 
112 	return result;
113 }
114 
115 
116 status_t
AddCookie(const BString & cookie,const BUrl & referrer)117 BNetworkCookieJar::AddCookie(const BString& cookie, const BUrl& referrer)
118 {
119 	BNetworkCookie* heapCookie = new(std::nothrow) BNetworkCookie(cookie,
120 		referrer);
121 
122 	if (heapCookie == NULL)
123 		return B_NO_MEMORY;
124 
125 	status_t result = AddCookie(heapCookie);
126 
127 	if (result != B_OK)
128 		delete heapCookie;
129 
130 	return result;
131 }
132 
133 
134 status_t
AddCookie(BNetworkCookie * cookie)135 BNetworkCookieJar::AddCookie(BNetworkCookie* cookie)
136 {
137 	if (fCookieHashMap == NULL)
138 		return B_NO_MEMORY;
139 
140 	if (cookie == NULL || !cookie->IsValid())
141 		return B_BAD_VALUE;
142 
143 	HashString key(cookie->Domain());
144 
145 	if (!fCookieHashMap->Lock())
146 		return B_ERROR;
147 
148 	// Get the cookies for the requested domain, or create a new list if there
149 	// isn't one yet.
150 	BNetworkCookieList* list = fCookieHashMap->Get(key);
151 	if (list == NULL) {
152 		list = new(std::nothrow) BNetworkCookieList();
153 
154 		if (list == NULL) {
155 			fCookieHashMap->Unlock();
156 			return B_NO_MEMORY;
157 		}
158 
159 		if (fCookieHashMap->Put(key, list) != B_OK) {
160 			fCookieHashMap->Unlock();
161 			delete list;
162 			return B_NO_MEMORY;
163 		}
164 	}
165 
166 	if (list->LockForWriting() != B_OK) {
167 		fCookieHashMap->Unlock();
168 		return B_ERROR;
169 	}
170 
171 	fCookieHashMap->Unlock();
172 
173 	// Remove any cookie with the same key as the one we're trying to add (it
174 	// replaces/updates them)
175 	for (int32 i = 0; i < list->CountItems(); i++) {
176 		const BNetworkCookie* c = list->ItemAt(i);
177 
178 		if (c->Name() == cookie->Name() && c->Path() == cookie->Path()) {
179 			list->RemoveItemAt(i);
180 			break;
181 		}
182 	}
183 
184 	// If the cookie has an expiration date in the past, stop here: we
185 	// effectively deleted a cookie.
186 	if (cookie->ShouldDeleteNow()) {
187 		TRACE("Remove cookie: %s\n", cookie->RawCookie(true).String());
188 		delete cookie;
189 	} else {
190 		// Make sure the cookie has cached the raw string and expiration date
191 		// string, so it is now actually immutable. This way we can safely
192 		// read the cookie data from multiple threads without any locking.
193 		const BString& raw = cookie->RawCookie(true);
194 		(void)raw;
195 
196 		TRACE("Add cookie: %s\n", raw.String());
197 
198 		// Keep the list sorted by path length (longest first). This makes sure
199 		// that cookies for most specific paths are returned first when
200 		// iterating the cookie jar.
201 		int32 i;
202 		for (i = 0; i < list->CountItems(); i++) {
203 			const BNetworkCookie* current = list->ItemAt(i);
204 			if (current->Path().Length() < cookie->Path().Length())
205 				break;
206 		}
207 		list->AddItem(cookie, i);
208 	}
209 
210 	list->Unlock();
211 
212 	return B_OK;
213 }
214 
215 
216 status_t
AddCookies(const BNetworkCookieList & cookies)217 BNetworkCookieJar::AddCookies(const BNetworkCookieList& cookies)
218 {
219 	for (int32 i = 0; i < cookies.CountItems(); i++) {
220 		const BNetworkCookie* cookiePtr = cookies.ItemAt(i);
221 
222 		// Using AddCookie by reference in order to avoid multiple
223 		// cookie jar share the same cookie pointers
224 		status_t result = AddCookie(*cookiePtr);
225 		if (result != B_OK)
226 			return result;
227 	}
228 
229 	return B_OK;
230 }
231 
232 
233 // #pragma mark Purge useless cookies
234 
235 
236 uint32
DeleteOutdatedCookies()237 BNetworkCookieJar::DeleteOutdatedCookies()
238 {
239 	int32 deleteCount = 0;
240 	const BNetworkCookie* cookiePtr;
241 
242 	for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
243 		if (cookiePtr->ShouldDeleteNow()) {
244 			delete it.Remove();
245 			deleteCount++;
246 		}
247 	}
248 
249 	return deleteCount;
250 }
251 
252 
253 uint32
PurgeForExit()254 BNetworkCookieJar::PurgeForExit()
255 {
256 	int32 deleteCount = 0;
257 	const BNetworkCookie* cookiePtr;
258 
259 	for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
260 		if (cookiePtr->ShouldDeleteAtExit()) {
261 			delete it.Remove();
262 			deleteCount++;
263 		}
264 	}
265 
266 	return deleteCount;
267 }
268 
269 
270 // #pragma mark BArchivable interface
271 
272 
273 status_t
Archive(BMessage * into,bool deep) const274 BNetworkCookieJar::Archive(BMessage* into, bool deep) const
275 {
276 	status_t error = BArchivable::Archive(into, deep);
277 
278 	if (error == B_OK) {
279 		const BNetworkCookie* cookiePtr;
280 
281 		for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
282 			BMessage subArchive;
283 
284 			error = cookiePtr->Archive(&subArchive, deep);
285 			if (error != B_OK)
286 				return error;
287 
288 			error = into->AddMessage(kArchivedCookieMessageName, &subArchive);
289 			if (error != B_OK)
290 				return error;
291 		}
292 	}
293 
294 	return error;
295 }
296 
297 
298 BArchivable*
Instantiate(BMessage * archive)299 BNetworkCookieJar::Instantiate(BMessage* archive)
300 {
301 	if (archive->HasMessage(kArchivedCookieMessageName))
302 		return new(std::nothrow) BNetworkCookieJar(archive);
303 
304 	return NULL;
305 }
306 
307 
308 // #pragma mark BFlattenable interface
309 
310 
311 bool
IsFixedSize() const312 BNetworkCookieJar::IsFixedSize() const
313 {
314 	// Flattened size vary
315 	return false;
316 }
317 
318 
319 type_code
TypeCode() const320 BNetworkCookieJar::TypeCode() const
321 {
322 	// TODO: Add a B_COOKIEJAR_TYPE
323 	return B_ANY_TYPE;
324 }
325 
326 
327 ssize_t
FlattenedSize() const328 BNetworkCookieJar::FlattenedSize() const
329 {
330 	_DoFlatten();
331 	return fFlattened.Length() + 1;
332 }
333 
334 
335 status_t
Flatten(void * buffer,ssize_t size) const336 BNetworkCookieJar::Flatten(void* buffer, ssize_t size) const
337 {
338 	if (FlattenedSize() > size)
339 		return B_ERROR;
340 
341 	fFlattened.CopyInto(reinterpret_cast<char*>(buffer), 0,
342 		fFlattened.Length());
343 	reinterpret_cast<char*>(buffer)[fFlattened.Length()] = 0;
344 
345 	return B_OK;
346 }
347 
348 
349 bool
AllowsTypeCode(type_code) const350 BNetworkCookieJar::AllowsTypeCode(type_code) const
351 {
352 	// TODO
353 	return false;
354 }
355 
356 
357 status_t
Unflatten(type_code,const void * buffer,ssize_t size)358 BNetworkCookieJar::Unflatten(type_code, const void* buffer, ssize_t size)
359 {
360 	BString flattenedCookies;
361 	flattenedCookies.SetTo(reinterpret_cast<const char*>(buffer), size);
362 
363 	while (flattenedCookies.Length() > 0) {
364 		BNetworkCookie tempCookie;
365 		BString tempCookieLine;
366 
367 		int32 endOfLine = flattenedCookies.FindFirst('\n', 0);
368 		if (endOfLine == -1)
369 			tempCookieLine = flattenedCookies;
370 		else {
371 			flattenedCookies.MoveInto(tempCookieLine, 0, endOfLine);
372 			flattenedCookies.Remove(0, 1);
373 		}
374 
375 		if (tempCookieLine.Length() != 0 && tempCookieLine[0] != '#') {
376 			for (int32 field = 0; field < 7; field++) {
377 				BString tempString;
378 
379 				int32 endOfField = tempCookieLine.FindFirst('\t', 0);
380 				if (endOfField == -1)
381 					tempString = tempCookieLine;
382 				else {
383 					tempCookieLine.MoveInto(tempString, 0, endOfField);
384 					tempCookieLine.Remove(0, 1);
385 				}
386 
387 				switch (field) {
388 					case 0:
389 						tempCookie.SetDomain(tempString);
390 						break;
391 
392 					case 1:
393 						// TODO: Useless field ATM
394 						break;
395 
396 					case 2:
397 						tempCookie.SetPath(tempString);
398 						break;
399 
400 					case 3:
401 						tempCookie.SetSecure(tempString == "TRUE");
402 						break;
403 
404 					case 4:
405 						tempCookie.SetExpirationDate(atoi(tempString));
406 						break;
407 
408 					case 5:
409 						tempCookie.SetName(tempString);
410 						break;
411 
412 					case 6:
413 						tempCookie.SetValue(tempString);
414 						break;
415 				} // switch
416 			} // for loop
417 
418 			AddCookie(tempCookie);
419 		}
420 	}
421 
422 	return B_OK;
423 }
424 
425 
426 BNetworkCookieJar&
operator =(const BNetworkCookieJar & other)427 BNetworkCookieJar::operator=(const BNetworkCookieJar& other)
428 {
429 	if (&other == this)
430 		return *this;
431 
432 	for (Iterator it = GetIterator(); it.Next() != NULL;)
433 		delete it.Remove();
434 
435 	BArchivable::operator=(other);
436 	BFlattenable::operator=(other);
437 
438 	fFlattened = other.fFlattened;
439 
440 	delete fCookieHashMap;
441 	fCookieHashMap = new(std::nothrow) PrivateHashMap();
442 
443 	for (Iterator it = other.GetIterator(); it.HasNext();) {
444 		const BNetworkCookie* cookie = it.Next();
445 		AddCookie(*cookie); // Pass by reference so the cookie is copied.
446 	}
447 
448 	return *this;
449 }
450 
451 
452 // #pragma mark Iterators
453 
454 
455 BNetworkCookieJar::Iterator
GetIterator() const456 BNetworkCookieJar::GetIterator() const
457 {
458 	return BNetworkCookieJar::Iterator(this);
459 }
460 
461 
462 BNetworkCookieJar::UrlIterator
GetUrlIterator(const BUrl & url) const463 BNetworkCookieJar::GetUrlIterator(const BUrl& url) const
464 {
465 	if (!url.HasPath()) {
466 		BUrl copy(url);
467 		copy.SetPath("/");
468 		return BNetworkCookieJar::UrlIterator(this, copy);
469 	}
470 
471 	return BNetworkCookieJar::UrlIterator(this, url);
472 }
473 
474 
475 void
_DoFlatten() const476 BNetworkCookieJar::_DoFlatten() const
477 {
478 	fFlattened.Truncate(0);
479 
480 	const BNetworkCookie* cookiePtr;
481 	for (Iterator it = GetIterator(); (cookiePtr = it.Next()) != NULL;) {
482 		fFlattened 	<< cookiePtr->Domain() << '\t' << "TRUE" << '\t'
483 			<< cookiePtr->Path() << '\t'
484 			<< (cookiePtr->Secure()?"TRUE":"FALSE") << '\t'
485 			<< (int32)cookiePtr->ExpirationDate() << '\t'
486 			<< cookiePtr->Name() << '\t' << cookiePtr->Value() << '\n';
487 	}
488 }
489 
490 
491 // #pragma mark Iterator
492 
493 
Iterator(const Iterator & other)494 BNetworkCookieJar::Iterator::Iterator(const Iterator& other)
495 	:
496 	fCookieJar(other.fCookieJar),
497 	fIterator(NULL),
498 	fLastList(NULL),
499 	fList(NULL),
500 	fElement(NULL),
501 	fLastElement(NULL),
502 	fIndex(0)
503 {
504 	fIterator = new(std::nothrow) PrivateIterator(
505 		fCookieJar->fCookieHashMap->GetIterator());
506 
507 	_FindNext();
508 }
509 
510 
Iterator(const BNetworkCookieJar * cookieJar)511 BNetworkCookieJar::Iterator::Iterator(const BNetworkCookieJar* cookieJar)
512 	:
513 	fCookieJar(const_cast<BNetworkCookieJar*>(cookieJar)),
514 	fIterator(NULL),
515 	fLastList(NULL),
516 	fList(NULL),
517 	fElement(NULL),
518 	fLastElement(NULL),
519 	fIndex(0)
520 {
521 	fIterator = new(std::nothrow) PrivateIterator(
522 		fCookieJar->fCookieHashMap->GetIterator());
523 
524 	// Locate first cookie
525 	_FindNext();
526 }
527 
528 
~Iterator()529 BNetworkCookieJar::Iterator::~Iterator()
530 {
531 	if (fList != NULL)
532 		fList->Unlock();
533 	if (fLastList != NULL)
534 		fLastList->Unlock();
535 
536 	delete fIterator;
537 }
538 
539 
540 BNetworkCookieJar::Iterator&
operator =(const Iterator & other)541 BNetworkCookieJar::Iterator::operator=(const Iterator& other)
542 {
543 	if (this == &other)
544 		return *this;
545 
546 	delete fIterator;
547 	if (fList != NULL)
548 		fList->Unlock();
549 
550 	fCookieJar = other.fCookieJar;
551 	fIterator = NULL;
552 	fLastList = NULL;
553 	fList = NULL;
554 	fElement = NULL;
555 	fLastElement = NULL;
556 	fIndex = 0;
557 
558 	fIterator = new(std::nothrow) PrivateIterator(
559 		fCookieJar->fCookieHashMap->GetIterator());
560 
561 	_FindNext();
562 
563 	return *this;
564 }
565 
566 
567 bool
HasNext() const568 BNetworkCookieJar::Iterator::HasNext() const
569 {
570 	return fElement;
571 }
572 
573 
574 const BNetworkCookie*
Next()575 BNetworkCookieJar::Iterator::Next()
576 {
577 	if (!fElement)
578 		return NULL;
579 
580 	const BNetworkCookie* result = fElement;
581 	_FindNext();
582 	return result;
583 }
584 
585 
586 const BNetworkCookie*
NextDomain()587 BNetworkCookieJar::Iterator::NextDomain()
588 {
589 	if (!fElement)
590 		return NULL;
591 
592 	const BNetworkCookie* result = fElement;
593 
594 	if (!fIterator->fCookieMapIterator.HasNext()) {
595 		fElement = NULL;
596 		return result;
597 	}
598 
599 	if (fList != NULL)
600 		fList->Unlock();
601 
602 	if (fCookieJar->fCookieHashMap->Lock()) {
603 		fList = fIterator->fCookieMapIterator.Next().value;
604 		fList->LockForReading();
605 
606 		while (fList->CountItems() == 0
607 			&& fIterator->fCookieMapIterator.HasNext()) {
608 			// Empty list. Skip it
609 			fList->Unlock();
610 			fList = fIterator->fCookieMapIterator.Next().value;
611 			fList->LockForReading();
612 		}
613 
614 		fCookieJar->fCookieHashMap->Unlock();
615 	}
616 
617 	fIndex = 0;
618 	fElement = fList->ItemAt(fIndex);
619 	return result;
620 }
621 
622 
623 const BNetworkCookie*
Remove()624 BNetworkCookieJar::Iterator::Remove()
625 {
626 	if (!fLastElement)
627 		return NULL;
628 
629 	const BNetworkCookie* result = fLastElement;
630 
631 	if (fIndex == 0) {
632 		if (fLastList && fCookieJar->fCookieHashMap->Lock()) {
633 			// We are on the first item of fList, so we need to remove the
634 			// last of fLastList
635 			fLastList->Unlock();
636 			if (fLastList->LockForWriting() == B_OK) {
637 				fLastList->RemoveItemAt(fLastList->CountItems() - 1);
638 				// TODO if the list became empty, we could remove it from the
639 				// map, but this can be a problem if other iterators are still
640 				// referencing it. Is there a safe place and locking pattern
641 				// where we can do that?
642 				fLastList->Unlock();
643 				fLastList->LockForReading();
644 			}
645 			fCookieJar->fCookieHashMap->Unlock();
646 		}
647 	} else {
648 		fIndex--;
649 
650 		if (fCookieJar->fCookieHashMap->Lock()) {
651 			// Switch to a write lock
652 			fList->Unlock();
653 			if (fList->LockForWriting() == B_OK) {
654 				fList->RemoveItemAt(fIndex);
655 				fList->Unlock();
656 			}
657 			fList->LockForReading();
658 			fCookieJar->fCookieHashMap->Unlock();
659 		}
660 	}
661 
662 	fLastElement = NULL;
663 	return result;
664 }
665 
666 
667 void
_FindNext()668 BNetworkCookieJar::Iterator::_FindNext()
669 {
670 	fLastElement = fElement;
671 
672 	fIndex++;
673 	if (fList && fIndex < fList->CountItems()) {
674 		// Get an element from the current list
675 		fElement = fList->ItemAt(fIndex);
676 		return;
677 	}
678 
679 	if (fIterator == NULL || !fIterator->fCookieMapIterator.HasNext()) {
680 		// We are done iterating
681 		fElement = NULL;
682 		return;
683 	}
684 
685 	// Get an element from the next list
686 	if (fLastList != NULL) {
687 		fLastList->Unlock();
688 	}
689 	fLastList = fList;
690 
691 	if (fCookieJar->fCookieHashMap->Lock()) {
692 		fList = (fIterator->fCookieMapIterator.Next().value);
693 		fList->LockForReading();
694 
695 		while (fList->CountItems() == 0
696 			&& fIterator->fCookieMapIterator.HasNext()) {
697 			// Empty list. Skip it
698 			fList->Unlock();
699 			fList = fIterator->fCookieMapIterator.Next().value;
700 			fList->LockForReading();
701 		}
702 
703 		fCookieJar->fCookieHashMap->Unlock();
704 	}
705 
706 	fIndex = 0;
707 	fElement = fList->ItemAt(fIndex);
708 }
709 
710 
711 // #pragma mark URL Iterator
712 
713 
UrlIterator(const UrlIterator & other)714 BNetworkCookieJar::UrlIterator::UrlIterator(const UrlIterator& other)
715 	:
716 	fCookieJar(other.fCookieJar),
717 	fIterator(NULL),
718 	fList(NULL),
719 	fLastList(NULL),
720 	fElement(NULL),
721 	fLastElement(NULL),
722 	fIndex(0),
723 	fLastIndex(0),
724 	fUrl(other.fUrl)
725 {
726 	_Initialize();
727 }
728 
729 
UrlIterator(const BNetworkCookieJar * cookieJar,const BUrl & url)730 BNetworkCookieJar::UrlIterator::UrlIterator(const BNetworkCookieJar* cookieJar,
731 	const BUrl& url)
732 	:
733 	fCookieJar(const_cast<BNetworkCookieJar*>(cookieJar)),
734 	fIterator(NULL),
735 	fList(NULL),
736 	fLastList(NULL),
737 	fElement(NULL),
738 	fLastElement(NULL),
739 	fIndex(0),
740 	fLastIndex(0),
741 	fUrl(url)
742 {
743 	_Initialize();
744 }
745 
746 
~UrlIterator()747 BNetworkCookieJar::UrlIterator::~UrlIterator()
748 {
749 	if (fList != NULL)
750 		fList->Unlock();
751 	if (fLastList != NULL)
752 		fLastList->Unlock();
753 
754 	delete fIterator;
755 }
756 
757 
758 bool
HasNext() const759 BNetworkCookieJar::UrlIterator::HasNext() const
760 {
761 	return fElement;
762 }
763 
764 
765 const BNetworkCookie*
Next()766 BNetworkCookieJar::UrlIterator::Next()
767 {
768 	if (!fElement)
769 		return NULL;
770 
771 	const BNetworkCookie* result = fElement;
772 	_FindNext();
773 	return result;
774 }
775 
776 
777 const BNetworkCookie*
Remove()778 BNetworkCookieJar::UrlIterator::Remove()
779 {
780 	if (!fLastElement)
781 		return NULL;
782 
783 	const BNetworkCookie* result = fLastElement;
784 
785 	if (fCookieJar->fCookieHashMap->Lock()) {
786 		fLastList->Unlock();
787 		if (fLastList->LockForWriting() == B_OK) {
788 			fLastList->RemoveItemAt(fLastIndex);
789 
790 			if (fLastList->CountItems() == 0) {
791 				fCookieJar->fCookieHashMap->Remove(fIterator->fCookieMapIterator);
792 				delete fLastList;
793 				fLastList = NULL;
794 			} else {
795 				fLastList->Unlock();
796 				fLastList->LockForReading();
797 			}
798 		}
799 		fCookieJar->fCookieHashMap->Unlock();
800 	}
801 
802 	fLastElement = NULL;
803 	return result;
804 }
805 
806 
807 BNetworkCookieJar::UrlIterator&
operator =(const BNetworkCookieJar::UrlIterator & other)808 BNetworkCookieJar::UrlIterator::operator=(
809 	const BNetworkCookieJar::UrlIterator& other)
810 {
811 	if (this == &other)
812 		return *this;
813 
814 	// Teardown
815 	if (fList)
816 		fList->Unlock();
817 
818 	delete fIterator;
819 
820 	// Init
821 	fCookieJar = other.fCookieJar;
822 	fIterator = NULL;
823 	fList = NULL;
824 	fLastList = NULL;
825 	fElement = NULL;
826 	fLastElement = NULL;
827 	fIndex = 0;
828 	fLastIndex = 0;
829 	fUrl = other.fUrl;
830 
831 	_Initialize();
832 
833 	return *this;
834 }
835 
836 
837 void
_Initialize()838 BNetworkCookieJar::UrlIterator::_Initialize()
839 {
840 	BString domain = fUrl.Host();
841 
842 	if (!domain.Length()) {
843 		if (fUrl.Protocol() == "file")
844 			domain = "localhost";
845 		else
846 			return;
847 	}
848 
849 	fIterator = new(std::nothrow) PrivateIterator(
850 		fCookieJar->fCookieHashMap->GetIterator());
851 
852 	if (fIterator != NULL) {
853 		// Prepending a dot since _FindNext is going to call _SupDomain()
854 		domain.Prepend(".");
855 		fIterator->fKey.SetTo(domain, domain.Length());
856 		_FindNext();
857 	}
858 }
859 
860 
861 bool
_SuperDomain()862 BNetworkCookieJar::UrlIterator::_SuperDomain()
863 {
864 	BString domain(fIterator->fKey.GetString());
865 		// Makes a copy of the characters from the key. This is important,
866 		// because HashString doesn't like SetTo to be called with a substring
867 		// of its original string (use-after-free + memcpy overwrite).
868 	int32 firstDot = domain.FindFirst('.');
869 	if (firstDot < 0)
870 		return false;
871 
872 	const char* nextDot = domain.String() + firstDot;
873 
874 	fIterator->fKey.SetTo(nextDot + 1);
875 	return true;
876 }
877 
878 
879 void
_FindNext()880 BNetworkCookieJar::UrlIterator::_FindNext()
881 {
882 	fLastIndex = fIndex;
883 	fLastElement = fElement;
884 	if (fLastList != NULL)
885 		fLastList->Unlock();
886 
887 	fLastList = fList;
888 	if (fCookieJar->fCookieHashMap->Lock()) {
889 		if (fLastList)
890 			fLastList->LockForReading();
891 
892 		while (!_FindPath()) {
893 			if (!_SuperDomain()) {
894 				fElement = NULL;
895 				fCookieJar->fCookieHashMap->Unlock();
896 				return;
897 			}
898 
899 			_FindDomain();
900 		}
901 		fCookieJar->fCookieHashMap->Unlock();
902 	}
903 }
904 
905 
906 void
_FindDomain()907 BNetworkCookieJar::UrlIterator::_FindDomain()
908 {
909 	if (fList != NULL)
910 		fList->Unlock();
911 
912 	if (fCookieJar->fCookieHashMap->Lock()) {
913 		fList = fCookieJar->fCookieHashMap->Get(fIterator->fKey);
914 
915 		if (fList == NULL)
916 			fElement = NULL;
917 		else {
918 			fList->LockForReading();
919 		}
920 		fCookieJar->fCookieHashMap->Unlock();
921 	}
922 
923 	fIndex = -1;
924 }
925 
926 
927 bool
_FindPath()928 BNetworkCookieJar::UrlIterator::_FindPath()
929 {
930 	fIndex++;
931 	while (fList && fIndex < fList->CountItems()) {
932 		fElement = fList->ItemAt(fIndex);
933 
934 		if (fElement->IsValidForPath(fUrl.Path()))
935 			return true;
936 
937 		fIndex++;
938 	}
939 
940 	return false;
941 }
942 
943 
944 // #pragma mark - BNetworkCookieList
945 
946 
BNetworkCookieList()947 BNetworkCookieList::BNetworkCookieList()
948 {
949 	pthread_rwlock_init(&fLock, NULL);
950 }
951 
952 
~BNetworkCookieList()953 BNetworkCookieList::~BNetworkCookieList()
954 {
955 	// Note: this is expected to be called with the write lock held.
956 	pthread_rwlock_destroy(&fLock);
957 }
958 
959 
960 status_t
LockForReading()961 BNetworkCookieList::LockForReading()
962 {
963 	return pthread_rwlock_rdlock(&fLock);
964 }
965 
966 
967 status_t
LockForWriting()968 BNetworkCookieList::LockForWriting()
969 {
970 	return pthread_rwlock_wrlock(&fLock);
971 }
972 
973 
974 status_t
Unlock()975 BNetworkCookieList::Unlock()
976 {
977 	return pthread_rwlock_unlock(&fLock);
978 }
979 
980