Panda3D
|
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 }