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
21using 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
32const char* kArchivedCookieMessageName = "be:cookie";
33
34
35BNetworkCookieJar::BNetworkCookieJar()
36	:
37	fCookieHashMap(new(std::nothrow) PrivateHashMap())
38{
39}
40
41
42BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieJar& other)
43	:
44	fCookieHashMap(new(std::nothrow) PrivateHashMap())
45{
46	*this = other;
47}
48
49
50BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieList& otherList)
51	:
52	fCookieHashMap(new(std::nothrow) PrivateHashMap())
53{
54	AddCookies(otherList);
55}
56
57
58BNetworkCookieJar::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
80BNetworkCookieJar::~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
101status_t
102BNetworkCookieJar::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
116status_t
117BNetworkCookieJar::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
134status_t
135BNetworkCookieJar::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
216status_t
217BNetworkCookieJar::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
236uint32
237BNetworkCookieJar::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
253uint32
254BNetworkCookieJar::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
273status_t
274BNetworkCookieJar::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
298BArchivable*
299BNetworkCookieJar::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
311bool
312BNetworkCookieJar::IsFixedSize() const
313{
314	// Flattened size vary
315	return false;
316}
317
318
319type_code
320BNetworkCookieJar::TypeCode() const
321{
322	// TODO: Add a B_COOKIEJAR_TYPE
323	return B_ANY_TYPE;
324}
325
326
327ssize_t
328BNetworkCookieJar::FlattenedSize() const
329{
330	_DoFlatten();
331	return fFlattened.Length() + 1;
332}
333
334
335status_t
336BNetworkCookieJar::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
349bool
350BNetworkCookieJar::AllowsTypeCode(type_code) const
351{
352	// TODO
353	return false;
354}
355
356
357status_t
358BNetworkCookieJar::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
426BNetworkCookieJar&
427BNetworkCookieJar::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
455BNetworkCookieJar::Iterator
456BNetworkCookieJar::GetIterator() const
457{
458	return BNetworkCookieJar::Iterator(this);
459}
460
461
462BNetworkCookieJar::UrlIterator
463BNetworkCookieJar::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
475void
476BNetworkCookieJar::_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
494BNetworkCookieJar::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
511BNetworkCookieJar::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
529BNetworkCookieJar::Iterator::~Iterator()
530{
531	if (fList != NULL)
532		fList->Unlock();
533	if (fLastList != NULL)
534		fLastList->Unlock();
535
536	delete fIterator;
537}
538
539
540BNetworkCookieJar::Iterator&
541BNetworkCookieJar::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
567bool
568BNetworkCookieJar::Iterator::HasNext() const
569{
570	return fElement;
571}
572
573
574const BNetworkCookie*
575BNetworkCookieJar::Iterator::Next()
576{
577	if (!fElement)
578		return NULL;
579
580	const BNetworkCookie* result = fElement;
581	_FindNext();
582	return result;
583}
584
585
586const BNetworkCookie*
587BNetworkCookieJar::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
623const BNetworkCookie*
624BNetworkCookieJar::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
667void
668BNetworkCookieJar::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
714BNetworkCookieJar::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
730BNetworkCookieJar::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
747BNetworkCookieJar::UrlIterator::~UrlIterator()
748{
749	if (fList != NULL)
750		fList->Unlock();
751	if (fLastList != NULL)
752		fLastList->Unlock();
753
754	delete fIterator;
755}
756
757
758bool
759BNetworkCookieJar::UrlIterator::HasNext() const
760{
761	return fElement;
762}
763
764
765const BNetworkCookie*
766BNetworkCookieJar::UrlIterator::Next()
767{
768	if (!fElement)
769		return NULL;
770
771	const BNetworkCookie* result = fElement;
772	_FindNext();
773	return result;
774}
775
776
777const BNetworkCookie*
778BNetworkCookieJar::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
807BNetworkCookieJar::UrlIterator&
808BNetworkCookieJar::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
837void
838BNetworkCookieJar::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
861bool
862BNetworkCookieJar::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
879void
880BNetworkCookieJar::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
906void
907BNetworkCookieJar::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
927bool
928BNetworkCookieJar::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
947BNetworkCookieList::BNetworkCookieList()
948{
949	pthread_rwlock_init(&fLock, NULL);
950}
951
952
953BNetworkCookieList::~BNetworkCookieList()
954{
955	// Note: this is expected to be called with the write lock held.
956	pthread_rwlock_destroy(&fLock);
957}
958
959
960status_t
961BNetworkCookieList::LockForReading()
962{
963	return pthread_rwlock_rdlock(&fLock);
964}
965
966
967status_t
968BNetworkCookieList::LockForWriting()
969{
970	return pthread_rwlock_wrlock(&fLock);
971}
972
973
974status_t
975BNetworkCookieList::Unlock()
976{
977	return pthread_rwlock_unlock(&fLock);
978}
979
980