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