private DataParseStatus ParseStatusLine(
byte [] statusLine,
int statusLineLength,
ref int bytesParsed,
ref int [] statusLineInts,
ref string statusDescription,
ref int statusState,
ref WebParseError parseError) {
GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", statusLineLength.ToString(NumberFormatInfo.InvariantInfo) + ", " + bytesParsed.ToString(NumberFormatInfo.InvariantInfo) +", " +statusState.ToString(NumberFormatInfo.InvariantInfo));
GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine");
GlobalLog.Assert((statusLineLength - bytesParsed) >= 0, "Connection#{0}::ParseStatusLine()|(statusLineLength - bytesParsed) < 0", ValidationHelper.HashString(this));
//GlobalLog.Dump(statusLine, bytesParsed, statusLineLength);
DataParseStatus parseStatus = DataParseStatus.Done;
int statusLineSize = 0;
int startIndexStatusDescription = -1;
int lastUnSpaceIndex = 0;
//
// While walking the Status Line looking for terminating \r\n,
// we extract the Major.Minor Versions and Status Code in that order.
// text and spaces will lie between/before/after the three numbers
// but the idea is to remember which number we're calculating based on a numeric state
// If all goes well the loop will churn out an array with the 3 numbers plugged in as DWORDs
//
while ((bytesParsed < statusLineLength) && (statusLine[bytesParsed] != '\r') && (statusLine[bytesParsed] != '\n')) {
// below should be wrapped in while (response[i] != ' ') to be more robust???
switch (statusState) {
case BeforeVersionNumbers:
if (statusLine[bytesParsed] == '/') {
//INET_ASSERT(statusState == BeforeVersionNumbers);
statusState++; // = MajorVersionNumber
}
else if (statusLine[bytesParsed] == ' ') {
statusState = StatusCodeNumber;
}
break;
case MajorVersionNumber:
if (statusLine[bytesParsed] == '.') {
//INET_ASSERT(statusState == MajorVersionNumber);
statusState++; // = MinorVersionNumber
break;
}
// fall through
goto case MinorVersionNumber;
case MinorVersionNumber:
if (statusLine[bytesParsed] == ' ') {
//INET_ASSERT(statusState == MinorVersionNumber);
statusState++; // = StatusCodeNumber
break;
}
// fall through
goto case StatusCodeNumber;
case StatusCodeNumber:
if (Char.IsDigit((char)statusLine[bytesParsed])) {
int val = statusLine[bytesParsed] - '0';
statusLineInts[statusState] = statusLineInts[statusState] * 10 + val;
}
else if (statusLineInts[StatusCodeNumber] > 0) {
//
// we eat spaces before status code is found,
// once we have the status code we can go on to the next
// state on the next non-digit. This is done
// to cover cases with several spaces between version
// and the status code number.
//
statusState++; // = AfterStatusCode
break;
}
else if (!Char.IsWhiteSpace((char) statusLine[bytesParsed])) {
statusLineInts[statusState] = (int)-1;
}
break;
case AfterStatusCode:
if (statusLine[bytesParsed] != ' ') {
lastUnSpaceIndex = bytesParsed;
if (startIndexStatusDescription == -1) {
startIndexStatusDescription = bytesParsed;
}
}
break;
}
++bytesParsed;
if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
parseStatus = DataParseStatus.DataTooBig;
goto quit;
}
}
statusLineSize = bytesParsed;
// add to Description if already partialy parsed
if (startIndexStatusDescription != -1) {
statusDescription +=
WebHeaderCollection.HeaderEncoding.GetString(
statusLine,
startIndexStatusDescription,
lastUnSpaceIndex - startIndexStatusDescription + 1 );
}
if (bytesParsed == statusLineLength) {
//
// response now points one past the end of the buffer. We may be looking
// over the edge...
//
// if we're at the end of the connection then the server sent us an
// incorrectly formatted response. Probably an error.
//
// Otherwise its a partial response. We need more
//
parseStatus = DataParseStatus.NeedMoreData;
//
// if we really hit the end of the response then update the amount of
// headers scanned
//
GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", parseStatus.ToString());
return parseStatus;
}
while ((bytesParsed < statusLineLength)
&& ((statusLine[bytesParsed] == '\r') || (statusLine[bytesParsed] == ' '))) {
++bytesParsed;
if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
parseStatus = DataParseStatus.DataTooBig;
goto quit;
}
}
if (bytesParsed == statusLineLength) {
//
// hit end of buffer without finding LF
//
parseStatus = DataParseStatus.NeedMoreData;
goto quit;
}
else if (statusLine[bytesParsed] == '\n') {
++bytesParsed;
if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
parseStatus = DataParseStatus.DataTooBig;
goto quit;
}
//
// if we found the empty line then we are done
//
parseStatus = DataParseStatus.Done;
}
//
// Now we have our parsed header to add to the array
//
quit:
if (parseStatus == DataParseStatus.Done && statusState != AfterStatusCode) {
// need to handle the case where we parse the StatusCode,
// but didn't get a status Line, and there was no space afer it.
if (statusState != StatusCodeNumber || statusLineInts[StatusCodeNumber] <= 0) {
//
// we're done with the status line, if we didn't parse all the
// numbers needed this is invalid protocol on the server
//
parseStatus = DataParseStatus.Invalid;
}
}
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine() StatusCode:" + statusLineInts[StatusCodeNumber] + " MajorVersionNumber:" + statusLineInts[MajorVersionNumber] + " MinorVersionNumber:" + statusLineInts[MinorVersionNumber] + " StatusDescription:" + ValidationHelper.ToString(statusDescription));
GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", parseStatus.ToString());
if (parseStatus == DataParseStatus.Invalid) {
parseError.Section = WebParseErrorSection.ResponseStatusLine;
parseError.Code = WebParseErrorCode.Generic;
}
return parseStatus;
}