487 lines
17 KiB
C#
487 lines
17 KiB
C#
// The MIT License (MIT)
|
|
|
|
// Copyright (c) .NET Foundation and Contributors
|
|
|
|
// All rights reserved.
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
// Slightly modified from:
|
|
// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Net/HttpDateParser.cs
|
|
|
|
using System;
|
|
using System.Globalization;
|
|
|
|
namespace Proxima
|
|
{
|
|
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_Microsoft = 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);
|
|
|
|
|
|
//
|
|
// MAKE_UPPER - 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)
|
|
//
|
|
|
|
private
|
|
static
|
|
char
|
|
MAKE_UPPER(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
|
|
|
|
--*/
|
|
|
|
private
|
|
static
|
|
int
|
|
MapDayMonthToDword(
|
|
char [] lpszDay,
|
|
int index
|
|
) {
|
|
switch (MAKE_UPPER(lpszDay[index])) { // make uppercase
|
|
case 'A':
|
|
switch (MAKE_UPPER(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 (MAKE_UPPER(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 (MAKE_UPPER(lpszDay[index+1])) {
|
|
case 'O':
|
|
return DATE_TOKEN_MONDAY;
|
|
case 'A':
|
|
switch (MAKE_UPPER(lpszDay[index+2])) {
|
|
case 'R':
|
|
return DATE_TOKEN_Microsoft;
|
|
case 'Y':
|
|
return DATE_TOKEN_MAY;
|
|
}
|
|
|
|
// fall through to error
|
|
break;
|
|
}
|
|
|
|
return DATE_TOKEN_ERROR;
|
|
|
|
case 'N':
|
|
return DATE_TOKEN_NOVEMBER;
|
|
|
|
case 'J':
|
|
|
|
switch (MAKE_UPPER(lpszDay[index+1])) {
|
|
case 'A':
|
|
return DATE_TOKEN_JANUARY;
|
|
|
|
case 'U':
|
|
switch (MAKE_UPPER(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 (MAKE_UPPER(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 (MAKE_UPPER(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
|
|
|
|
--*/
|
|
public
|
|
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;
|
|
}
|
|
}
|
|
} |