The Labs \ Source Viewer \ SSCLI \ System.Net \ HttpDateParse

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

Developer Fusion