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