Panda3D
 All Classes Functions Variables Enumerations
httpDate.cxx
1 // Filename: httpDate.cxx
2 // Created by: drose (28Jan03)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "httpDate.h"
16 
17 #include <ctype.h>
18 
19 static const int num_weekdays = 7;
20 static const char * const weekdays[num_weekdays] = {
21  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
22 };
23 
24 static const int num_months = 12;
25 static const char * const months[num_months] = {
26  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
27  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
28 };
29 
30 
31 ////////////////////////////////////////////////////////////////////
32 // Function: HTTPDate::Constructor
33 // Access: Published
34 // Description: Decodes the string into a sensible date. Returns 0
35 // (!is_valid()) if the string cannot be correctly
36 // 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 mm/dd/.
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 mm/dd/ 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
140  // a day name, or a timezone name--but the only timezone name we
141  // expect to see 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
188  // that assumption puts it more than 50 years in the future.
189  time_t now = time(NULL);
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.
213  // rdb made this an #if 0 check as timegm is a nonstandard extension
214  // so it fails in some situations even if 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
225  // to GMT.
226 #ifdef IS_FREEBSD
227  time_t now = time(NULL);
228  struct tm *tp = localtime(&now);
229  _time -= tp->tm_gmtoff;
230 #else /* IS_FREEBSD */
231  extern long int timezone;
232  _time -= timezone;
233 #endif /* IS_FREEBSD */
234  }
235 #endif // __GNUC__
236 }
237 
238 ////////////////////////////////////////////////////////////////////
239 // Function: HTTPDate::get_string
240 // Access: Published
241 // Description:
242 ////////////////////////////////////////////////////////////////////
243 string HTTPDate::
244 get_string() const {
245  if (!is_valid()) {
246  return "Invalid Date";
247  }
248 
249  struct tm *tp = gmtime(&_time);
250 
251  ostringstream result;
252  result
253  << weekdays[tp->tm_wday] << ", "
254  << setw(2) << setfill('0') << tp->tm_mday << " "
255  << months[tp->tm_mon] << " "
256  << setw(4) << setfill('0') << tp->tm_year + 1900 << " "
257  << setw(2) << setfill('0') << tp->tm_hour << ":"
258  << setw(2) << setfill('0') << tp->tm_min << ":"
259  << setw(2) << setfill('0') << tp->tm_sec << " GMT";
260 
261  return result.str();
262 }
263 
264 
265 ////////////////////////////////////////////////////////////////////
266 // Function: HTTPDate::input
267 // Access: Published
268 // Description:
269 ////////////////////////////////////////////////////////////////////
270 bool HTTPDate::
271 input(istream &in) {
272  (*this) = HTTPDate();
273 
274  // Extract out the quoted date string.
275  char ch;
276  in >> ch;
277  if (ch != '"') {
278  return false;
279  }
280 
281  string date;
282  ch = in.get();
283  while (!in.fail() && !in.eof() && ch != '"') {
284  date += ch;
285  ch = in.get();
286  }
287 
288  if (ch != '"') {
289  return false;
290  }
291 
292  // Visual C++ has problems with "(*this) = HTTPDate(date)".
293  HTTPDate new_date(date);
294  (*this) = new_date;
295  return is_valid();
296 }
297 
298 ////////////////////////////////////////////////////////////////////
299 // Function: HTTPDate::output
300 // Access: Published
301 // Description:
302 ////////////////////////////////////////////////////////////////////
303 void HTTPDate::
304 output(ostream &out) const {
305  // We put quotes around the string on output, so we can reliably
306  // detect the end of the date string on input, above.
307  out << '"' << get_string() << '"';
308 }
309 
310 ////////////////////////////////////////////////////////////////////
311 // Function: HTTPDate::get_token
312 // Access: Published
313 // Description: Extracts the next token from the string starting at
314 // the indicated position. Returns the token and
315 // updates pos. When the last token has been extracted,
316 // returns empty string.
317 //
318 // A token is defined as a contiguous sequence of digits
319 // or letters. If it is a sequence of letters, the
320 // function quietly truncates it to three letters before
321 // returning, and forces the first letter to capital and
322 // the second two to lowercase. If it is a sequence of
323 // digits, the function also returns the next character
324 // following the last digit (unless it is a letter).
325 ////////////////////////////////////////////////////////////////////
326 string HTTPDate::
327 get_token(const string &str, size_t &pos) {
328  // Start by scanning for the first alphanumeric character.
329  size_t start = pos;
330  while (start < str.length() && !isalnum(str[start])) {
331  start++;
332  }
333 
334  if (start >= str.length()) {
335  // End of the line.
336  pos = string::npos;
337  return string();
338  }
339 
340  string token;
341 
342  if (isalpha(str[start])) {
343  // A string of letters.
344  token = toupper(str[start]);
345  pos = start + 1;
346  while (pos < str.length() && isalpha(str[pos])) {
347  if (token.length() < 3) {
348  token += tolower(str[pos]);
349  }
350  pos++;
351  }
352 
353  } else {
354  // A string of digits.
355  pos = start + 1;
356  while (pos < str.length() && isdigit(str[pos])) {
357  pos++;
358  }
359  // Get one more character, so we can identify things like hh:
360  if (pos < str.length() && !isalpha(str[pos])) {
361  pos++;
362  }
363  token = str.substr(start, pos - start);
364  }
365 
366  return token;
367 }
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:72
A container for an HTTP-legal time/date indication.
Definition: httpDate.h:30
static HTTPDate now()
Returns an HTTPDate that represents the current time and date.
Definition: httpDate.I:60