1/*
2 * Copyright 2010 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 */
8
9
10#include <new>
11
12#include <Debug.h>
13#include <HashMap.h>
14#include <HashString.h>
15#include <Message.h>
16#include <NetworkCookieJar.h>
17#include "NetworkCookieJarPrivate.h"
18
19const char* kArchivedCookieMessageName	=	"be:cookie";
20
21
22BNetworkCookieJar::BNetworkCookieJar()
23	:
24	fCookieHashMap(new PrivateHashMap)
25{
26}
27
28
29BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieJar&)
30	:
31	BArchivable(),
32	fCookieHashMap(new PrivateHashMap)
33{
34	// TODO
35}
36
37
38BNetworkCookieJar::BNetworkCookieJar(const BNetworkCookieList& otherList)
39	:
40	fCookieHashMap(new PrivateHashMap)
41{
42	AddCookies(otherList);
43}
44
45
46BNetworkCookieJar::BNetworkCookieJar(BMessage* archive)
47	:
48	fCookieHashMap(new PrivateHashMap)
49{
50	BMessage extractedCookie;
51
52	for (int32 i = 0;
53		archive->FindMessage(kArchivedCookieMessageName, i, &extractedCookie)
54			== B_OK;
55		i++) {
56		BNetworkCookie* heapCookie
57			= new(std::nothrow) BNetworkCookie(&extractedCookie);
58
59		if (heapCookie == NULL || !AddCookie(heapCookie))
60			break;
61	}
62}
63
64
65BNetworkCookieJar::~BNetworkCookieJar()
66{
67	BNetworkCookie* cookiePtr;
68
69	for (Iterator it(GetIterator()); (cookiePtr = it.Next()); )
70		delete it.Remove();
71}
72
73
74// #pragma mark Add cookie to cookie jar
75
76
77bool
78BNetworkCookieJar::AddCookie(const BNetworkCookie& cookie)
79{
80	BNetworkCookie* heapCookie = new(std::nothrow) BNetworkCookie(cookie);
81
82	if (!AddCookie(heapCookie)) {
83		delete heapCookie;
84		return false;
85	}
86
87	return true;
88}
89
90
91bool
92BNetworkCookieJar::AddCookie(BNetworkCookie* cookie)
93{
94	if (cookie != NULL) {
95		HashString key(cookie->Domain());
96
97		if (!fCookieHashMap->fHashMap.ContainsKey(key))
98			fCookieHashMap->fHashMap.Put(key, new BList);
99
100		BNetworkCookieList* list = fCookieHashMap->fHashMap.Get(key);
101
102		for (int32 i = 0; i < list->CountItems(); i++) {
103			BNetworkCookie* c
104				= reinterpret_cast<BNetworkCookie*>(list->ItemAt(i));
105
106			if (c->Name() == cookie->Name()) {
107				list->RemoveItem(i);
108				break;
109			}
110		}
111
112		// Discard the cookie if it's to be deleted
113		if (!cookie->ShouldDeleteNow())
114			list->AddItem(cookie);
115	}
116
117	return true;
118}
119
120
121bool
122BNetworkCookieJar::AddCookies(const BNetworkCookieList& cookies)
123{
124	for (int32 i = 0; i < cookies.CountItems(); i++) {
125		BNetworkCookie* cookiePtr
126			= reinterpret_cast<BNetworkCookie*>(cookies.ItemAt(i));
127
128		// Using AddCookie by reference in order to avoid multiple
129		// cookie jar share the same cookie pointers
130		if (!AddCookie(*cookiePtr))
131			return false;
132	}
133
134	return true;
135}
136
137
138// #pragma mark Purge useless cookies
139
140
141uint32
142BNetworkCookieJar::DeleteOutdatedCookies()
143{
144	int32 deleteCount = 0;
145	BNetworkCookie* cookiePtr;
146
147	for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) {
148		if (cookiePtr->ShouldDeleteNow()) {
149			delete it.Remove();
150			deleteCount++;
151		}
152	}
153
154	return deleteCount;
155}
156
157
158uint32
159BNetworkCookieJar::PurgeForExit()
160{
161	int32 deleteCount = 0;
162	BNetworkCookie* cookiePtr;
163
164	for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) {
165		if (cookiePtr->ShouldDeleteAtExit()) {
166			delete it.Remove();
167			deleteCount++;
168		}
169	}
170
171	return deleteCount;
172}
173
174
175// #pragma mark BArchivable interface
176
177
178status_t
179BNetworkCookieJar::Archive(BMessage* into, bool deep) const
180{
181	status_t error = BArchivable::Archive(into, deep);
182
183	if (error == B_OK) {
184		BNetworkCookie* cookiePtr;
185
186		for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) {
187			BMessage subArchive;
188
189			error = cookiePtr->Archive(&subArchive, deep);
190			if (error != B_OK)
191				return error;
192
193			error = into->AddMessage(kArchivedCookieMessageName, &subArchive);
194			if (error != B_OK)
195				return error;
196		}
197	}
198
199	return error;
200}
201
202
203BArchivable*
204BNetworkCookieJar::Instantiate(BMessage* archive)
205{
206	if (archive->HasMessage(kArchivedCookieMessageName))
207		return new(std::nothrow) BNetworkCookieJar(archive);
208
209	return NULL;
210}
211
212
213// #pragma mark BFlattenable interface
214
215
216bool
217BNetworkCookieJar::IsFixedSize() const
218{
219	// Flattened size vary
220	return false;
221}
222
223
224type_code
225BNetworkCookieJar::TypeCode() const
226{
227	// TODO: Add a B_COOKIEJAR_TYPE
228	return B_ANY_TYPE;
229}
230
231
232ssize_t
233BNetworkCookieJar::FlattenedSize() const
234{
235	_DoFlatten();
236	return fFlattened.Length() + 1;
237}
238
239
240status_t
241BNetworkCookieJar::Flatten(void* buffer, ssize_t size) const
242{
243	if (FlattenedSize() > size)
244		return B_ERROR;
245
246	fFlattened.CopyInto(reinterpret_cast<char*>(buffer), 0,
247		fFlattened.Length());
248	reinterpret_cast<char*>(buffer)[fFlattened.Length()] = 0;
249
250	return B_OK;
251}
252
253
254bool
255BNetworkCookieJar::AllowsTypeCode(type_code) const
256{
257	// TODO
258	return false;
259}
260
261
262status_t
263BNetworkCookieJar::Unflatten(type_code, const void* buffer, ssize_t size)
264{
265	BString flattenedCookies;
266	flattenedCookies.SetTo(reinterpret_cast<const char*>(buffer), size);
267
268	while (flattenedCookies.Length() > 0) {
269		BNetworkCookie tempCookie;
270		BString tempCookieLine;
271
272		int32 endOfLine = flattenedCookies.FindFirst('\n', 0);
273		if (endOfLine == -1)
274			tempCookieLine = flattenedCookies;
275		else {
276			flattenedCookies.MoveInto(tempCookieLine, 0, endOfLine);
277			flattenedCookies.Remove(0, 1);
278		}
279
280		if (tempCookieLine.Length() != 0 && tempCookieLine[0] != '#') {
281			for (int32 field = 0; field < 7; field++) {
282				BString tempString;
283
284				int32 endOfField = tempCookieLine.FindFirst('\t', 0);
285				if (endOfField == -1)
286					tempString = tempCookieLine;
287				else {
288					tempCookieLine.MoveInto(tempString, 0, endOfField);
289					tempCookieLine.Remove(0, 1);
290				}
291
292				switch (field) {
293					case 0:
294						tempCookie.SetDomain(tempString);
295						break;
296
297					case 1:
298						// TODO: Useless field ATM
299						break;
300
301					case 2:
302						tempCookie.SetPath(tempString);
303						break;
304
305					case 3:
306						tempCookie.SetSecure(tempString == "TRUE");
307						break;
308
309					case 4:
310						tempCookie.SetExpirationDate(atoi(tempString));
311						break;
312
313					case 5:
314						tempCookie.SetName(tempString);
315						break;
316
317					case 6:
318						tempCookie.SetValue(tempString);
319						break;
320				} // switch
321			} // for loop
322
323			AddCookie(tempCookie);
324		}
325	}
326
327	return B_OK;
328}
329
330
331// #pragma mark Iterators
332
333
334BNetworkCookieJar::Iterator
335BNetworkCookieJar::GetIterator() const
336{
337	return BNetworkCookieJar::Iterator(this);
338}
339
340
341BNetworkCookieJar::UrlIterator
342BNetworkCookieJar::GetUrlIterator(const BUrl& url) const
343{
344	if (!url.HasPath()) {
345		BUrl copy(url);
346		copy.SetPath("/");
347		return BNetworkCookieJar::UrlIterator(this, copy);
348	}
349
350	return BNetworkCookieJar::UrlIterator(this, url);
351}
352
353
354void
355BNetworkCookieJar::_DoFlatten() const
356{
357	fFlattened.Truncate(0);
358
359	BNetworkCookie* cookiePtr;
360	for (Iterator it(GetIterator()); (cookiePtr = it.Next()); ) {
361		fFlattened 	<< cookiePtr->Domain() << '\t' << "TRUE" << '\t'
362			<< cookiePtr->Path() << '\t'
363			<< (cookiePtr->Secure()?"TRUE":"FALSE") << '\t'
364			<< (int32)cookiePtr->ExpirationDate() << '\t'
365			<< cookiePtr->Name() << '\t' << cookiePtr->Value() << '\n';
366	}
367}
368
369
370// #pragma mark Iterator
371
372
373BNetworkCookieJar::Iterator::Iterator(const Iterator& other)
374	:
375	fCookieJar(other.fCookieJar),
376	fIterator(other.fIterator),
377	fLastList(other.fLastList),
378	fList(other.fList),
379	fElement(other.fElement),
380	fLastElement(other.fLastElement),
381	fIndex(other.fIndex)
382{
383}
384
385
386BNetworkCookieJar::Iterator::Iterator(const BNetworkCookieJar* cookieJar)
387	:
388	fCookieJar(const_cast<BNetworkCookieJar*>(cookieJar)),
389	fIterator(NULL),
390	fLastList(NULL),
391	fList(NULL),
392	fElement(NULL),
393	fLastElement(NULL),
394	fIndex(0)
395{
396	fIterator = new(std::nothrow) PrivateIterator(
397		fCookieJar->fCookieHashMap->fHashMap.GetIterator());
398
399	// Locate first cookie
400	_FindNext();
401}
402
403
404BNetworkCookieJar::Iterator::~Iterator()
405{
406	delete fIterator;
407}
408
409
410bool
411BNetworkCookieJar::Iterator::HasNext() const
412{
413	return fElement;
414}
415
416
417BNetworkCookie*
418BNetworkCookieJar::Iterator::Next()
419{
420	if (!fElement)
421		return NULL;
422
423	BNetworkCookie* result = fElement;
424	_FindNext();
425	return result;
426}
427
428
429BNetworkCookie*
430BNetworkCookieJar::Iterator::NextDomain()
431{
432	if (!fElement)
433		return NULL;
434
435	BNetworkCookie* result = fElement;
436
437	if (!fIterator->fCookieMapIterator.HasNext()) {
438		fElement = NULL;
439		return NULL;
440	}
441
442	fList = *(fIterator->fCookieMapIterator.NextValue());
443	fIndex = 0;
444	fElement = reinterpret_cast<BNetworkCookie*>(fList->ItemAt(fIndex));
445
446	return result;
447}
448
449
450BNetworkCookie*
451BNetworkCookieJar::Iterator::Remove()
452{
453	if (!fLastElement)
454		return NULL;
455
456	BNetworkCookie* result = fLastElement;
457
458	if (fIndex == 0) {
459		if (fLastList->CountItems() == 1) {
460			fIterator->fCookieMapIterator.Remove();
461			delete fLastList;
462		}
463		else
464			fLastList->RemoveItem(fLastList->CountItems() - 1);
465	} else {
466		fList->RemoveItem(fIndex-1);
467		fIndex--;
468	}
469
470	fLastElement = NULL;
471	return result;
472}
473
474
475BNetworkCookieJar::Iterator&
476BNetworkCookieJar::Iterator::operator=(const BNetworkCookieJar::Iterator& other)
477{
478	fCookieJar = other.fCookieJar;
479	fIterator = other.fIterator;
480	fLastList = other.fLastList;
481	fList = other.fList;
482	fElement = other.fElement;
483	fLastElement = other.fLastElement;
484	fIndex = other.fIndex;
485	return *this;
486}
487
488
489void
490BNetworkCookieJar::Iterator::_FindNext()
491{
492	fLastElement = fElement;
493
494	fIndex++;
495	if (fList && fIndex < fList->CountItems()) {
496		fElement = reinterpret_cast<BNetworkCookie*>(fList->ItemAt(fIndex));
497		return;
498	}
499
500	if (!fIterator->fCookieMapIterator.HasNext()) {
501		fElement = NULL;
502		return;
503	}
504
505	fLastList = fList;
506	fList = *(fIterator->fCookieMapIterator.NextValue());
507	fIndex = 0;
508	fElement = reinterpret_cast<BNetworkCookie*>(fList->ItemAt(fIndex));
509}
510
511
512// #pragma mark URL Iterator
513
514
515BNetworkCookieJar::UrlIterator::UrlIterator(const UrlIterator& other)
516{
517	*this = other;
518}
519
520
521BNetworkCookieJar::UrlIterator::UrlIterator(const BNetworkCookieJar* cookieJar,
522	const BUrl& url)
523	:
524	fCookieJar(const_cast<BNetworkCookieJar*>(cookieJar)),
525	fIterator(NULL),
526	fList(NULL),
527	fLastList(NULL),
528	fElement(NULL),
529	fLastElement(NULL),
530	fIndex(0),
531	fLastIndex(0),
532	fUrl(const_cast<BUrl&>(url))
533{
534	BString domain(url.Host());
535
536	if (!domain.Length())
537		return;
538
539	if (domain[0] != '.')
540		domain.Prepend(".");
541
542	//  Prepending another dot since _FindNext is going to
543	// call _SupDomain()
544	domain.Prepend(".");
545
546	fIterator = new(std::nothrow) PrivateIterator(
547		fCookieJar->fCookieHashMap->fHashMap.GetIterator());
548	fIterator->fKey.SetTo(domain, domain.Length());
549
550	_FindNext();
551}
552
553
554BNetworkCookieJar::UrlIterator::~UrlIterator()
555{
556	delete fIterator;
557}
558
559
560bool
561BNetworkCookieJar::UrlIterator::HasNext() const
562{
563	return fElement;
564}
565
566
567BNetworkCookie*
568BNetworkCookieJar::UrlIterator::Next()
569{
570	if (!fElement)
571		return NULL;
572
573	BNetworkCookie* result = fElement;
574	_FindNext();
575	return result;
576}
577
578
579BNetworkCookie*
580BNetworkCookieJar::UrlIterator::Remove()
581{
582	if (!fLastElement)
583		return NULL;
584
585	BNetworkCookie* result = fLastElement;
586
587	fLastList->RemoveItem(fLastIndex);
588
589	if (fLastList->CountItems() == 0) {
590		HashString lastKey(fLastElement->Domain(),
591			fLastElement->Domain().Length());
592
593		delete fCookieJar->fCookieHashMap->fHashMap.Remove(lastKey);
594	}
595
596	fLastElement = NULL;
597	return result;
598}
599
600
601BNetworkCookieJar::UrlIterator&
602BNetworkCookieJar::UrlIterator::operator=(
603	const BNetworkCookieJar::UrlIterator& other)
604{
605	fCookieJar = other.fCookieJar;
606	fList = other.fList;
607	fLastList = other.fLastList;
608	fElement = other.fElement;
609	fLastElement = other.fLastElement;
610	fIndex = other.fIndex;
611	fLastIndex = other.fLastIndex;
612	fUrl = other.fUrl;
613	fIterator = other.fIterator;
614	return *this;
615}
616
617
618bool
619BNetworkCookieJar::UrlIterator::_SupDomain()
620{
621	BString domain(fIterator->fKey.GetString());
622	int32 nextDot = domain.FindFirst('.', 1);
623
624	if (nextDot == -1)
625		return false;
626
627	domain.Remove(0, nextDot);
628	fIterator->fKey.SetTo(domain.String(), domain.Length());
629	return true;
630}
631
632
633void
634BNetworkCookieJar::UrlIterator::_FindNext()
635{
636	fLastIndex = fIndex;
637	fLastElement = fElement;
638
639	if (_FindPath())
640		return;
641
642	fLastList = fList;
643	do {
644		if (!_SupDomain()) {
645			fElement = NULL;
646			return;
647		}
648
649		_FindDomain();
650	} while (!_FindPath());
651}
652
653
654void
655BNetworkCookieJar::UrlIterator::_FindDomain()
656{
657	fList = fCookieJar->fCookieHashMap->fHashMap.Get(fIterator->fKey);
658
659	if (fList == NULL)
660		fElement = NULL;
661
662	fIndex = -1;
663}
664
665
666bool
667BNetworkCookieJar::UrlIterator::_FindPath()
668{
669	fIndex++;
670	if (fList && fIndex < fList->CountItems()) {
671		do {
672			fElement
673				= reinterpret_cast<BNetworkCookie*>(fList->ItemAt(fIndex));
674
675			if (fElement->IsValidForPath(fUrl.Path()))
676				return true;
677
678			fIndex++;
679		} while (fList && fIndex < fList->CountItems());
680	}
681
682	return false;
683}
684