private DataParseStatus ParseResponseData(ref ConnectionReturnResult returnResult, out bool requestDone, out CoreResponseData continueResponseData)
{
GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()");
DataParseStatus parseStatus = DataParseStatus.NeedMoreData;
DataParseStatus parseSubStatus;
// Indicates whether or not at least one whole request was processed in this loop.
// (i.e. Whether ParseStreamData() was called.
requestDone = false;
continueResponseData = null;
// loop in case of multiple sets of headers or streams,
// that may be generated due to a pipelined response
// Invariants: at the start of this loop, m_BytesRead
// is the number of bytes in the buffer, and m_BytesScanned
// is how many bytes of the buffer we've consumed so far.
// and the m_ReadState var will be updated at end of
// each code path, call to this function to reflect,
// the state, or error condition of the parsing of data
//
// We use the following variables in the code below:
//
// m_ReadState - tracks the current state of our Parsing in a
// response. z.B.
// Start - initial start state and begining of response
// StatusLine - the first line sent in response, include status code
// Headers - \r\n delimiated Header parsing until we find entity body
// Data - Entity Body parsing, if we have all data, we create stream directly
//
// m_ResponseData - An object used to gather Stream, Headers, and other
// tidbits so that a request/Response can receive this data when
// this code is finished processing
//
// m_ReadBuffer - Of course the buffer of data we are parsing from
//
// m_BytesScanned - The bytes scanned in this buffer so far,
// since its always assumed that parse to completion, this
// var becomes ended of known data at the end of this function,
// based off of 0
//
// m_BytesRead - The total bytes read in buffer, should be const,
// till its updated at end of function.
//
//
// Now attempt to parse the data,
// we first parse status line,
// then read headers,
// and finally transfer results to a new stream, and tell request
//
switch (m_ReadState) {
case ReadState.Start:
if (m_CurrentRequest == null)
{
lock (this)
{
if (m_WriteList.Count == 0 || ((m_CurrentRequest = m_WriteList[0] as HttpWebRequest) == null))
{
m_ParseError.Section = WebParseErrorSection.Generic;
m_ParseError.Code = WebParseErrorCode.Generic;
parseStatus = DataParseStatus.Invalid;
break;
}
}
}
m_MaximumResponseHeadersLength = m_CurrentRequest.MaximumResponseHeadersLength * 1024;
m_ResponseData = new CoreResponseData();
m_ReadState = ReadState.StatusLine;
m_TotalResponseHeadersLength = 0;
InitializeParseStatusLine();
goto case ReadState.StatusLine;
case ReadState.StatusLine:
//
// Reads HTTP status response line
//
if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
{
// This one uses an array to store the parsed values in. Marshal between this legacy way.
int[] statusInts = new int[] { 0, m_StatusLineValues.MajorVersion, m_StatusLineValues.MinorVersion, m_StatusLineValues.StatusCode };
if (m_StatusLineValues.StatusDescription == null)
m_StatusLineValues.StatusDescription = "";
parseSubStatus = ParseStatusLine(
m_ReadBuffer, // buffer we're working with
m_BytesRead, // total bytes read so far
ref m_BytesScanned, // index off of what we've scanned
ref statusInts,
ref m_StatusLineValues.StatusDescription,
ref m_StatusState,
ref m_ParseError);
m_StatusLineValues.MajorVersion = statusInts[1];
m_StatusLineValues.MinorVersion = statusInts[2];
m_StatusLineValues.StatusCode = statusInts[3];
}
else
{
parseSubStatus = ParseStatusLineStrict(
m_ReadBuffer, // buffer we're working with
m_BytesRead, // total bytes read so far
ref m_BytesScanned, // index off of what we've scanned
ref m_StatusState,
m_StatusLineValues,
m_MaximumResponseHeadersLength,
ref m_TotalResponseHeadersLength,
ref m_ParseError);
}
if (parseSubStatus == DataParseStatus.Done)
{
if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_status_line, m_StatusLineValues.MajorVersion+"."+m_StatusLineValues.MinorVersion, m_StatusLineValues.StatusCode, m_StatusLineValues.StatusDescription));
SetStatusLineParsed();
m_ReadState = ReadState.Headers;
m_ResponseData.m_ResponseHeaders = new WebHeaderCollection(WebHeaderCollectionType.HttpWebResponse);
goto case ReadState.Headers;
}
else if (parseSubStatus != DataParseStatus.NeedMoreData)
{
//
// report error - either Invalid or DataTooBig
//
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseStatusLine() parseSubStatus:" + parseSubStatus.ToString());
parseStatus = parseSubStatus;
break;
}
break; // read more data
case ReadState.Headers:
//
// Parse additional lines of header-name: value pairs
//
if (m_BytesScanned >= m_BytesRead) {
//
// we already can tell we need more data
//
break;
}
if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
{
parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeaders(
m_ReadBuffer,
m_BytesRead,
ref m_BytesScanned,
ref m_TotalResponseHeadersLength,
m_MaximumResponseHeadersLength,
ref m_ParseError);
}
else
{
parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeadersStrict(
m_ReadBuffer,
m_BytesRead,
ref m_BytesScanned,
ref m_TotalResponseHeadersLength,
m_MaximumResponseHeadersLength,
ref m_ParseError);
}
if (parseSubStatus == DataParseStatus.Invalid || parseSubStatus == DataParseStatus.DataTooBig)
{
//
// report error
//
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseHeaders() parseSubStatus:" + parseSubStatus.ToString());
parseStatus = parseSubStatus;
break;
}
else if (parseSubStatus == DataParseStatus.Done)
{
if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_headers, m_ResponseData.m_ResponseHeaders.ToString(true)));
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() DataParseStatus.Done StatusCode:" + (int)m_ResponseData.m_StatusCode + " m_BytesRead:" + m_BytesRead + " m_BytesScanned:" + m_BytesScanned);
//get the IIS server version
if(m_IISVersion == -1){
string server = m_ResponseData.m_ResponseHeaders.Server;
if (server != null && server.ToLower(CultureInfo.InvariantCulture).Contains("microsoft-iis")){
int i = server.IndexOf("/");
if(i++>0 && i <server.Length){
m_IISVersion = server[i++] - '0';
while(i < server.Length && Char.IsDigit(server[i])) {
m_IISVersion = m_IISVersion*10 + server[i++] - '0';
}
}
}
//we got a response,so if we don't know the server by now and it wasn't a 100 continue,
//we can't assume we will ever know it. IIS5 sends its server header w/ the continue
if(m_IISVersion == -1 && m_ResponseData.m_StatusCode != HttpStatusCode.Continue){
m_IISVersion = 0;
}
}
if (m_ResponseData.m_StatusCode == HttpStatusCode.Continue || m_ResponseData.m_StatusCode == HttpStatusCode.BadRequest) {
GlobalLog.Assert(m_CurrentRequest != null, "Connection#{0}::ParseResponseData()|m_CurrentRequest == null", ValidationHelper.HashString(this));
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest));
if (m_ResponseData.m_StatusCode == HttpStatusCode.BadRequest) {
// If we have a 400 and we were sending a chunked request going through to a proxy with a chunked upload,
// this proxy is a partially compliant so shut off fancy features (pipelining and chunked uploads)
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() got a 400 StatusDescription:" + m_ResponseData.m_StatusDescription);
if (ServicePoint.HttpBehaviour == HttpBehaviour.HTTP11
&& m_CurrentRequest.HttpWriteMode==HttpWriteMode.Chunked
&& m_ResponseData.m_ResponseHeaders.Via != null
&& string.Compare(m_ResponseData.m_StatusDescription, "Bad Request ( The HTTP request includes a non-supported header. Contact the Server administrator. )", StringComparison.OrdinalIgnoreCase)==0) {
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() downgrading server to HTTP11PartiallyCompliant.");
ServicePoint.HttpBehaviour = HttpBehaviour.HTTP11PartiallyCompliant;
}
}
else {
// If we have an HTTP continue, eat these headers and look
// for the 200 OK
//
// we got a 100 Continue. set this on the HttpWebRequest
//
m_CurrentRequest.Saw100Continue = true;
if (!ServicePoint.Understands100Continue) {
//
// and start expecting it again if this behaviour was turned off
//
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest) + " ServicePoint#" + ValidationHelper.HashString(ServicePoint) + " sent UNexpected 100 Continue");
ServicePoint.Understands100Continue = true;
}
//
// set Continue Ack on request.
//
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() calling SetRequestContinue()");
continueResponseData = m_ResponseData;
//if we got a 100continue we swallow it and start looking for a final response
goto case ReadState.Start;
}
}
m_ReadState = ReadState.Data;
goto case ReadState.Data;
}
// need more data,
break;
case ReadState.Data:
// (check status code for continue handling)
// 1. Figure out if its Chunked, content-length, or encoded
// 2. Takes extra data, place in stream(s)
// 3. Wake up blocked stream requests
//
// Got through one entire response
requestDone = true;
// parse and create a stream if needed
parseStatus = ParseStreamData(ref returnResult);
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() result:" + parseStatus);
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
break;
}
if (m_BytesScanned == m_BytesRead)
ClearReaderState();
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() m_ReadState:" + m_ReadState);
GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()", parseStatus.ToString());
return parseStatus;
}