1/*
2 * Copyright 2010-2016 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 *		Adrien Destugues, pulkomandy@gmail.com
8 */
9
10#include <HttpTime.h>
11
12#include <new>
13
14#include <cstdio>
15#include <locale.h>
16
17using namespace BPrivate::Network;
18
19
20// The formats used should be, in order of preference (according to RFC2616,
21// section 3.3):
22// RFC1123 / RFC822: "Sun, 06 Nov 1994 08:49:37 GMT"
23// RFC1036 / RFC850: "Sunday, 06-Nov-94 08:49:37 GMT"
24// asctime         : "Sun Nov  6 08:49:37 1994"
25//
26// RFC1123 is the preferred one because it has 4 digit years.
27//
28// But of course in real life, all possible mixes of the formats are used.
29// Believe it or not, it's even possible to find some website that gets this
30// right and use one of the 3 formats above.
31// Often seen variants are:
32// - RFC1036 but with 4 digit year,
33// - Missing or different timezone indicator
34// - Invalid weekday
35static const char* kDateFormats[] = {
36	// RFC1123
37	"%a, %d %b %Y %H:%M:%S",     // without timezone
38	"%a, %d %b %Y %H:%M:%S GMT", // canonical
39
40	// RFC1036
41	"%A, %d-%b-%y %H:%M:%S",     // without timezone
42	"%A, %d-%b-%y %H:%M:%S GMT", // canonical
43
44	// RFC1036 with 4 digit year
45	"%a, %d-%b-%Y %H:%M:%S",     // without timezone
46	"%a, %d-%b-%Y %H:%M:%S GMT", // with 4-digit year
47	"%a, %d-%b-%Y %H:%M:%S UTC", // "UTC" timezone
48
49	// asctime
50	"%a %d %b %H:%M:%S %Y"
51};
52
53
54static locale_t posix = newlocale(LC_ALL_MASK, "POSIX", (locale_t)0);
55
56
57BHttpTime::BHttpTime()
58	:
59	fDate(0),
60	fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
61{
62}
63
64
65BHttpTime::BHttpTime(BDateTime date)
66	:
67	fDate(date),
68	fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
69{
70}
71
72
73BHttpTime::BHttpTime(const BString& dateString)
74	:
75	fDateString(dateString),
76	fDate(0),
77	fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
78{
79}
80
81
82// #pragma mark Date modification
83
84
85void
86BHttpTime::SetString(const BString& string)
87{
88	fDateString = string;
89}
90
91
92void
93BHttpTime::SetDate(BDateTime date)
94{
95	fDate = date;
96}
97
98
99// #pragma mark Date conversion
100
101
102BDateTime
103BHttpTime::Parse()
104{
105	struct tm expireTime;
106
107	if (fDateString.Length() < 4)
108		return 0;
109
110	memset(&expireTime, 0, sizeof(struct tm));
111
112	// Save the current locale, switch to POSIX for strptime to match strings
113	// in English, switch back when we're done.
114	locale_t current = uselocale(posix);
115
116	fDateFormat = B_HTTP_TIME_FORMAT_PARSED;
117	unsigned int i;
118	for (i = 0; i < sizeof(kDateFormats) / sizeof(const char*);
119		i++) {
120		const char* result = strptime(fDateString.String(), kDateFormats[i],
121			&expireTime);
122
123		// We need to parse the complete value for the "Expires" key.
124		// Otherwise, we consider this to be a session cookie (or try another
125		// one of the date formats).
126		if (result == fDateString.String() + fDateString.Length()) {
127			fDateFormat = i;
128			break;
129		}
130	}
131
132	uselocale(current);
133
134	// Did we identify some valid format?
135	if (fDateFormat == B_HTTP_TIME_FORMAT_PARSED)
136		return 0;
137
138	// Now convert the struct tm from strptime into a BDateTime.
139	BTime time(expireTime.tm_hour, expireTime.tm_min, expireTime.tm_sec);
140	BDate date(expireTime.tm_year + 1900, expireTime.tm_mon + 1,
141		expireTime.tm_mday);
142	BDateTime dateTime(date, time);
143	return dateTime;
144}
145
146
147BString
148BHttpTime::ToString(int8 format)
149{
150	BString expirationFinal;
151	struct tm expirationTm;
152	expirationTm.tm_sec = fDate.Time().Second();
153	expirationTm.tm_min = fDate.Time().Minute();
154	expirationTm.tm_hour = fDate.Time().Hour();
155	expirationTm.tm_mday = fDate.Date().Day();
156	expirationTm.tm_mon = fDate.Date().Month() - 1;
157	expirationTm.tm_year = fDate.Date().Year() - 1900;
158	// strftime starts weekday count at 0 for Sunday,
159	// while DayOfWeek starts at 1 for Monday and thus uses 7 for Sunday
160	expirationTm.tm_wday = fDate.Date().DayOfWeek() % 7;
161	expirationTm.tm_yday = 0;
162	expirationTm.tm_isdst = 0;
163
164	if (format == B_HTTP_TIME_FORMAT_PARSED)
165		format = fDateFormat;
166
167	if (format != B_HTTP_TIME_FORMAT_PARSED) {
168		static const uint16 kTimetToStringMaxLength = 128;
169		char expirationString[kTimetToStringMaxLength + 1];
170		size_t strLength;
171
172		strLength = strftime(expirationString, kTimetToStringMaxLength,
173			kDateFormats[format], &expirationTm);
174
175		expirationFinal.SetTo(expirationString, strLength);
176	}
177	return expirationFinal;
178}
179