private static unsafe DataParseStatus ParseStatusLineStrict(
byte[] statusLine,
int statusLineLength,
ref int bytesParsed,
ref int statusState,
StatusLineValues statusLineValues,
int maximumHeaderLength,
ref int totalBytesParsed,
ref WebParseError parseError)
{
GlobalLog.Enter("Connection::ParseStatusLineStrict", statusLineLength.ToString(NumberFormatInfo.InvariantInfo) + ", " + bytesParsed.ToString(NumberFormatInfo.InvariantInfo) + ", " + statusState.ToString(NumberFormatInfo.InvariantInfo));
GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection::ParseStatusLineStrict");
GlobalLog.Assert((statusLineLength - bytesParsed) >= 0, "Connection::ParseStatusLineStrict()|(statusLineLength - bytesParsed) < 0");
GlobalLog.Assert(maximumHeaderLength <= 0 || totalBytesParsed <= maximumHeaderLength, "Connection::ParseStatusLineStrict()|Headers already read exceeds limit.");
// Remember where we started.
int initialBytesParsed = bytesParsed;
// Set up parsing status with what will happen if we exceed the buffer.
DataParseStatus parseStatus = DataParseStatus.DataTooBig;
int effectiveMax = maximumHeaderLength <= 0 ? int.MaxValue : (maximumHeaderLength - totalBytesParsed + bytesParsed);
if (statusLineLength < effectiveMax)
{
parseStatus = DataParseStatus.NeedMoreData;
effectiveMax = statusLineLength;
}
// sanity check
if (bytesParsed >= effectiveMax)
goto quit;
fixed (byte* byteBuffer = statusLine)
{
// Use this switch to jump midway into the action. They all fall through until the end of the buffer is reached or
// the status line is fully parsed.
switch (statusState)
{
case BeforeVersionNumbers:
// This takes advantage of the fact that this token must be the very first thing in the response.
while (totalBytesParsed - initialBytesParsed + bytesParsed < BeforeVersionNumberBytes.Length)
{
if ((byte)BeforeVersionNumberBytes[totalBytesParsed - initialBytesParsed + bytesParsed] != byteBuffer[bytesParsed])
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
if(++bytesParsed == effectiveMax)
goto quit;
}
// When entering the MajorVersionNumber phase, make sure at least one digit is present.
if (byteBuffer[bytesParsed] == '.')
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
statusState = MajorVersionNumber;
goto case MajorVersionNumber;
case MajorVersionNumber:
while (byteBuffer[bytesParsed] != '.')
{
if (byteBuffer[bytesParsed] < '0' || byteBuffer[bytesParsed] > '9')
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
statusLineValues.MajorVersion = statusLineValues.MajorVersion * 10 + byteBuffer[bytesParsed] - '0';
if (++bytesParsed == effectiveMax)
goto quit;
}
// Need visibility past the dot.
if (bytesParsed + 1 == effectiveMax)
goto quit;
bytesParsed++;
// When entering the MinorVersionNumber phase, make sure at least one digit is present.
if (byteBuffer[bytesParsed] == ' ')
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
statusState = MinorVersionNumber;
goto case MinorVersionNumber;
case MinorVersionNumber:
// Only a single SP character is allowed to delimit fields in the status line.
while (byteBuffer[bytesParsed] != ' ')
{
if (byteBuffer[bytesParsed] < '0' || byteBuffer[bytesParsed] > '9')
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
statusLineValues.MinorVersion = statusLineValues.MinorVersion * 10 + byteBuffer[bytesParsed] - '0';
if (++bytesParsed == effectiveMax)
goto quit;
}
statusState = StatusCodeNumber;
// Start the status code out as "1". This will effectively add 1000 to the code. It's used to count
// the number of digits to make sure it's three. At the end, subtract 1000.
statusLineValues.StatusCode = 1;
// Move past the space.
if (++bytesParsed == effectiveMax)
goto quit;
goto case StatusCodeNumber;
case StatusCodeNumber:
// RFC2616 says codes with an unrecognized first digit
// should be rejected. We're allowing the application to define their own "understanding" of
// 0, 6, 7, 8, and 9xx codes.
while (byteBuffer[bytesParsed] >= '0' && byteBuffer[bytesParsed] <= '9')
{
// Make sure it isn't too big. The leading '1' will be removed after three digits are read.
if (statusLineValues.StatusCode >= 1000)
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
statusLineValues.StatusCode = statusLineValues.StatusCode * 10 + byteBuffer[bytesParsed] - '0';
if (++bytesParsed == effectiveMax)
goto quit;
}
// Make sure there was enough, and exactly one space.
if (byteBuffer[bytesParsed] != ' ' || statusLineValues.StatusCode < 1000)
{
if(byteBuffer[bytesParsed] == '\r' && statusLineValues.StatusCode >= 1000){
statusLineValues.StatusCode -= 1000;
statusState = AfterCarriageReturn;
if (++bytesParsed == effectiveMax)
goto quit;
goto case AfterCarriageReturn;
}
parseStatus = DataParseStatus.Invalid;
goto quit;
}
// Remove the extra leading 1.
statusLineValues.StatusCode -= 1000;
statusState = AfterStatusCode;
// Move past the space.
if (++bytesParsed == effectiveMax)
goto quit;
goto case AfterStatusCode;
case AfterStatusCode:
{
// Check for shortcuts.
if (statusLineValues.StatusDescription == null)
{
foreach (string s in s_ShortcutStatusDescriptions)
{
if (bytesParsed < effectiveMax - s.Length && byteBuffer[bytesParsed] == (byte) s[0])
{
int i;
byte *pBuffer = byteBuffer + bytesParsed + 1;
for(i = 1; i < s.Length; i++)
if (*(pBuffer++) != (byte) s[i])
break;
if (i == s.Length)
{
statusLineValues.StatusDescription = s;
bytesParsed += s.Length;
}
break;
}
}
}
int beginning = bytesParsed;
while (byteBuffer[bytesParsed] != '\r')
{
if (byteBuffer[bytesParsed] < ' ' || byteBuffer[bytesParsed] == 127)
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
if (++bytesParsed == effectiveMax)
{
string s = WebHeaderCollection.HeaderEncoding.GetString(byteBuffer + beginning, bytesParsed - beginning);
if (statusLineValues.StatusDescription == null)
statusLineValues.StatusDescription = s;
else
statusLineValues.StatusDescription += s;
goto quit;
}
}
if (bytesParsed > beginning)
{
string s = WebHeaderCollection.HeaderEncoding.GetString(byteBuffer + beginning, bytesParsed - beginning);
if (statusLineValues.StatusDescription == null)
statusLineValues.StatusDescription = s;
else
statusLineValues.StatusDescription += s;
}
else if (statusLineValues.StatusDescription == null)
{
statusLineValues.StatusDescription = "";
}
statusState = AfterCarriageReturn;
// Move past the CR.
if (++bytesParsed == effectiveMax)
goto quit;
goto case AfterCarriageReturn;
}
case AfterCarriageReturn:
if (byteBuffer[bytesParsed] != '\n')
{
parseStatus = DataParseStatus.Invalid;
goto quit;
}
parseStatus = DataParseStatus.Done;
bytesParsed++;
break;
}
}
quit:
totalBytesParsed += bytesParsed - initialBytesParsed;
GlobalLog.Print("Connection::ParseStatusLineStrict() StatusCode:" + statusLineValues.StatusCode + " MajorVersionNumber:" + statusLineValues.MajorVersion + " MinorVersionNumber:" + statusLineValues.MinorVersion + " StatusDescription:" + ValidationHelper.ToString(statusLineValues.StatusDescription));
GlobalLog.Leave("Connection::ParseStatusLineStrict", parseStatus.ToString());
if (parseStatus == DataParseStatus.Invalid) {
parseError.Section = WebParseErrorSection.ResponseStatusLine;
parseError.Code = WebParseErrorCode.Generic;
}
return parseStatus;
}