Panda3D
Loading...
Searching...
No Matches
httpDate.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file httpDate.cxx
10 * @author drose
11 * @date 2003-01-28
12 */
13
14#include "httpDate.h"
15
16#include <ctype.h>
17
18using std::setfill;
19using std::setw;
20using std::string;
21
22static const int num_weekdays = 7;
23static const char * const weekdays[num_weekdays] = {
24 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
25};
26
27static const int num_months = 12;
28static const char * const months[num_months] = {
29 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
30 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
31};
32
33
34/**
35 * Decodes the string into a sensible date. Returns 0 (!is_valid()) if the
36 * string cannot be correctly decoded.
37 */
38HTTPDate::
39HTTPDate(const string &format) {
40 _time = (time_t)(-1);
41
42 struct tm t;
43 memset(&t, 0, sizeof(t));
44
45 bool got_weekday = false;
46 bool got_month = false;
47 bool got_day = false;
48 bool got_year = false;
49 bool got_hour = false;
50 bool got_minute = false;
51 bool got_second = false;
52
53 enum ExpectNext {
54 EN_none,
55 EN_second,
56 EN_year
57 };
58 ExpectNext expect_next = EN_none;
59
60 size_t pos = 0;
61 string token = get_token(format, pos);
62 while (!token.empty()) {
63 ExpectNext expected = expect_next;
64 expect_next = EN_none;
65
66 if (isdigit(token[0])) {
67 // Here's a number.
68 int value = atoi(token.c_str());
69 if (token[token.length() - 1] == ':') {
70 // If it ends in a colon, it must be hh or mm.
71 if (!got_hour) {
72 t.tm_hour = value;
73 got_hour = true;
74
75 } else if (!got_minute) {
76 t.tm_min = value;
77 got_minute = true;
78 expect_next = EN_second;
79
80 } else {
81 return;
82 }
83
84 } else if (token[token.length() - 1] == '/') {
85 // If it ends in a colon, it must be mmdd.
86 if (!got_month) {
87 t.tm_mon = value - 1;
88 got_month = true;
89
90 } else if (!got_day) {
91 t.tm_mday = value;
92 got_day = true;
93 expect_next = EN_year;
94
95 } else {
96 return;
97 }
98
99 } else {
100 if (expected == EN_second) {
101 // The first number following hh:mm: is always the seconds.
102 t.tm_sec = value;
103 got_second = true;
104
105 } else if (expected == EN_year) {
106 // The first number following mmdd is always the year.
107 t.tm_year = value;
108 got_year = true;
109
110 } else if (!got_day) {
111 // Assume it's a day.
112 t.tm_mday = value;
113 got_day = true;
114
115 } else if (!got_year) {
116 // It must be the year.
117 t.tm_year = value;
118 got_year = true;
119
120 } else if (!got_hour) {
121 t.tm_hour = value;
122 got_hour = true;
123
124 } else if (!got_minute) {
125 t.tm_min = value;
126 got_minute = true;
127
128 } else if (!got_second) {
129 t.tm_sec = value;
130 got_second = true;
131
132 } else {
133 // Huh, an unexpected numeric value.
134 return;
135 }
136 }
137
138 } else {
139 // This is a string token. It should be either a month name or a day
140 // name, or a timezone name--but the only timezone name we expect to see
141 // is "GMT".
142 bool matched = false;
143 int i;
144
145 for (i = 0; i < num_weekdays && !matched; i++) {
146 if (token == weekdays[i]) {
147 if (got_weekday) {
148 return;
149 }
150 matched = true;
151 got_weekday = true;
152 t.tm_wday = i;
153 }
154 }
155
156 for (i = 0; i < num_months && !matched; i++) {
157 if (token == months[i]) {
158 if (got_month) {
159 return;
160 }
161 matched = true;
162 got_month = true;
163 t.tm_mon = i;
164 }
165 }
166
167 if (!matched && token == "Gmt") {
168 matched = true;
169 }
170
171 if (!matched) {
172 // Couldn't figure this one out.
173 return;
174 }
175 }
176
177 token = get_token(format, pos);
178 }
179
180 // Now check that we got the minimum expected tokens.
181 if (!(got_month && got_day && got_year && got_hour && got_minute)) {
182 return;
183 }
184
185 // Also validate the tokens we did get.
186 if (t.tm_year < 100) {
187 // Two-digit year. Assume it's in the same century, unless that
188 // assumption puts it more than 50 years in the future.
189 time_t now = time(nullptr);
190 struct tm *tp = gmtime(&now);
191 t.tm_year += 100 * (tp->tm_year / 100);
192 if (t.tm_year - tp->tm_year > 50) {
193 t.tm_year -= 100;
194 }
195
196 } else if (t.tm_year < 1900) {
197 // Invalid three- or four-digit year. Give up.
198 return;
199
200 } else {
201 t.tm_year -= 1900;
202 }
203
204 if (!((t.tm_mon >= 0 && t.tm_mon < num_months) &&
205 (t.tm_mday >= 1 && t.tm_mday <= 31) &&
206 (t.tm_hour >= 0 && t.tm_hour < 60) &&
207 (t.tm_min >= 0 && t.tm_min < 60) &&
208 (t.tm_sec >= 0 && t.tm_sec < 62) /* maybe leap seconds */)) {
209 return;
210 }
211
212 // Everything checks out; convert the date. rdb made this an if 0 check as
213 // timegm is a nonstandard extension so it fails in some situations even if
214 // the compiler defines __GNUC__
215#if 0
216
217 _time = timegm(&t);
218
219#else // __GNUC__
220 // Without the GNU extension timegm, we have to use mktime() instead.
221 _time = mktime(&t);
222
223 if (_time != (time_t)-1) {
224 // Unfortunately, mktime() assumes local time; convert this back to GMT.
225#if defined(IS_FREEBSD)
226 time_t now = time(nullptr);
227 struct tm *tp = localtime(&now);
228 _time -= tp->tm_gmtoff;
229#elif defined(_WIN32)
230 long int timezone;
231 _get_timezone(&timezone);
232 _time -= timezone;
233#else
234 extern long int timezone;
235 _time -= timezone;
236#endif
237 }
238#endif // __GNUC__
239}
240
241/**
242 *
243 */
244string HTTPDate::
245get_string() const {
246 if (!is_valid()) {
247 return "Invalid Date";
248 }
249
250 struct tm *tp = gmtime(&_time);
251
252 std::ostringstream result;
253 result
254 << weekdays[tp->tm_wday] << ", "
255 << setw(2) << setfill('0') << tp->tm_mday << " "
256 << months[tp->tm_mon] << " "
257 << setw(4) << setfill('0') << tp->tm_year + 1900 << " "
258 << setw(2) << setfill('0') << tp->tm_hour << ":"
259 << setw(2) << setfill('0') << tp->tm_min << ":"
260 << setw(2) << setfill('0') << tp->tm_sec << " GMT";
261
262 return result.str();
263}
264
265
266/**
267 *
268 */
269bool HTTPDate::
270input(std::istream &in) {
271 (*this) = HTTPDate();
272
273 // Extract out the quoted date string.
274 char ch;
275 in >> ch;
276 if (ch != '"') {
277 return false;
278 }
279
280 string date;
281 ch = in.get();
282 while (!in.fail() && ch != '"') {
283 date += ch;
284 ch = in.get();
285 }
286
287 if (ch != '"') {
288 return false;
289 }
290
291 // Visual C++ has problems with "(*this) = HTTPDate(date)".
292 HTTPDate new_date(date);
293 (*this) = new_date;
294 return is_valid();
295}
296
297/**
298 *
299 */
300void HTTPDate::
301output(std::ostream &out) const {
302 // We put quotes around the string on output, so we can reliably detect the
303 // end of the date string on input, above.
304 out << '"' << get_string() << '"';
305}
306
307/**
308 * Extracts the next token from the string starting at the indicated position.
309 * Returns the token and updates pos. When the last token has been extracted,
310 * returns empty string.
311 *
312 * A token is defined as a contiguous sequence of digits or letters. If it is
313 * a sequence of letters, the function quietly truncates it to three letters
314 * before returning, and forces the first letter to capital and the second two
315 * to lowercase. If it is a sequence of digits, the function also returns the
316 * next character following the last digit (unless it is a letter).
317 */
318string HTTPDate::
319get_token(const string &str, size_t &pos) {
320 // Start by scanning for the first alphanumeric character.
321 size_t start = pos;
322 while (start < str.length() && !isalnum(str[start])) {
323 start++;
324 }
325
326 if (start >= str.length()) {
327 // End of the line.
328 pos = string::npos;
329 return string();
330 }
331
332 string token;
333
334 if (isalpha(str[start])) {
335 // A string of letters.
336 token = toupper(str[start]);
337 pos = start + 1;
338 while (pos < str.length() && isalpha(str[pos])) {
339 if (token.length() < 3) {
340 token += tolower(str[pos]);
341 }
342 pos++;
343 }
344
345 } else {
346 // A string of digits.
347 pos = start + 1;
348 while (pos < str.length() && isdigit(str[pos])) {
349 pos++;
350 }
351 // Get one more character, so we can identify things like hh:
352 if (pos < str.length() && !isalpha(str[pos])) {
353 pos++;
354 }
355 token = str.substr(start, pos - start);
356 }
357
358 return token;
359}
A container for an HTTP-legal time/date indication.
Definition httpDate.h:27
static HTTPDate now()
Returns an HTTPDate that represents the current time and date.
Definition httpDate.I:47
bool is_valid() const
Returns true if the date is meaningful, or false if it is -1 (which generally indicates the source st...
Definition httpDate.I:56
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.