Panda3D
 All Classes Functions Variables Enumerations
httpDate.cxx
00001 // Filename: httpDate.cxx
00002 // Created by:  drose (28Jan03)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "httpDate.h"
00016 
00017 #include <ctype.h>
00018 
00019 static const int num_weekdays = 7;
00020 static const char * const weekdays[num_weekdays] = {
00021   "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
00022 };
00023 
00024 static const int num_months = 12;
00025 static const char * const months[num_months] = {
00026   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00027   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
00028 };
00029 
00030 
00031 ////////////////////////////////////////////////////////////////////
00032 //     Function: HTTPDate::Constructor
00033 //       Access: Published
00034 //  Description: Decodes the string into a sensible date.  Returns 0
00035 //               (!is_valid()) if the string cannot be correctly
00036 //               decoded.
00037 ////////////////////////////////////////////////////////////////////
00038 HTTPDate::
00039 HTTPDate(const string &format) {
00040   _time = (time_t)(-1);
00041 
00042   struct tm t;
00043   memset(&t, 0, sizeof(t));
00044 
00045   bool got_weekday = false;
00046   bool got_month = false;
00047   bool got_day = false;
00048   bool got_year = false;
00049   bool got_hour = false;
00050   bool got_minute = false;
00051   bool got_second = false;
00052 
00053   enum ExpectNext { 
00054     EN_none,
00055     EN_second,
00056     EN_year
00057   };
00058   ExpectNext expect_next = EN_none;
00059 
00060   size_t pos = 0;
00061   string token = get_token(format, pos);
00062   while (!token.empty()) {
00063     ExpectNext expected = expect_next;
00064     expect_next = EN_none;
00065 
00066     if (isdigit(token[0])) {
00067       // Here's a number.
00068       int value = atoi(token.c_str());
00069       if (token[token.length() - 1] == ':') {
00070         // If it ends in a colon, it must be hh or mm.
00071         if (!got_hour) {
00072           t.tm_hour = value;
00073           got_hour = true;
00074           
00075         } else if (!got_minute) {
00076           t.tm_min = value;
00077           got_minute = true;
00078           expect_next = EN_second;
00079 
00080         } else {
00081           return;
00082         }
00083 
00084       } else if (token[token.length() - 1] == '/') {
00085         // If it ends in a colon, it must be mm/dd/.
00086         if (!got_month) {
00087           t.tm_mon = value - 1;
00088           got_month = true;
00089           
00090         } else if (!got_day) {
00091           t.tm_mday = value;
00092           got_day = true;
00093           expect_next = EN_year;
00094 
00095         } else {
00096           return;
00097         }
00098 
00099       } else {
00100         if (expected == EN_second) {
00101           // The first number following hh:mm: is always the seconds.
00102           t.tm_sec = value;
00103           got_second = true;
00104 
00105         } else if (expected == EN_year) {
00106           // The first number following mm/dd/ is always the year.
00107           t.tm_year = value;
00108           got_year = true;
00109           
00110         } else if (!got_day) {
00111           // Assume it's a day.
00112           t.tm_mday = value;
00113           got_day = true;
00114           
00115         } else if (!got_year) {
00116           // It must be the year.
00117           t.tm_year = value;
00118           got_year = true;
00119           
00120         } else if (!got_hour) {
00121           t.tm_hour = value;
00122           got_hour = true;
00123           
00124         } else if (!got_minute) {
00125           t.tm_min = value;
00126           got_minute = true;
00127           
00128         } else if (!got_second) {
00129           t.tm_sec = value;
00130           got_second = true;
00131           
00132         } else {
00133           // Huh, an unexpected numeric value.
00134           return;
00135         }
00136       }
00137 
00138     } else {
00139       // This is a string token.  It should be either a month name or
00140       // a day name, or a timezone name--but the only timezone name we
00141       // expect to see is "GMT".
00142       bool matched = false;
00143       int i;
00144 
00145       for (i = 0; i < num_weekdays && !matched; i++) {
00146         if (token == weekdays[i]) {
00147           if (got_weekday) {
00148             return;
00149           }
00150           matched = true;
00151           got_weekday = true;
00152           t.tm_wday = i;
00153         }
00154       }
00155 
00156       for (i = 0; i < num_months && !matched; i++) {
00157         if (token == months[i]) {
00158           if (got_month) {
00159             return;
00160           }
00161           matched = true;
00162           got_month = true;
00163           t.tm_mon = i;
00164         }
00165       }
00166 
00167       if (!matched && token == "Gmt") {
00168         matched = true;
00169       }
00170 
00171       if (!matched) {
00172         // Couldn't figure this one out.
00173         return;
00174       }
00175     }
00176 
00177     token = get_token(format, pos);
00178   }
00179 
00180   // Now check that we got the minimum expected tokens.
00181   if (!(got_month && got_day && got_year && got_hour && got_minute)) {
00182     return;
00183   }
00184 
00185   // Also validate the tokens we did get.
00186   if (t.tm_year < 100) {
00187     // Two-digit year.  Assume it's in the same century, unless
00188     // that assumption puts it more than 50 years in the future.
00189     time_t now = time(NULL);
00190     struct tm *tp = gmtime(&now);
00191     t.tm_year += 100 * (tp->tm_year / 100);
00192     if (t.tm_year - tp->tm_year > 50) {
00193       t.tm_year -= 100;
00194     }
00195     
00196   } else if (t.tm_year < 1900) {
00197     // Invalid three- or four-digit year.  Give up.
00198     return;
00199     
00200   } else {
00201     t.tm_year -= 1900;
00202   }
00203           
00204   if (!((t.tm_mon >= 0 && t.tm_mon < num_months) &&
00205         (t.tm_mday >= 1 && t.tm_mday <= 31) &&
00206         (t.tm_hour >= 0 && t.tm_hour < 60) &&
00207         (t.tm_min >= 0 && t.tm_min < 60) &&
00208         (t.tm_sec >= 0 && t.tm_sec < 62) /* maybe leap seconds */)) {
00209     return;
00210   }
00211 
00212   // Everything checks out; convert the date.
00213   // rdb made this an #if 0 check as timegm is a nonstandard extension
00214   // so it fails in some situations even if the compiler defines __GNUC__
00215 #if 0
00216 
00217   _time = timegm(&t);
00218 
00219 #else  // __GNUC__
00220   // Without the GNU extension timegm, we have to use mktime() instead.
00221   _time = mktime(&t);
00222 
00223   if (_time != (time_t)-1) {
00224     // Unfortunately, mktime() assumes local time; convert this back
00225     // to GMT.
00226     extern long int timezone;
00227     _time -= timezone;
00228   }
00229 #endif  // __GNUC__
00230 }
00231 
00232 ////////////////////////////////////////////////////////////////////
00233 //     Function: HTTPDate::get_string
00234 //       Access: Published
00235 //  Description:
00236 ////////////////////////////////////////////////////////////////////
00237 string HTTPDate::
00238 get_string() const {
00239   if (!is_valid()) {
00240     return "Invalid Date";
00241   }
00242 
00243   struct tm *tp = gmtime(&_time);
00244 
00245   ostringstream result;
00246   result
00247     << weekdays[tp->tm_wday] << ", " 
00248     << setw(2) << setfill('0') << tp->tm_mday << " "
00249     << months[tp->tm_mon] << " "
00250     << setw(4) << setfill('0') << tp->tm_year + 1900 << " "
00251     << setw(2) << setfill('0') << tp->tm_hour << ":"
00252     << setw(2) << setfill('0') << tp->tm_min << ":"
00253     << setw(2) << setfill('0') << tp->tm_sec << " GMT";
00254 
00255   return result.str();
00256 }
00257 
00258 
00259 ////////////////////////////////////////////////////////////////////
00260 //     Function: HTTPDate::input
00261 //       Access: Published
00262 //  Description: 
00263 ////////////////////////////////////////////////////////////////////
00264 bool HTTPDate::
00265 input(istream &in) {
00266   (*this) = HTTPDate();
00267 
00268   // Extract out the quoted date string.
00269   char ch;
00270   in >> ch;
00271   if (ch != '"') {
00272     return false;
00273   }
00274 
00275   string date;
00276   ch = in.get();
00277   while (!in.fail() && !in.eof() && ch != '"') {
00278     date += ch;
00279     ch = in.get();
00280   }
00281 
00282   if (ch != '"') {
00283     return false;
00284   }
00285 
00286   // Visual C++ has problems with "(*this) = HTTPDate(date)".
00287   HTTPDate new_date(date);
00288   (*this) = new_date;
00289   return is_valid();
00290 }
00291 
00292 ////////////////////////////////////////////////////////////////////
00293 //     Function: HTTPDate::output
00294 //       Access: Published
00295 //  Description: 
00296 ////////////////////////////////////////////////////////////////////
00297 void HTTPDate::
00298 output(ostream &out) const {
00299   // We put quotes around the string on output, so we can reliably
00300   // detect the end of the date string on input, above.
00301   out << '"' << get_string() << '"';
00302 }
00303 
00304 ////////////////////////////////////////////////////////////////////
00305 //     Function: HTTPDate::get_token
00306 //       Access: Published
00307 //  Description: Extracts the next token from the string starting at
00308 //               the indicated position.  Returns the token and
00309 //               updates pos.  When the last token has been extracted,
00310 //               returns empty string.
00311 //
00312 //               A token is defined as a contiguous sequence of digits
00313 //               or letters.  If it is a sequence of letters, the
00314 //               function quietly truncates it to three letters before
00315 //               returning, and forces the first letter to capital and
00316 //               the second two to lowercase.  If it is a sequence of
00317 //               digits, the function also returns the next character
00318 //               following the last digit (unless it is a letter).
00319 ////////////////////////////////////////////////////////////////////
00320 string HTTPDate::
00321 get_token(const string &str, size_t &pos) {
00322   // Start by scanning for the first alphanumeric character.
00323   size_t start = pos;
00324   while (start < str.length() && !isalnum(str[start])) {
00325     start++;
00326   }
00327 
00328   if (start >= str.length()) {
00329     // End of the line.
00330     pos = string::npos;
00331     return string();
00332   }
00333 
00334   string token;
00335 
00336   if (isalpha(str[start])) {
00337     // A string of letters.
00338     token = toupper(str[start]);
00339     pos = start + 1;
00340     while (pos < str.length() && isalpha(str[pos])) {
00341       if (token.length() < 3) {
00342         token += tolower(str[pos]);
00343       }
00344       pos++;
00345     }
00346 
00347   } else {
00348     // A string of digits.
00349     pos = start + 1;
00350     while (pos < str.length() && isdigit(str[pos])) {
00351       pos++;
00352     }
00353     // Get one more character, so we can identify things like hh:
00354     if (pos < str.length() && !isalpha(str[pos])) {
00355       pos++;
00356     }
00357     token = str.substr(start, pos - start);
00358   }
00359 
00360   return token;
00361 }
 All Classes Functions Variables Enumerations