Panda3D
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 
18 using std::setfill;
19 using std::setw;
20 using std::string;
21 
22 static const int num_weekdays = 7;
23 static const char * const weekdays[num_weekdays] = {
24  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
25 };
26 
27 static const int num_months = 12;
28 static 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  */
38 HTTPDate::
39 HTTPDate(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  */
244 string HTTPDate::
245 get_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  */
269 bool HTTPDate::
270 input(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  */
300 void HTTPDate::
301 output(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  */
318 string HTTPDate::
319 get_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 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
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