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