1 //------------------------------------------------------------------------------ 2 // <copyright file="_HTTPDateParse.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 using System.Globalization; 8 9 namespace System.Net { 10 internal static class HttpDateParse { 11 private const int BASE_DEC = 10; // base 10 12 13 // 14 // Date indicies used to figure out what each entry is. 15 // 16 17 18 private const int DATE_INDEX_DAY_OF_WEEK = 0; 19 20 private const int DATE_1123_INDEX_DAY = 1; 21 private const int DATE_1123_INDEX_MONTH = 2; 22 private const int DATE_1123_INDEX_YEAR = 3; 23 private const int DATE_1123_INDEX_HRS = 4; 24 private const int DATE_1123_INDEX_MINS = 5; 25 private const int DATE_1123_INDEX_SECS = 6; 26 27 private const int DATE_ANSI_INDEX_MONTH = 1; 28 private const int DATE_ANSI_INDEX_DAY = 2; 29 private const int DATE_ANSI_INDEX_HRS = 3; 30 private const int DATE_ANSI_INDEX_MINS = 4; 31 private const int DATE_ANSI_INDEX_SECS = 5; 32 private const int DATE_ANSI_INDEX_YEAR = 6; 33 34 private const int DATE_INDEX_TZ = 7; 35 36 private const int DATE_INDEX_LAST = DATE_INDEX_TZ; 37 private const int MAX_FIELD_DATE_ENTRIES = (DATE_INDEX_LAST+1); 38 39 // 40 // DATE_TOKEN's DWORD values used to determine what day/month we're on 41 // 42 43 private const int DATE_TOKEN_JANUARY = 1; 44 private const int DATE_TOKEN_FEBRUARY = 2; 45 private const int DATE_TOKEN_Microsoft = 3; 46 private const int DATE_TOKEN_APRIL = 4; 47 private const int DATE_TOKEN_MAY = 5; 48 private const int DATE_TOKEN_JUNE = 6; 49 private const int DATE_TOKEN_JULY = 7; 50 private const int DATE_TOKEN_AUGUST = 8; 51 private const int DATE_TOKEN_SEPTEMBER = 9; 52 private const int DATE_TOKEN_OCTOBER = 10; 53 private const int DATE_TOKEN_NOVEMBER = 11; 54 private const int DATE_TOKEN_DECEMBER = 12; 55 56 private const int DATE_TOKEN_LAST_MONTH = (DATE_TOKEN_DECEMBER+1); 57 58 private const int DATE_TOKEN_SUNDAY = 0; 59 private const int DATE_TOKEN_MONDAY = 1; 60 private const int DATE_TOKEN_TUESDAY = 2; 61 private const int DATE_TOKEN_WEDNESDAY = 3; 62 private const int DATE_TOKEN_THURSDAY = 4; 63 private const int DATE_TOKEN_FRIDAY = 5; 64 private const int DATE_TOKEN_SATURDAY = 6; 65 66 private const int DATE_TOKEN_LAST_DAY = (DATE_TOKEN_SATURDAY+1); 67 68 private const int DATE_TOKEN_GMT = -1000; 69 70 private const int DATE_TOKEN_LAST = DATE_TOKEN_GMT; 71 72 private const int DATE_TOKEN_ERROR = (DATE_TOKEN_LAST+1); 73 74 75 // 76 // MAKE_UPPER - takes an assumed lower character and bit manipulates into a upper. 77 // (make sure the character is Lower case alpha char to begin, 78 // otherwise it corrupts) 79 // 80 81 private 82 static 83 char MAKE_UPPER(char c)84 MAKE_UPPER(char c) { 85 return(Char.ToUpper(c, CultureInfo.InvariantCulture)); 86 } 87 88 /*++ 89 90 Routine Description: 91 92 Looks at the first three bytes of string to determine if we're looking 93 at a Day of the Week, or Month, or "GMT" string. Is inlined so that 94 the compiler can optimize this code into the caller FInternalParseHttpDate. 95 96 Arguments: 97 98 lpszDay - a string ptr to the first byte of the string in question. 99 100 Return Value: 101 102 DWORD 103 Success - The Correct date token, 0-6 for day of the week, 1-14 for month, etc 104 105 Failure - DATE_TOKEN_ERROR 106 107 --*/ 108 109 private 110 static 111 int MapDayMonthToDword( char [] lpszDay, int index )112 MapDayMonthToDword( 113 char [] lpszDay, 114 int index 115 ) { 116 switch (MAKE_UPPER(lpszDay[index])) { // make uppercase 117 case 'A': 118 switch (MAKE_UPPER(lpszDay[index+1])) { 119 case 'P': 120 return DATE_TOKEN_APRIL; 121 case 'U': 122 return DATE_TOKEN_AUGUST; 123 124 } 125 return DATE_TOKEN_ERROR; 126 127 case 'D': 128 return DATE_TOKEN_DECEMBER; 129 130 case 'F': 131 switch (MAKE_UPPER(lpszDay[index+1])) { 132 case 'R': 133 return DATE_TOKEN_FRIDAY; 134 case 'E': 135 return DATE_TOKEN_FEBRUARY; 136 } 137 138 return DATE_TOKEN_ERROR; 139 140 case 'G': 141 return DATE_TOKEN_GMT; 142 143 case 'M': 144 145 switch (MAKE_UPPER(lpszDay[index+1])) { 146 case 'O': 147 return DATE_TOKEN_MONDAY; 148 case 'A': 149 switch (MAKE_UPPER(lpszDay[index+2])) { 150 case 'R': 151 return DATE_TOKEN_Microsoft; 152 case 'Y': 153 return DATE_TOKEN_MAY; 154 } 155 156 // fall through to error 157 break; 158 } 159 160 return DATE_TOKEN_ERROR; 161 162 case 'N': 163 return DATE_TOKEN_NOVEMBER; 164 165 case 'J': 166 167 switch (MAKE_UPPER(lpszDay[index+1])) { 168 case 'A': 169 return DATE_TOKEN_JANUARY; 170 171 case 'U': 172 switch (MAKE_UPPER(lpszDay[index+2])) { 173 case 'N': 174 return DATE_TOKEN_JUNE; 175 case 'L': 176 return DATE_TOKEN_JULY; 177 } 178 179 // fall through to error 180 break; 181 } 182 183 return DATE_TOKEN_ERROR; 184 185 case 'O': 186 return DATE_TOKEN_OCTOBER; 187 188 case 'S': 189 190 switch (MAKE_UPPER(lpszDay[index+1])) { 191 case 'A': 192 return DATE_TOKEN_SATURDAY; 193 case 'U': 194 return DATE_TOKEN_SUNDAY; 195 case 'E': 196 return DATE_TOKEN_SEPTEMBER; 197 } 198 199 return DATE_TOKEN_ERROR; 200 201 202 case 'T': 203 switch (MAKE_UPPER(lpszDay[index+1])) { 204 case 'U': 205 return DATE_TOKEN_TUESDAY; 206 case 'H': 207 return DATE_TOKEN_THURSDAY; 208 } 209 210 return DATE_TOKEN_ERROR; 211 212 case 'U': 213 return DATE_TOKEN_GMT; 214 215 case 'W': 216 return DATE_TOKEN_WEDNESDAY; 217 218 } 219 220 return DATE_TOKEN_ERROR; 221 } 222 223 /*++ 224 225 Routine Description: 226 227 Parses through a ANSI, RFC850, or RFC1123 date format and covents it into 228 a FILETIME/SYSTEMTIME time format. 229 230 Important this a time-critical function and should only be changed 231 with the intention of optimizing or a critical need work item. 232 233 Arguments: 234 235 lpft - Ptr to FILETIME structure. Used to store converted result. 236 Must be NULL if not intended to be used !!! 237 238 lpSysTime - Ptr to SYSTEMTIME struture. Used to return Systime if needed. 239 240 lpcszDateStr - Const Date string to parse. 241 242 Return Value: 243 244 BOOL 245 Success - TRUE 246 247 Failure - FALSE 248 249 --*/ 250 public 251 static 252 bool ParseHttpDate( String DateString, out DateTime dtOut )253 ParseHttpDate( 254 String DateString, 255 out DateTime dtOut 256 ) { 257 int index = 0; 258 int i = 0, iLastLettered = -1; 259 bool fIsANSIDateFormat = false; 260 int [] rgdwDateParseResults = new int[MAX_FIELD_DATE_ENTRIES]; 261 bool fRet = true; 262 char [] lpInputBuffer = DateString.ToCharArray(); 263 264 dtOut = new DateTime(); 265 266 // 267 // Date Parsing v2 (1 more to go), and here is how it works... 268 // We take a date string and churn through it once, converting 269 // integers to integers, Month,Day, and GMT strings into integers, 270 // and all is then placed IN order in a temp array. 271 // 272 // At the completetion of the parse stage, we simple look at 273 // the data, and then map the results into the correct 274 // places in the SYSTIME structure. Simple, No allocations, and 275 // No dirting the data. 276 // 277 // The end of the function does something munging and pretting 278 // up of the results to handle the year 2000, and TZ offsets 279 // Note: do we need to fully handle TZs anymore? 280 // 281 282 while (index < DateString.Length && i < MAX_FIELD_DATE_ENTRIES) { 283 if (lpInputBuffer[index] >= '0' && lpInputBuffer[index] <= '9') { 284 // 285 // we have a numerical entry, scan through it and convent to DWORD 286 // 287 288 rgdwDateParseResults[i] = 0; 289 290 do { 291 rgdwDateParseResults[i] *= BASE_DEC; 292 rgdwDateParseResults[i] += (lpInputBuffer[index] - '0'); 293 index++; 294 } while (index < DateString.Length && 295 lpInputBuffer[index] >= '0' && 296 lpInputBuffer[index] <= '9'); 297 298 i++; // next token 299 } 300 else if ((lpInputBuffer[index] >= 'A' && lpInputBuffer[index] <= 'Z') || 301 (lpInputBuffer[index] >= 'a' && lpInputBuffer[index] <= 'z')) { 302 // 303 // we have a string, should be a day, month, or GMT 304 // lets skim to the end of the string 305 // 306 307 rgdwDateParseResults[i] = 308 MapDayMonthToDword(lpInputBuffer, index); 309 310 iLastLettered = i; 311 312 // We want to ignore the possibility of a time zone such as PST or EST in a non-standard 313 // date format such as "Thu Dec 17 16:01:28 PST 1998" (Notice that the year is _after_ the time zone 314 if ((rgdwDateParseResults[i] == DATE_TOKEN_ERROR) 315 && 316 !(fIsANSIDateFormat && (i==DATE_ANSI_INDEX_YEAR))) { 317 fRet = false; 318 goto quit; 319 } 320 321 // 322 // At this point if we have a vaild string 323 // at this index, we know for sure that we're 324 // looking at a ANSI type DATE format. 325 // 326 327 if (i == DATE_ANSI_INDEX_MONTH) { 328 fIsANSIDateFormat = true; 329 } 330 331 // 332 // Read past the end of the current set of alpha characters, 333 // as MapDayMonthToDword only peeks at a few characters 334 // 335 336 do { 337 index++; 338 } while (index < DateString.Length && 339 ( (lpInputBuffer[index] >= 'A' && lpInputBuffer[index] <= 'Z') || 340 (lpInputBuffer[index] >= 'a' && lpInputBuffer[index] <= 'z') )); 341 342 i++; // next token 343 } 344 else { 345 // 346 // For the generic case its either a space, comma, semi-colon, etc. 347 // the point is we really don't care, nor do we need to waste time 348 // worring about it (the orginal code did). The point is we 349 // care about the actual date information, So we just advance to the 350 // next lexume. 351 // 352 353 index++; 354 } 355 } 356 357 // 358 // We're finished parsing the string, now take the parsed tokens 359 // and turn them to the actual structured information we care about. 360 // So we build lpSysTime from the Array, using a local if none is passed in. 361 // 362 363 int year; 364 int month; 365 int day; 366 int hour; 367 int minute; 368 int second; 369 int millisecond; 370 371 millisecond = 0; 372 373 if (fIsANSIDateFormat) { 374 day = rgdwDateParseResults[DATE_ANSI_INDEX_DAY]; 375 month = rgdwDateParseResults[DATE_ANSI_INDEX_MONTH]; 376 hour = rgdwDateParseResults[DATE_ANSI_INDEX_HRS]; 377 minute = rgdwDateParseResults[DATE_ANSI_INDEX_MINS]; 378 second = rgdwDateParseResults[DATE_ANSI_INDEX_SECS]; 379 if (iLastLettered != DATE_ANSI_INDEX_YEAR) { 380 year = rgdwDateParseResults[DATE_ANSI_INDEX_YEAR]; 381 } 382 else { 383 // This is a fix to get around toString/toGMTstring (where the timezone is 384 // appended at the end. (See above) 385 year = rgdwDateParseResults[DATE_INDEX_TZ]; 386 } 387 } 388 else { 389 day = rgdwDateParseResults[DATE_1123_INDEX_DAY]; 390 month = rgdwDateParseResults[DATE_1123_INDEX_MONTH]; 391 year = rgdwDateParseResults[DATE_1123_INDEX_YEAR]; 392 hour = rgdwDateParseResults[DATE_1123_INDEX_HRS]; 393 minute = rgdwDateParseResults[DATE_1123_INDEX_MINS]; 394 second = rgdwDateParseResults[DATE_1123_INDEX_SECS]; 395 } 396 397 // 398 // Normalize the year, 90 == 1990, handle the year 2000, 02 == 2002 399 // This is Year 2000 handling folks!!! We get this wrong and 400 // we all look bad. 401 // 402 403 if (year < 100) { 404 year += ((year < 80) ? 2000 : 1900); 405 } 406 407 // 408 // if we got misformed time, then plug in the current time 409 // !lpszHrs || !lpszMins || !lpszSec 410 // 411 412 if ((i < 4) 413 || (day > 31) 414 || (hour > 23) 415 || (minute > 59) 416 || (second > 59)) { 417 fRet = false; 418 goto quit; 419 } 420 421 // 422 // Now do the DateTime conversion 423 // 424 425 dtOut = new DateTime (year, month, day, hour, minute, second, millisecond); 426 427 // 428 // we want the system time to be accurate. This is _suhlow_ 429 // The time passed in is in the local time zone; we have to convert this into GMT. 430 // 431 432 if (iLastLettered==DATE_ANSI_INDEX_YEAR) { 433 // this should be an unusual case. 434 dtOut = dtOut.ToUniversalTime(); 435 } 436 437 // 438 // If we have an Offset to another Time Zone 439 // then convert to appropriate GMT time 440 // 441 442 if ((i > DATE_INDEX_TZ && 443 rgdwDateParseResults[DATE_INDEX_TZ] != DATE_TOKEN_GMT)) { 444 445 // 446 // if we received +/-nnnn as offset (hhmm), modify the output FILETIME 447 // 448 449 double offset; 450 451 offset = (double) rgdwDateParseResults[DATE_INDEX_TZ]; 452 dtOut.AddHours(offset); 453 } 454 455 // In the end, we leave it all in LocalTime 456 457 dtOut = dtOut.ToLocalTime(); 458 459 quit: 460 461 return fRet; 462 } 463 } 464 465 } // namespace System.Net 466