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 <cstdlib>
11#include <ctime>
12#include <new>
13
14#include <HttpTime.h>
15#include <NetworkCookie.h>
16
17#include <cstdio>
18#define PRINT(x) printf x;
19
20using BPrivate::BHttpTime;
21
22static const char* 	kArchivedCookieComment 			= "be:cookie.comment";
23static const char* 	kArchivedCookieCommentUrl		= "be:cookie.commenturl";
24static const char* 	kArchivedCookieDiscard			= "be:cookie.discard";
25static const char* 	kArchivedCookieDomain 			= "be:cookie.domain";
26static const char* 	kArchivedCookieExpirationDate	= "be:cookie.expiredate";
27static const char* 	kArchivedCookiePath 			= "be:cookie.path";
28static const char* 	kArchivedCookieSecure 			= "be:cookie.secure";
29static const char* 	kArchivedCookieVersion 			= "be:cookie.version";
30static const char* 	kArchivedCookieName 			= "be:cookie.name";
31static const char* 	kArchivedCookieValue 			= "be:cookie.value";
32
33
34BNetworkCookie::BNetworkCookie(const char* name, const char* value)
35	:
36	fDiscard(false),
37	fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)),
38	fVersion(0),
39	fName(name),
40	fValue(value),
41	fSessionCookie(true)
42{
43	_Reset();
44}
45
46
47BNetworkCookie::BNetworkCookie(const BNetworkCookie& other)
48	:
49	BArchivable(),
50	fDiscard(false),
51	fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)),
52	fVersion(0),
53	fSessionCookie(true)
54{
55	_Reset();
56	*this = other;
57}
58
59
60BNetworkCookie::BNetworkCookie(const BString& cookieString)
61	:
62	fDiscard(false),
63	fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)),
64	fVersion(0),
65	fSessionCookie(true)
66{
67	_Reset();
68	ParseCookieString(cookieString);
69}
70
71
72BNetworkCookie::BNetworkCookie(const BString& cookieString,
73	const BUrl& url)
74	:
75	fDiscard(false),
76	fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)),
77	fVersion(0),
78	fSessionCookie(true)
79{
80	_Reset();
81	ParseCookieStringFromUrl(cookieString, url);
82}
83
84
85BNetworkCookie::BNetworkCookie(BMessage* archive)
86	:
87	fDiscard(false),
88	fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)),
89	fVersion(0),
90	fSessionCookie(true)
91{
92	_Reset();
93
94	archive->FindString(kArchivedCookieName, &fName);
95	archive->FindString(kArchivedCookieValue, &fValue);
96
97	archive->FindString(kArchivedCookieComment, &fComment);
98	archive->FindString(kArchivedCookieCommentUrl, &fCommentUrl);
99	archive->FindString(kArchivedCookieDomain, &fDomain);
100	archive->FindString(kArchivedCookiePath, &fPath);
101	archive->FindBool(kArchivedCookieSecure, &fSecure);
102
103	if (archive->FindBool(kArchivedCookieDiscard, &fDiscard) == B_OK)
104		fHasDiscard = true;
105
106	if (archive->FindInt8(kArchivedCookieVersion, &fVersion) == B_OK)
107		fHasVersion = true;
108
109	int32 expiration;
110	if (archive->FindInt32(kArchivedCookieExpirationDate, &expiration)
111			== B_OK) {
112		SetExpirationDate((time_t)expiration);
113	}
114}
115
116
117BNetworkCookie::BNetworkCookie()
118	:
119	fDiscard(false),
120	fExpiration(BDateTime::CurrentDateTime(B_GMT_TIME)),
121	fPath("/"),
122	fVersion(0),
123	fSessionCookie(true)
124{
125	_Reset();
126}
127
128
129BNetworkCookie::~BNetworkCookie()
130{
131}
132
133
134// #pragma mark String to cookie fields
135
136
137BNetworkCookie&
138BNetworkCookie::ParseCookieStringFromUrl(const BString& string,
139	const BUrl& url)
140{
141	BString cookieString(string);
142	int16 index = 0;
143
144	_Reset();
145
146	// Default values from url
147	SetDomain(url.Host());
148	SetPath(url.Path());
149
150	_ExtractNameValuePair(cookieString, &index);
151
152	while (index < cookieString.Length())
153		_ExtractNameValuePair(cookieString, &index, true);
154
155	return *this;
156}
157
158
159BNetworkCookie&
160BNetworkCookie::ParseCookieString(const BString& string)
161{
162	BUrl url;
163	ParseCookieStringFromUrl(string, url);
164	return *this;
165}
166
167
168// #pragma mark Cookie fields modification
169
170
171BNetworkCookie&
172BNetworkCookie::SetComment(const BString& comment)
173{
174	fComment = comment;
175	fRawFullCookieValid = false;
176	return *this;
177}
178
179
180BNetworkCookie&
181BNetworkCookie::SetCommentUrl(const BString& commentUrl)
182{
183	fCommentUrl = commentUrl;
184	fRawFullCookieValid = false;
185	return *this;
186}
187
188
189BNetworkCookie&
190BNetworkCookie::SetDiscard(bool discard)
191{
192	fDiscard = discard;
193	fHasDiscard = true;
194	fRawFullCookieValid = false;
195	return *this;
196}
197
198
199BNetworkCookie&
200BNetworkCookie::SetDomain(const BString& domain)
201{
202	fDomain = domain;
203
204	//  We always use pre-dotted domains for tail matching
205	if (fDomain.ByteAt(0) != '.')
206		fDomain.Prepend(".");
207
208	fRawFullCookieValid = false;
209	return *this;
210}
211
212
213BNetworkCookie&
214BNetworkCookie::SetMaxAge(int32 maxAge)
215{
216	BDateTime expiration = BDateTime::CurrentDateTime(B_GMT_TIME);
217	expiration.Time().AddSeconds(maxAge);
218	return SetExpirationDate(expiration);
219}
220
221
222BNetworkCookie&
223BNetworkCookie::SetExpirationDate(time_t expireDate)
224{
225	BDateTime expiration;
226	expiration.SetTime_t(expireDate);
227	return SetExpirationDate(expiration);
228}
229
230
231BNetworkCookie&
232BNetworkCookie::SetExpirationDate(BDateTime& expireDate)
233{
234	if (expireDate.Time_t() <= 0) {
235		fExpiration.SetTime_t(0);
236		fSessionCookie = true;
237		fExpirationStringValid = false;
238		fRawFullCookieValid = false;
239		fHasExpirationDate = false;
240	} else {
241		fExpiration = expireDate;
242		fSessionCookie = false;
243		fExpirationStringValid = false;
244		fRawFullCookieValid = false;
245		fHasExpirationDate = true;
246	}
247
248	return *this;
249}
250
251
252BNetworkCookie&
253BNetworkCookie::SetPath(const BString& path)
254{
255	fPath = path;
256	fRawFullCookieValid = false;
257	return *this;
258}
259
260
261BNetworkCookie&
262BNetworkCookie::SetSecure(bool secure)
263{
264	fSecure = secure;
265	fRawFullCookieValid = false;
266	return *this;
267}
268
269
270BNetworkCookie&
271BNetworkCookie::SetVersion(int8 version)
272{
273	fVersion = version;
274	fHasVersion = true;
275	fRawCookieValid = false;
276	return *this;
277}
278
279
280BNetworkCookie&
281BNetworkCookie::SetName(const BString& name)
282{
283	fName = name;
284	fRawFullCookieValid = false;
285	fRawCookieValid = false;
286	return *this;
287}
288
289
290BNetworkCookie&
291BNetworkCookie::SetValue(const BString& value)
292{
293	fValue = value;
294	fRawFullCookieValid = false;
295	fRawCookieValid = false;
296	return *this;
297}
298
299
300// #pragma mark Cookie fields access
301
302
303const BString&
304BNetworkCookie::Comment() const
305{
306	return fComment;
307}
308
309
310const BString&
311BNetworkCookie::CommentUrl() const
312{
313	return fCommentUrl;
314}
315
316
317bool
318BNetworkCookie::Discard() const
319{
320	return fDiscard;
321}
322
323
324const BString&
325BNetworkCookie::Domain() const
326{
327	return fDomain;
328}
329
330
331int32
332BNetworkCookie::MaxAge() const
333{
334	return fExpiration.Time_t() - BDateTime::CurrentDateTime(B_GMT_TIME).Time_t();
335}
336
337
338time_t
339BNetworkCookie::ExpirationDate() const
340{
341	return fExpiration.Time_t();
342}
343
344
345const BString&
346BNetworkCookie::ExpirationString() const
347{
348	BHttpTime date(ExpirationDate());
349
350	if (!fExpirationStringValid) {
351		fExpirationString = date.ToString(BPrivate::B_HTTP_TIME_FORMAT_COOKIE);
352		fExpirationStringValid = true;
353	}
354
355	return fExpirationString;
356}
357
358
359const BString&
360BNetworkCookie::Path() const
361{
362	return fPath;
363}
364
365
366bool
367BNetworkCookie::Secure() const
368{
369	return fSecure;
370}
371
372
373int8
374BNetworkCookie::Version() const
375{
376	return fVersion;
377}
378
379
380const BString&
381BNetworkCookie::Name() const
382{
383	return fName;
384}
385
386
387const BString&
388BNetworkCookie::Value() const
389{
390	return fValue;
391}
392
393
394const BString&
395BNetworkCookie::RawCookie(bool full) const
396{
397	if (full && !fRawFullCookieValid) {
398		fRawFullCookie.Truncate(0);
399		fRawFullCookieValid = true;
400
401		fRawFullCookie << fName << "=" << fValue;
402
403		if (HasCommentUrl())
404			fRawFullCookie << "; Comment-Url=" << fCommentUrl;
405		if (HasComment())
406			fRawFullCookie << "; Comment=" << fComment;
407		if (HasDiscard())
408			fRawFullCookie << "; Discard=" << (fDiscard?"true":"false");
409		if (HasDomain())
410			fRawFullCookie << "; Domain=" << fDomain;
411		if (HasExpirationDate())
412			fRawFullCookie << "; Max-Age=" << MaxAge();
413//			fRawFullCookie << "; Expires=" << ExpirationString();
414		if (HasPath())
415			fRawFullCookie << "; Path=" << fPath;
416		if (Secure() && fSecure)
417			fRawFullCookie << "; Secure=" << (fSecure?"true":"false");
418		if (HasVersion())
419			fRawFullCookie << ", Version=" << fVersion;
420
421	} else if (!full && !fRawCookieValid) {
422		fRawCookie.Truncate(0);
423		fRawCookieValid = true;
424
425		fRawCookie << fName << "=" << fValue;
426	}
427
428	return full?fRawFullCookie:fRawCookie;
429}
430
431
432// #pragma mark Cookie test
433
434
435bool
436BNetworkCookie::IsSessionCookie() const
437{
438	return fSessionCookie;
439}
440
441
442bool
443BNetworkCookie::IsValid(bool strict) const
444{
445	return HasName() && HasValue() && (!strict || HasVersion());
446}
447
448
449bool
450BNetworkCookie::IsValidForUrl(const BUrl& url) const
451{
452	BString urlHost = url.Host();
453	BString urlPath = url.Path();
454
455	return IsValidForDomain(urlHost) && IsValidForPath(urlPath);
456}
457
458
459bool
460BNetworkCookie::IsValidForDomain(const BString& domain) const
461{
462	if (fDomain.Length() > domain.Length())
463		return false;
464
465	return domain.FindLast(fDomain) == (domain.Length() - fDomain.Length());
466}
467
468
469bool
470BNetworkCookie::IsValidForPath(const BString& path) const
471{
472	if (fPath.Length() > path.Length())
473		return false;
474
475	return path.FindFirst(fPath) == 0;
476}
477
478
479// #pragma mark Cookie fields existence tests
480
481
482bool
483BNetworkCookie::HasCommentUrl() const
484{
485	return fCommentUrl.Length() > 0;
486}
487
488
489bool
490BNetworkCookie::HasComment() const
491{
492	return fComment.Length() > 0;
493}
494
495
496bool
497BNetworkCookie::HasDiscard() const
498{
499	return fHasDiscard;
500}
501
502
503bool
504BNetworkCookie::HasDomain() const
505{
506	return fDomain.Length() > 0;
507}
508
509
510bool
511BNetworkCookie::HasPath() const
512{
513	return fPath.Length() > 0;
514}
515
516
517bool
518BNetworkCookie::HasVersion() const
519{
520	return fHasVersion;
521}
522
523
524bool
525BNetworkCookie::HasName() const
526{
527	return fName.Length() > 0;
528}
529
530
531bool
532BNetworkCookie::HasValue() const
533{
534	return fValue.Length() > 0;
535}
536
537
538bool
539BNetworkCookie::HasExpirationDate() const
540{
541	return fHasExpirationDate;
542}
543
544
545// #pragma mark Cookie delete test
546
547
548bool
549BNetworkCookie::ShouldDeleteAtExit() const
550{
551	return (HasDiscard() && Discard())
552		|| (!IsSessionCookie() && ShouldDeleteNow())
553		|| IsSessionCookie();
554}
555
556
557bool
558BNetworkCookie::ShouldDeleteNow() const
559{
560	if (!IsSessionCookie() && HasExpirationDate())
561		return (BDateTime::CurrentDateTime(B_GMT_TIME) > fExpiration);
562
563	return false;
564}
565
566
567// #pragma mark BArchivable members
568
569
570status_t
571BNetworkCookie::Archive(BMessage* into, bool deep) const
572{
573	status_t error = BArchivable::Archive(into, deep);
574
575	if (error != B_OK)
576		return error;
577
578	error = into->AddString(kArchivedCookieName, fName);
579	if (error != B_OK)
580		return error;
581
582	error = into->AddString(kArchivedCookieValue, fValue);
583	if (error != B_OK)
584		return error;
585
586
587	// We add optional fields only if they're defined
588	if (HasComment()) {
589		error = into->AddString(kArchivedCookieComment, fComment);
590		if (error != B_OK)
591			return error;
592	}
593
594	if (HasCommentUrl()) {
595		error = into->AddString(kArchivedCookieCommentUrl, fCommentUrl);
596		if (error != B_OK)
597			return error;
598	}
599
600	if (HasDiscard()) {
601		error = into->AddBool(kArchivedCookieDiscard, fDiscard);
602		if (error != B_OK)
603			return error;
604	}
605
606	if (HasDomain()) {
607		error = into->AddString(kArchivedCookieDomain, fDomain);
608		if (error != B_OK)
609			return error;
610	}
611
612	if (fHasExpirationDate) {
613		error = into->AddInt32(kArchivedCookieExpirationDate,
614			fExpiration.Time_t());
615		if (error != B_OK)
616			return error;
617	}
618
619	if (HasPath()) {
620		error = into->AddString(kArchivedCookiePath, fPath);
621		if (error != B_OK)
622			return error;
623	}
624
625	if (Secure()) {
626		error = into->AddBool(kArchivedCookieSecure, fSecure);
627		if (error != B_OK)
628			return error;
629	}
630
631	if (HasVersion()) {
632		error = into->AddInt8(kArchivedCookieVersion, fVersion);
633		if (error != B_OK)
634			return error;
635	}
636
637	return B_OK;
638}
639
640
641/*static*/ BArchivable*
642BNetworkCookie::Instantiate(BMessage* archive)
643{
644	if (archive->HasString(kArchivedCookieName)
645		&& archive->HasString(kArchivedCookieValue))
646		return new(std::nothrow) BNetworkCookie(archive);
647
648	return NULL;
649}
650
651
652// #pragma mark Overloaded operators
653
654
655BNetworkCookie&
656BNetworkCookie::operator=(const BNetworkCookie& other)
657{
658	// Should we prefer to discard the cache ?
659	fRawCookie					= other.fRawCookie;
660	fRawCookieValid				= other.fRawCookieValid;
661	fRawFullCookie				= other.fRawFullCookie;
662	fRawFullCookieValid			= other.fRawFullCookieValid;
663	fExpirationString			= other.fExpirationString;
664	fExpirationStringValid		= other.fExpirationStringValid;
665
666	fComment					= other.fComment;
667	fCommentUrl					= other.fCommentUrl;
668	fDiscard					= other.fDiscard;
669	fDomain						= other.fDomain;
670	fExpiration					= other.fExpiration;
671	fPath						= other.fPath;
672	fSecure						= other.fSecure;
673	fVersion					= other.fVersion;
674	fName						= other.fName;
675	fValue						= other.fValue;
676
677	fHasDiscard					= other.fHasDiscard;
678	fHasExpirationDate			= other.fHasExpirationDate;
679	fSessionCookie				= other.fSessionCookie;
680	fHasVersion					= other.fHasVersion;
681
682	return *this;
683}
684
685
686BNetworkCookie&
687BNetworkCookie::operator=(const char* string)
688{
689	return ParseCookieString(string);
690}
691
692
693bool
694BNetworkCookie::operator==(const BNetworkCookie& other)
695{
696	// Equality : name and values equals
697	return fName == other.fName && fValue == other.fValue;
698}
699
700
701bool
702BNetworkCookie::operator!=(const BNetworkCookie& other)
703{
704	return !(*this == other);
705}
706
707
708void
709BNetworkCookie::_Reset()
710{
711	fComment.Truncate(0);
712	fCommentUrl.Truncate(0);
713	fDomain.Truncate(0);
714	fPath.Truncate(0);
715	fName.Truncate(0);
716	fValue.Truncate(0);
717	fDiscard 				= false;
718	fSecure 				= false;
719	fVersion 				= 0;
720	fExpiration 			= 0;
721
722	fHasDiscard 			= false;
723	fHasExpirationDate 		= false;
724	fSessionCookie 			= true;
725	fHasVersion 			= false;
726
727	fRawCookieValid 		= false;
728	fRawFullCookieValid 	= false;
729	fExpirationStringValid 	= false;
730}
731
732
733void
734BNetworkCookie::_ExtractNameValuePair(const BString& cookieString,
735	int16* index, bool parseField)
736{
737	// Skip whitespaces
738	while (cookieString.ByteAt(*index) == ' '
739		&& *index < cookieString.Length())
740		(*index)++;
741
742	if (*index >= cookieString.Length())
743		return;
744
745
746	// Look for a name=value pair
747	int16 firstSemiColon = cookieString.FindFirst(";", *index);
748	int16 firstEqual = cookieString.FindFirst("=", *index);
749
750	BString name;
751	BString value;
752
753	if (firstSemiColon == -1) {
754		if (firstEqual != -1) {
755			cookieString.CopyInto(name, *index, firstEqual - *index);
756			cookieString.CopyInto(value, firstEqual + 1,
757				cookieString.Length() - firstEqual - 1);
758		} else
759			cookieString.CopyInto(value, *index,
760				cookieString.Length() - *index);
761
762		*index = cookieString.Length() + 1;
763	} else {
764		if (firstEqual != -1 && firstEqual < firstSemiColon) {
765			cookieString.CopyInto(name, *index, firstEqual - *index);
766			cookieString.CopyInto(value, firstEqual + 1,
767				firstSemiColon - firstEqual - 1);
768		} else
769			cookieString.CopyInto(value, *index, firstSemiColon - *index);
770
771		*index = firstSemiColon + 1;
772	}
773
774	// Cookie name/value pair
775	if (!parseField) {
776		SetName(name);
777		SetValue(value);
778		return;
779	}
780
781	name.ToLower();
782	name.Trim();
783	value.Trim();
784
785	// Cookie comment
786	if (name == "comment")
787		SetComment(value);
788	// Cookie comment URL
789	else if (name == "comment-url")
790		SetCommentUrl(value);
791	// Cookie discard flag
792	else if (name == "discard")
793		SetDiscard(value.Length() == 0 || value.ToLower() == "true");
794	// Cookie max-age
795	else if (name == "maxage")
796		SetMaxAge(atoi(value.String()));
797	// Cookie expiration date
798	else if (name == "expires") {
799		BHttpTime date(value);
800		SetExpirationDate(date.Parse());
801	// Cookie valid domain
802	} else if (name == "domain")
803		SetDomain(value);
804	// Cookie valid path
805	else if (name == "path")
806		SetPath(value);
807	// Cookie secure flag
808	else if (name == "secure")
809		SetSecure(value.Length() == 0 || value.ToLower() == "true");
810	// Cookie version
811	else if (name == "version")
812		SetVersion(atoi(value.String()));
813}
814