xref: /haiku/src/kits/network/libnetservices/HttpTime.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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 
17 using 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
35 static 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 
54 static locale_t posix = newlocale(LC_ALL_MASK, "POSIX", (locale_t)0);
55 
56 
57 BHttpTime::BHttpTime()
58 	:
59 	fDate(0),
60 	fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
61 {
62 }
63 
64 
65 BHttpTime::BHttpTime(BDateTime date)
66 	:
67 	fDate(date),
68 	fDateFormat(B_HTTP_TIME_FORMAT_PREFERRED)
69 {
70 }
71 
72 
73 BHttpTime::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 
85 void
86 BHttpTime::SetString(const BString& string)
87 {
88 	fDateString = string;
89 }
90 
91 
92 void
93 BHttpTime::SetDate(BDateTime date)
94 {
95 	fDate = date;
96 }
97 
98 
99 // #pragma mark Date conversion
100 
101 
102 BDateTime
103 BHttpTime::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 
147 BString
148 BHttpTime::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