//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Web { using System; using System.Globalization; // DO NOT EDIT THIS CODE. // // All of the code from this class was taken from build 20717.00 // of System.Net.HttpDateParse. If there is a bug with this code // it should be fixed in the original System.Net.HttpDateParse // and then ported here. [[....]] internal static class HttpDateParse { private const int BASE_DEC = 10; // base 10 // // Date indicies used to figure out what each entry is. // private const int DATE_INDEX_DAY_OF_WEEK = 0; private const int DATE_1123_INDEX_DAY = 1; private const int DATE_1123_INDEX_MONTH = 2; private const int DATE_1123_INDEX_YEAR = 3; private const int DATE_1123_INDEX_HRS = 4; private const int DATE_1123_INDEX_MINS = 5; private const int DATE_1123_INDEX_SECS = 6; private const int DATE_ANSI_INDEX_MONTH = 1; private const int DATE_ANSI_INDEX_DAY = 2; private const int DATE_ANSI_INDEX_HRS = 3; private const int DATE_ANSI_INDEX_MINS = 4; private const int DATE_ANSI_INDEX_SECS = 5; private const int DATE_ANSI_INDEX_YEAR = 6; private const int DATE_INDEX_TZ = 7; private const int DATE_INDEX_LAST = DATE_INDEX_TZ; private const int MAX_FIELD_DATE_ENTRIES = (DATE_INDEX_LAST + 1); // // DATE_TOKEN's DWORD values used to determine what day/month we're on // private const int DATE_TOKEN_JANUARY = 1; private const int DATE_TOKEN_FEBRUARY = 2; private const int DATE_TOKEN_MARCH = 3; private const int DATE_TOKEN_APRIL = 4; private const int DATE_TOKEN_MAY = 5; private const int DATE_TOKEN_JUNE = 6; private const int DATE_TOKEN_JULY = 7; private const int DATE_TOKEN_AUGUST = 8; private const int DATE_TOKEN_SEPTEMBER = 9; private const int DATE_TOKEN_OCTOBER = 10; private const int DATE_TOKEN_NOVEMBER = 11; private const int DATE_TOKEN_DECEMBER = 12; private const int DATE_TOKEN_LAST_MONTH = (DATE_TOKEN_DECEMBER + 1); private const int DATE_TOKEN_SUNDAY = 0; private const int DATE_TOKEN_MONDAY = 1; private const int DATE_TOKEN_TUESDAY = 2; private const int DATE_TOKEN_WEDNESDAY = 3; private const int DATE_TOKEN_THURSDAY = 4; private const int DATE_TOKEN_FRIDAY = 5; private const int DATE_TOKEN_SATURDAY = 6; private const int DATE_TOKEN_LAST_DAY = (DATE_TOKEN_SATURDAY + 1); private const int DATE_TOKEN_GMT = -1000; private const int DATE_TOKEN_LAST = DATE_TOKEN_GMT; private const int DATE_TOKEN_ERROR = (DATE_TOKEN_LAST + 1); // // MakeUpper - takes an assumed lower character and bit manipulates into a upper. // (make sure the character is Lower case alpha char to begin, // otherwise it corrupts) // static char MakeUpper(char c) { return (Char.ToUpper(c, CultureInfo.InvariantCulture)); } // Routine Description: // // Looks at the first three bytes of string to determine if we're looking // at a Day of the Week, or Month, or "GMT" string. Is inlined so that // the compiler can optimize this code into the caller FInternalParseHttpDate. // // Arguments: // // lpszDay - a string ptr to the first byte of the string in question. // // Return Value: // // DWORD // Success - The Correct date token, 0-6 for day of the week, 1-14 for month, etc // // Failure - DATE_TOKEN_ERROR static int MapDayMonthToDword(char[] lpszDay, int index) { switch (MakeUpper(lpszDay[index])) { // make uppercase case 'A': switch (MakeUpper(lpszDay[index + 1])) { case 'P': return DATE_TOKEN_APRIL; case 'U': return DATE_TOKEN_AUGUST; } return DATE_TOKEN_ERROR; case 'D': return DATE_TOKEN_DECEMBER; case 'F': switch (MakeUpper(lpszDay[index + 1])) { case 'R': return DATE_TOKEN_FRIDAY; case 'E': return DATE_TOKEN_FEBRUARY; } return DATE_TOKEN_ERROR; case 'G': return DATE_TOKEN_GMT; case 'M': switch (MakeUpper(lpszDay[index + 1])) { case 'O': return DATE_TOKEN_MONDAY; case 'A': switch (MakeUpper(lpszDay[index + 2])) { case 'R': return DATE_TOKEN_MARCH; case 'Y': return DATE_TOKEN_MAY; } // fall through to error break; } return DATE_TOKEN_ERROR; case 'N': return DATE_TOKEN_NOVEMBER; case 'J': switch (MakeUpper(lpszDay[index + 1])) { case 'A': return DATE_TOKEN_JANUARY; case 'U': switch (MakeUpper(lpszDay[index + 2])) { case 'N': return DATE_TOKEN_JUNE; case 'L': return DATE_TOKEN_JULY; } // fall through to error break; } return DATE_TOKEN_ERROR; case 'O': return DATE_TOKEN_OCTOBER; case 'S': switch (MakeUpper(lpszDay[index + 1])) { case 'A': return DATE_TOKEN_SATURDAY; case 'U': return DATE_TOKEN_SUNDAY; case 'E': return DATE_TOKEN_SEPTEMBER; } return DATE_TOKEN_ERROR; case 'T': switch (MakeUpper(lpszDay[index + 1])) { case 'U': return DATE_TOKEN_TUESDAY; case 'H': return DATE_TOKEN_THURSDAY; } return DATE_TOKEN_ERROR; case 'U': return DATE_TOKEN_GMT; case 'W': return DATE_TOKEN_WEDNESDAY; } return DATE_TOKEN_ERROR; } // Routine Description: // // Parses through a ANSI, RFC850, or RFC1123 date format and covents it into // a FILETIME/SYSTEMTIME time format. // // Important this a time-critical function and should only be changed // with the intention of optimizing or a critical need work item. // // Arguments: // // lpft - Ptr to FILETIME structure. Used to store converted result. // Must be NULL if not intended to be used !!! // // lpSysTime - Ptr to SYSTEMTIME struture. Used to return Systime if needed. // // lpcszDateStr - Const Date string to parse. // // Return Value: // // BOOL // Success - TRUE // Failure - FALSE internal static bool ParseHttpDate(String DateString, out DateTime dtOut) { int index = 0; int i = 0, iLastLettered = -1; bool fIsANSIDateFormat = false; int[] rgdwDateParseResults = new int[MAX_FIELD_DATE_ENTRIES]; bool fRet = true; char[] lpInputBuffer = DateString.ToCharArray(); dtOut = new DateTime(); // // Date Parsing v2 (1 more to go), and here is how it works... // We take a date string and churn through it once, converting // integers to integers, Month,Day, and GMT strings into integers, // and all is then placed IN order in a temp array. // // At the completetion of the parse stage, we simple look at // the data, and then map the results into the correct // places in the SYSTIME structure. Simple, No allocations, and // No dirting the data. // // The end of the function does something munging and pretting // up of the results to handle the year 2000, and TZ offsets // Note: do we need to fully handle TZs anymore? // while (index < DateString.Length && i < MAX_FIELD_DATE_ENTRIES) { if (lpInputBuffer[index] >= '0' && lpInputBuffer[index] <= '9') { // // we have a numerical entry, scan through it and convent to DWORD // rgdwDateParseResults[i] = 0; do { rgdwDateParseResults[i] *= BASE_DEC; rgdwDateParseResults[i] += (lpInputBuffer[index] - '0'); index++; } while (index < DateString.Length && lpInputBuffer[index] >= '0' && lpInputBuffer[index] <= '9'); i++; // next token } else if ((lpInputBuffer[index] >= 'A' && lpInputBuffer[index] <= 'Z') || (lpInputBuffer[index] >= 'a' && lpInputBuffer[index] <= 'z')) { // // we have a string, should be a day, month, or GMT // lets skim to the end of the string // rgdwDateParseResults[i] = MapDayMonthToDword(lpInputBuffer, index); iLastLettered = i; // We want to ignore the possibility of a time zone such as PST or EST in a non-standard // date format such as "Thu Dec 17 16:01:28 PST 1998" (Notice that the year is _after_ the time zone if ((rgdwDateParseResults[i] == DATE_TOKEN_ERROR) && !(fIsANSIDateFormat && (i == DATE_ANSI_INDEX_YEAR))) { fRet = false; goto quit; } // // At this point if we have a vaild string // at this index, we know for sure that we're // looking at a ANSI type DATE format. // if (i == DATE_ANSI_INDEX_MONTH) { fIsANSIDateFormat = true; } // // Read past the end of the current set of alpha characters, // as MapDayMonthToDword only peeks at a few characters // do { index++; } while (index < DateString.Length && ((lpInputBuffer[index] >= 'A' && lpInputBuffer[index] <= 'Z') || (lpInputBuffer[index] >= 'a' && lpInputBuffer[index] <= 'z'))); i++; // next token } else { // // For the generic case its either a space, comma, semi-colon, etc. // the point is we really don't care, nor do we need to waste time // worring about it (the orginal code did). The point is we // care about the actual date information, So we just advance to the // next lexume. // index++; } } // // We're finished parsing the string, now take the parsed tokens // and turn them to the actual structured information we care about. // So we build lpSysTime from the Array, using a local if none is passed in. // int year; int month; int day; int hour; int minute; int second; int millisecond; millisecond = 0; if (fIsANSIDateFormat) { day = rgdwDateParseResults[DATE_ANSI_INDEX_DAY]; month = rgdwDateParseResults[DATE_ANSI_INDEX_MONTH]; hour = rgdwDateParseResults[DATE_ANSI_INDEX_HRS]; minute = rgdwDateParseResults[DATE_ANSI_INDEX_MINS]; second = rgdwDateParseResults[DATE_ANSI_INDEX_SECS]; if (iLastLettered != DATE_ANSI_INDEX_YEAR) { year = rgdwDateParseResults[DATE_ANSI_INDEX_YEAR]; } else { // This is a fix to get around toString/toGMTstring (where the timezone is // appended at the end. (See above) year = rgdwDateParseResults[DATE_INDEX_TZ]; } } else { day = rgdwDateParseResults[DATE_1123_INDEX_DAY]; month = rgdwDateParseResults[DATE_1123_INDEX_MONTH]; year = rgdwDateParseResults[DATE_1123_INDEX_YEAR]; hour = rgdwDateParseResults[DATE_1123_INDEX_HRS]; minute = rgdwDateParseResults[DATE_1123_INDEX_MINS]; second = rgdwDateParseResults[DATE_1123_INDEX_SECS]; } // // Normalize the year, 90 == 1990, handle the year 2000, 02 == 2002 // This is Year 2000 handling folks!!! We get this wrong and // we all look bad. // if (year < 100) { year += ((year < 80) ? 2000 : 1900); } // // if we got misformed time, then plug in the current time // !lpszHrs || !lpszMins || !lpszSec // if ((i < 4) || (day > 31) || (hour > 23) || (minute > 59) || (second > 59)) { fRet = false; goto quit; } // // Now do the DateTime conversion // dtOut = new DateTime(year, month, day, hour, minute, second, millisecond); // // we want the system time to be accurate. This is _suhlow_ // The time passed in is in the local time zone; we have to convert this into GMT. // if (iLastLettered == DATE_ANSI_INDEX_YEAR) { // this should be an unusual case. dtOut = dtOut.ToUniversalTime(); } // // If we have an Offset to another Time Zone // then convert to appropriate GMT time // if ((i > DATE_INDEX_TZ && rgdwDateParseResults[DATE_INDEX_TZ] != DATE_TOKEN_GMT)) { // // if we received +/-nnnn as offset (hhmm), modify the output FILETIME // double offset; offset = (double)rgdwDateParseResults[DATE_INDEX_TZ]; dtOut.AddHours(offset); } // In the end, we leave it all in LocalTime dtOut = dtOut.ToLocalTime(); quit: return fRet; } } }