xref: /haiku/src/kits/network/libnetservices2/HttpTime.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
1 /*
2  * Copyright 2010-2022 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  *		Niels Sascha Reedijk, niels.reedijk@gmail.com
9  */
10 
11 #include <HttpTime.h>
12 
13 #include <list>
14 #include <new>
15 
16 #include <cstdio>
17 
18 using namespace BPrivate::Network;
19 
20 
21 // The formats used should be, in order of preference (according to RFC2616,
22 // section 3.3):
23 // RFC1123 / RFC822: "Sun, 06 Nov 1994 08:49:37 GMT"
24 // RFC850          : "Sunday, 06-Nov-94 08:49:37 GMT" (obsoleted by RFC 1036)
25 // asctime         : "Sun Nov 6 08:49:37 1994"
26 //
27 // RFC1123 is the preferred one because it has 4 digit years.
28 //
29 // But of course in real life, all possible mixes of the formats are used.
30 // Believe it or not, it's even possible to find some website that gets this
31 // right and use one of the 3 formats above.
32 // Often seen variants are:
33 // - RFC1036 but with 4 digit year,
34 // - Missing or different timezone indicator
35 // - Invalid weekday
36 static const std::list<std::pair<BHttpTimeFormat, const char*>> kDateFormats = {
37 	// RFC822
38 	{BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S GMT"}, // canonical
39 	{BHttpTimeFormat::RFC1123, "%a, %d %b %Y %H:%M:%S"}, // without timezone
40 	// Standard RFC850
41 	{BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S GMT"}, // canonical
42 	{BHttpTimeFormat::RFC850, "%A, %d-%b-%y %H:%M:%S"}, // without timezone
43 	// RFC 850 with 4 digit year
44 	{BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S"}, // without timezone
45 	{BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S GMT"}, // with 4-digit year
46 	{BHttpTimeFormat::RFC850, "%a, %d-%b-%Y %H:%M:%S UTC"}, // "UTC" timezone
47 	// asctime
48 	{BHttpTimeFormat::AscTime, "%a %b %e %H:%M:%S %Y"},
49 };
50 
51 
52 // #pragma mark BHttpTime::InvalidInput
53 
54 
55 BHttpTime::InvalidInput::InvalidInput(const char* origin, BString input)
56 	:
57 	BError(origin),
58 	input(std::move(input))
59 {
60 }
61 
62 
63 const char*
64 BHttpTime::InvalidInput::Message() const noexcept
65 {
66 	if (input.IsEmpty())
67 		return "A HTTP timestamp cannot be empty";
68 	else
69 		return "The HTTP timestamp string does not match the expected format";
70 }
71 
72 
73 BString
74 BHttpTime::InvalidInput::DebugMessage() const
75 {
76 	BString output = BError::DebugMessage();
77 	if (!input.IsEmpty())
78 		output << ":\t " << input << "\n";
79 	return output;
80 }
81 
82 
83 // #pragma mark BHttpTime
84 
85 
86 BHttpTime::BHttpTime() noexcept
87 	:
88 	fDate(BDateTime::CurrentDateTime(B_GMT_TIME)),
89 	fDateFormat(BHttpTimeFormat::RFC1123)
90 {
91 }
92 
93 
94 BHttpTime::BHttpTime(BDateTime date)
95 	:
96 	fDate(date),
97 	fDateFormat(BHttpTimeFormat::RFC1123)
98 {
99 	if (!fDate.IsValid())
100 		throw InvalidInput(__PRETTY_FUNCTION__, "Invalid BDateTime object");
101 }
102 
103 
104 BHttpTime::BHttpTime(const BString& dateString)
105 	:
106 	fDate(0),
107 	fDateFormat(BHttpTimeFormat::RFC1123)
108 {
109 	_Parse(dateString);
110 }
111 
112 
113 // #pragma mark Date modification
114 
115 
116 void
117 BHttpTime::SetTo(const BString& string)
118 {
119 	_Parse(string);
120 }
121 
122 
123 void
124 BHttpTime::SetTo(BDateTime date)
125 {
126 	if (!date.IsValid())
127 		throw InvalidInput(__PRETTY_FUNCTION__, "Invalid BDateTime object");
128 
129 	fDate = date;
130 	fDateFormat = BHttpTimeFormat::RFC1123;
131 }
132 
133 
134 // #pragma mark Date Access
135 
136 
137 BDateTime
138 BHttpTime::DateTime() const noexcept
139 {
140 	return fDate;
141 }
142 
143 
144 BHttpTimeFormat
145 BHttpTime::DateTimeFormat() const noexcept
146 {
147 	return fDateFormat;
148 }
149 
150 
151 BString
152 BHttpTime::ToString(BHttpTimeFormat outputFormat) const
153 {
154 	BString expirationFinal;
155 	struct tm expirationTm = {};
156 	expirationTm.tm_sec = fDate.Time().Second();
157 	expirationTm.tm_min = fDate.Time().Minute();
158 	expirationTm.tm_hour = fDate.Time().Hour();
159 	expirationTm.tm_mday = fDate.Date().Day();
160 	expirationTm.tm_mon = fDate.Date().Month() - 1;
161 	expirationTm.tm_year = fDate.Date().Year() - 1900;
162 	// strftime starts weekday count at 0 for Sunday,
163 	// while DayOfWeek starts at 1 for Monday and thus uses 7 for Sunday
164 	expirationTm.tm_wday = fDate.Date().DayOfWeek() % 7;
165 	expirationTm.tm_yday = 0;
166 	expirationTm.tm_isdst = 0;
167 
168 	for (auto& [format, formatString]: kDateFormats) {
169 		if (format != outputFormat)
170 			continue;
171 
172 		static const uint16 kTimetToStringMaxLength = 128;
173 		char expirationString[kTimetToStringMaxLength + 1];
174 		size_t strLength;
175 
176 		strLength
177 			= strftime(expirationString, kTimetToStringMaxLength, formatString, &expirationTm);
178 
179 		expirationFinal.SetTo(expirationString, strLength);
180 		break;
181 	}
182 
183 	return expirationFinal;
184 }
185 
186 
187 void
188 BHttpTime::_Parse(const BString& dateString)
189 {
190 	if (dateString.Length() < 4)
191 		throw InvalidInput(__PRETTY_FUNCTION__, dateString);
192 
193 	struct tm expireTime = {};
194 
195 	bool found = false;
196 	for (auto& [format, formatString]: kDateFormats) {
197 		const char* result = strptime(dateString.String(), formatString, &expireTime);
198 
199 		if (result == dateString.String() + dateString.Length()) {
200 			fDateFormat = format;
201 			found = true;
202 			break;
203 		}
204 	}
205 
206 	// Did we identify some valid format?
207 	if (!found)
208 		throw InvalidInput(__PRETTY_FUNCTION__, dateString);
209 
210 	// Now convert the struct tm from strptime into a BDateTime.
211 	BTime time(expireTime.tm_hour, expireTime.tm_min, expireTime.tm_sec);
212 	BDate date(expireTime.tm_year + 1900, expireTime.tm_mon + 1, expireTime.tm_mday);
213 	fDate = BDateTime(date, time);
214 }
215 
216 
217 // #pragma mark Convenience Functions
218 
219 
220 BDateTime
221 BPrivate::Network::parse_http_time(const BString& string)
222 {
223 	BHttpTime httpTime(string);
224 	return httpTime.DateTime();
225 }
226 
227 
228 BString
229 BPrivate::Network::format_http_time(BDateTime timestamp, BHttpTimeFormat outputFormat)
230 {
231 	BHttpTime httpTime(timestamp);
232 	return httpTime.ToString(outputFormat);
233 }
234