// ParseHeaders - // Routine Description: // // This code is optimized for the case in which all the headers fit in the buffer. // we support multiple re-entrance, but we won't save intermediate // state, we will just roll back all the parsing done for the current header if we can't // parse a whole one (including multiline) or decide something else ("invalid data" or "done parsing"). // // we're going to cycle through the loop until we // // 1) find an HTTP violation (in this case we return DataParseStatus.Invalid) // 2) we need more data (in this case we return DataParseStatus.NeedMoreData) // 3) we found the end of the headers and the beginning of the entity body (in this case we return DataParseStatus.Done) // // // Arguments: // // buffer - buffer containing the data to be parsed // size - size of the buffer // unparsed - offset of data yet to be parsed // // Return Value: // // DataParseStatus - status of parsing // // Revision: // // 02/13/2001 rewrote the method from scratch. // // BreakPoint: // // b system.dll!System.Net.WebHeaderCollection::ParseHeaders internal unsafe DataParseStatus ParseHeaders(byte[] buffer, int size, ref int unparsed, ref int totalResponseHeadersLength, int maximumResponseHeadersLength, ref WebParseError parseError) { fixed (byte* byteBuffer = buffer) { char ch; // quick check in the boundaries (as we use unsafe pointer) if (buffer.Length < size) { return DataParseStatus.NeedMoreData; } int headerNameStartOffset = -1; int headerNameEndOffset = -1; int headerValueStartOffset = -1; int headerValueEndOffset = -1; int numberOfLf = -1; int index = unparsed; bool spaceAfterLf; string headerMultiLineValue; string headerName; string headerValue; // we need this because this method is entered multiple times. int localTotalResponseHeadersLength = totalResponseHeadersLength; WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic; DataParseStatus parseStatus = DataParseStatus.Invalid; // // according to RFC216 a header can have the following syntax: // // message-header = field-name ":" [ field-value ] // field-name = token // field-value = *( field-content | LWS ) // field-content = <the OCTETs making up the field-value and consisting of either *TEXT or combinations of token, separators, and quoted-string> // TEXT = <any OCTET except CTLs, but including LWS> // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> // SP = <US-ASCII SP, space (32)> // HT = <US-ASCII HT, horizontal-tab (9)> // CR = <US-ASCII CR, carriage return (13)> // LF = <US-ASCII LF, linefeed (10)> // LWS = [CR LF] 1*( SP | HT ) // CHAR = <any US-ASCII character (octets 0 - 127)> // token = 1*<any CHAR except CTLs or separators> // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) // qdtext = <any TEXT except <">> // quoted-pair = "\" CHAR // // // At each iteration of the following loop we expect to parse a single HTTP header entirely. // for (; ; ) { // // trim leading whitespaces (LWS) just for extra robustness, in fact if there are leading white spaces then: // 1) it could be that after the status line we might have spaces. handle this. // 2) this should have been detected to be a multiline header so there'll be no spaces and we'll spend some time here. // headerName = string.Empty; headerValue = string.Empty; spaceAfterLf = false; headerMultiLineValue = null; if (Count == 0) { // // so, restrict this extra trimming only on the first header line // while (index < size) { ch = (char)byteBuffer[index]; if (ch == ' ' || ch == '\t') { ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (index == size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } } // // what we have here is the beginning of a new header // headerNameStartOffset = index; while (index < size) { ch = (char)byteBuffer[index]; if (ch != ':' && ch != '\n') { if (ch > ' ') { // // if there's an illegal character we should return DataParseStatus.Invalid // instead we choose to be flexible, try to trim it, but include it in the string // headerNameEndOffset = index; } ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { if (ch == ':') { ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } break; } } if (index == size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } startOfValue: // // skip all [' ','\t','\r','\n'] characters until HeaderValue starts // if we didn't find any headers yet, we set numberOfLf to 1 // so that we take the '\n' from the status line into account // numberOfLf = (Count == 0 && headerNameEndOffset < 0) ? 1 : 0; while (index < size && numberOfLf < 2) { ch = (char)byteBuffer[index]; if (ch <= ' ') { if (ch == '\n') { numberOfLf++; // In this case, need to check for a space. if (numberOfLf == 1) { if (index + 1 == size) { // // we reached the end of the buffer. ask for more data. // need to be able to peek after the \n and see if there's some space. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } spaceAfterLf = (char)byteBuffer[index + 1] == ' ' || (char)byteBuffer[index + 1] == '\t'; } } ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (numberOfLf == 2 || (numberOfLf == 1 && !spaceAfterLf)) { // // if we've counted two '\n' we got at the end of the headers even if we're past the end of the buffer // if we've counted one '\n' and the first character after that was a ' ' or a '\t' // no matter if we found a ':' or not, treat this as an empty header name. // goto addHeader; } if (index == size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } headerValueStartOffset = index; while (index < size) { ch = (char)byteBuffer[index]; if (ch != '\n') { if (ch > ' ') { headerValueEndOffset = index; } ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (index == size) { // // we reached the end of the buffer. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } // // at this point we found either a '\n' or the end of the headers // hence we are at the end of the Header Line. 4 options: // 1) need more data // 2) if we find two '\n' => end of headers // 3) if we find one '\n' and a ' ' or a '\t' => multiline header // 4) if we find one '\n' and a valid char => next header // numberOfLf = 0; while (index < size && numberOfLf < 2) { ch = (char)byteBuffer[index]; if (ch == '\r' || ch == '\n') { if (ch == '\n') { numberOfLf++; } ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } } else { break; } } if (index == size && numberOfLf < 2) { // // we reached the end of the buffer but not of the headers. ask for more data. // parseStatus = DataParseStatus.NeedMoreData; goto quit; } addHeader: if (headerValueStartOffset >= 0 && headerValueStartOffset > headerNameEndOffset && headerValueEndOffset >= headerValueStartOffset) { // // Encoding fastest way to build the UNICODE string off the byte[] // headerValue = HeaderEncoding.GetString(byteBuffer + headerValueStartOffset, headerValueEndOffset - headerValueStartOffset + 1); } // // if we got here from the beginning of the for loop, headerMultiLineValue will be null // otherwise it will contain the headerValue constructed for the multiline header // add this line as well to it, separated by a single space // headerMultiLineValue = (headerMultiLineValue == null ? headerValue : headerMultiLineValue + " " + headerValue); if (index < size && numberOfLf == 1) { ch = (char)byteBuffer[index]; if (ch == ' ' || ch == '\t') { // // since we found only one Lf and the next header line begins with a Lws, // this is the beginning of a multiline header. // parse the next line into headerValue later it will be added to headerMultiLineValue // ++index; if (maximumResponseHeadersLength >= 0 && ++localTotalResponseHeadersLength >= maximumResponseHeadersLength) { parseStatus = DataParseStatus.DataTooBig; goto quit; } goto startOfValue; } } if (headerNameStartOffset >= 0 && headerNameEndOffset >= headerNameStartOffset) { // // Encoding is the fastest way to build the UNICODE string off the byte[] // headerName = HeaderEncoding.GetString(byteBuffer + headerNameStartOffset, headerNameEndOffset - headerNameStartOffset + 1); } // // now it's finally safe to add the header if we have a name for it // if (headerName.Length > 0) { // // the base clasee will check for pre-existing headerValue and append // it using commas as indicated in the RFC // GlobalLog.Print("WebHeaderCollection::ParseHeaders() calling AddInternal() key:[" + headerName + "], value:[" + headerMultiLineValue + "]"); AddInternal(headerName, headerMultiLineValue); } // // and update unparsed // totalResponseHeadersLength = localTotalResponseHeadersLength; unparsed = index; if (numberOfLf == 2) { parseStatus = DataParseStatus.Done; goto quit; } } quit: // for (;;) GlobalLog.Leave("WebHeaderCollection::ParseHeaders() returning parseStatus:" + parseStatus.ToString()); if (parseStatus == DataParseStatus.Invalid) { parseError.Section = WebParseErrorSection.ResponseHeader; parseError.Code = parseErrorCode; } return parseStatus; } }
internal unsafe DataParseStatus ParseHeadersStrict(byte[] buffer, int size, ref int unparsed, ref int totalResponseHeadersLength, int maximumResponseHeadersLength, ref WebParseError parseError) { GlobalLog.Enter("WebHeaderCollection::ParseHeadersStrict(): size:" + size.ToString() + ", unparsed:" + unparsed.ToString() + " buffer:[" + Encoding.ASCII.GetString(buffer, unparsed, Math.Min(256, size - unparsed)) + "]"); WebParseErrorCode parseErrorCode = WebParseErrorCode.Generic; DataParseStatus parseStatus = DataParseStatus.Invalid; int i = unparsed; RfcChar ch; int effectiveSize = maximumResponseHeadersLength <= 0 ? Int32.MaxValue : maximumResponseHeadersLength - totalResponseHeadersLength + i; DataParseStatus sizeError = DataParseStatus.DataTooBig; if (size < effectiveSize) { effectiveSize = size; sizeError = DataParseStatus.NeedMoreData; } // Verify the size. if (i >= effectiveSize) { parseStatus = sizeError; goto quit; } fixed (byte* byteBuffer = buffer) { while (true) { // If this is CRLF, actually we're done. if (byteBuffer[i] == '\r') { if (++i == effectiveSize) { parseStatus = sizeError; goto quit; } if (byteBuffer[i++] == '\n') { totalResponseHeadersLength += i - unparsed; unparsed = i; parseStatus = DataParseStatus.Done; goto quit; } parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.CrLfError; goto quit; } // Find the header name; only regular characters allowed. int iBeginName = i; for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.Reg; i++) ; if (i == effectiveSize) { parseStatus = sizeError; goto quit; } if (i == iBeginName) { parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.InvalidHeaderName; goto quit; } // Read to a colon. int iEndName = i - 1; int crlf = 0; // 1 = cr, 2 = crlf for (; i < effectiveSize && (ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) != RfcChar.Colon; i++) { switch (ch) { case RfcChar.WS: if (crlf == 1) { break; } crlf = 0; continue; case RfcChar.CR: if (crlf == 0) { crlf = 1; continue; } break; case RfcChar.LF: if (crlf == 1) { crlf = 2; continue; } break; } parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.CrLfError; goto quit; } if (i == effectiveSize) { parseStatus = sizeError; goto quit; } if (crlf != 0) { parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.IncompleteHeaderLine; goto quit; } // Skip the colon. if (++i == effectiveSize) { parseStatus = sizeError; goto quit; } // Read the value. crlf = 3 means in the whitespace after a CRLF int iBeginValue = -1; int iEndValue = -1; StringBuilder valueAccumulator = null; for (; i < effectiveSize && ((ch = byteBuffer[i] > 127 ? RfcChar.High : RfcCharMap[byteBuffer[i]]) == RfcChar.WS || crlf != 2); i++) { switch (ch) { case RfcChar.WS: if (crlf == 1) { break; } if (crlf == 2) { crlf = 3; } continue; case RfcChar.CR: if (crlf == 0) { crlf = 1; continue; } break; case RfcChar.LF: if (crlf == 1) { crlf = 2; continue; } break; case RfcChar.High: case RfcChar.Colon: case RfcChar.Delim: case RfcChar.Reg: if (crlf == 1) { break; } if (crlf == 3) { crlf = 0; if (iBeginValue != -1) { string s = HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1); if (valueAccumulator == null) { valueAccumulator = new StringBuilder(s, s.Length * 5); } else { valueAccumulator.Append(" "); valueAccumulator.Append(s); } } iBeginValue = -1; } if (iBeginValue == -1) { iBeginValue = i; } iEndValue = i; continue; } parseStatus = DataParseStatus.Invalid; parseErrorCode = WebParseErrorCode.CrLfError; goto quit; } if (i == effectiveSize) { parseStatus = sizeError; goto quit; } // Make the value. string sValue = iBeginValue == -1 ? "" : HeaderEncoding.GetString(byteBuffer + iBeginValue, iEndValue - iBeginValue + 1); if (valueAccumulator != null) { if (sValue.Length != 0) { valueAccumulator.Append(" "); valueAccumulator.Append(sValue); } sValue = valueAccumulator.ToString(); } // Make the name. See if it's a common header first. string sName = null; int headerNameLength = iEndName - iBeginName + 1; if (m_CommonHeaders != null) { int iHeader = s_CommonHeaderHints[byteBuffer[iBeginName] & 31]; if (iHeader >= 0) { while (true) { string s = s_CommonHeaderNames[iHeader++]; // Not found if we get to a shorter header or one with a different first character. if (s.Length < headerNameLength || CaseInsensitiveAscii.AsciiToLower[byteBuffer[iBeginName]] != CaseInsensitiveAscii.AsciiToLower[s[0]]) break; // Keep looking if the common header is too long. if (s.Length > headerNameLength) continue; int j; byte* pBuffer = byteBuffer + iBeginName + 1; for (j = 1; j < s.Length; j++) { // Avoid the case-insensitive compare in the common case where they match. if (*(pBuffer++) != s[j] && CaseInsensitiveAscii.AsciiToLower[*(pBuffer - 1)] != CaseInsensitiveAscii.AsciiToLower[s[j]]) break; } if (j == s.Length) { // Set it to the appropriate index. m_NumCommonHeaders++; iHeader--; if (m_CommonHeaders[iHeader] == null) { m_CommonHeaders[iHeader] = sValue; } else { // Don't currently handle combining multiple header instances in the common header case. // Nothing to do but punt them all to the NameValueCollection. NormalizeCommonHeaders(); AddInternalNotCommon(s, sValue); } sName = s; break; } } } } // If it wasn't a common header, add it to the hash. if (sName == null) { sName = HeaderEncoding.GetString(byteBuffer + iBeginName, headerNameLength); AddInternalNotCommon(sName, sValue); } totalResponseHeadersLength += i - unparsed; unparsed = i; } } quit: GlobalLog.Leave("WebHeaderCollection::ParseHeadersStrict() returning parseStatus:" + parseStatus.ToString()); if (parseStatus == DataParseStatus.Invalid) { parseError.Section = WebParseErrorSection.ResponseHeader; parseError.Code = parseErrorCode; } return parseStatus; }
internal DataParseStatus ParseHeaders(byte[] byteBuffer, int size, ref int unparsed, ref int totalResponseHeadersLength, int maximumResponseHeadersLength, ref WebParseError parseError) { char ch; string str2; if (byteBuffer.Length < size) { return DataParseStatus.NeedMoreData; } int index = -1; int num2 = -1; int num3 = -1; int num4 = -1; int num5 = -1; int num6 = unparsed; int num7 = totalResponseHeadersLength; WebParseErrorCode generic = WebParseErrorCode.Generic; DataParseStatus invalid = DataParseStatus.Invalid; Label_0023: str2 = string.Empty; string str3 = string.Empty; bool flag = false; string str = null; if (this.Count == 0) { while (num6 < size) { ch = (char) byteBuffer[num6]; if ((ch != ' ') && (ch != '\t')) { break; } num6++; if ((maximumResponseHeadersLength >= 0) && (++num7 >= maximumResponseHeadersLength)) { invalid = DataParseStatus.DataTooBig; goto Label_02E5; } } if (num6 == size) { invalid = DataParseStatus.NeedMoreData; goto Label_02E5; } } index = num6; while (num6 < size) { ch = (char) byteBuffer[num6]; if ((ch != ':') && (ch != '\n')) { if (ch > ' ') { num2 = num6; } num6++; if ((maximumResponseHeadersLength < 0) || (++num7 < maximumResponseHeadersLength)) { continue; } invalid = DataParseStatus.DataTooBig; } else { if (ch != ':') { break; } num6++; if ((maximumResponseHeadersLength < 0) || (++num7 < maximumResponseHeadersLength)) { break; } invalid = DataParseStatus.DataTooBig; } goto Label_02E5; } if (num6 == size) { invalid = DataParseStatus.NeedMoreData; goto Label_02E5; } Label_00EF:; num5 = ((this.Count == 0) && (num2 < 0)) ? 1 : 0; while ((num6 < size) && (num5 < 2)) { ch = (char) byteBuffer[num6]; if (ch > ' ') { break; } if (ch == '\n') { num5++; if (num5 == 1) { if ((num6 + 1) == size) { invalid = DataParseStatus.NeedMoreData; goto Label_02E5; } flag = (byteBuffer[num6 + 1] == 32) || (byteBuffer[num6 + 1] == 9); } } num6++; if ((maximumResponseHeadersLength >= 0) && (++num7 >= maximumResponseHeadersLength)) { invalid = DataParseStatus.DataTooBig; goto Label_02E5; } } if ((num5 != 2) && ((num5 != 1) || flag)) { if (num6 == size) { invalid = DataParseStatus.NeedMoreData; goto Label_02E5; } num3 = num6; while (num6 < size) { ch = (char) byteBuffer[num6]; if (ch == '\n') { break; } if (ch > ' ') { num4 = num6; } num6++; if ((maximumResponseHeadersLength >= 0) && (++num7 >= maximumResponseHeadersLength)) { invalid = DataParseStatus.DataTooBig; goto Label_02E5; } } if (num6 == size) { invalid = DataParseStatus.NeedMoreData; goto Label_02E5; } num5 = 0; while ((num6 < size) && (num5 < 2)) { ch = (char) byteBuffer[num6]; if ((ch != '\r') && (ch != '\n')) { break; } if (ch == '\n') { num5++; } num6++; if ((maximumResponseHeadersLength >= 0) && (++num7 >= maximumResponseHeadersLength)) { invalid = DataParseStatus.DataTooBig; goto Label_02E5; } } if ((num6 == size) && (num5 < 2)) { invalid = DataParseStatus.NeedMoreData; goto Label_02E5; } } if (((num3 >= 0) && (num3 > num2)) && (num4 >= num3)) { str3 = Encoding.UTF8.GetString(byteBuffer, num3, (num4 - num3) + 1); } str = (str == null) ? str3 : (str + " " + str3); if ((num6 < size) && (num5 == 1)) { switch (((char) byteBuffer[num6])) { case ' ': case '\t': num6++; if ((maximumResponseHeadersLength < 0) || (++num7 < maximumResponseHeadersLength)) { goto Label_00EF; } invalid = DataParseStatus.DataTooBig; goto Label_02E5; } } if ((index >= 0) && (num2 >= index)) { str2 = Encoding.UTF8.GetString(byteBuffer, index, (num2 - index) + 1); } if (str2.Length > 0) { this.Add(str2, str); } totalResponseHeadersLength = num7; unparsed = num6; if (num5 != 2) { goto Label_0023; } invalid = DataParseStatus.Done; Label_02E5: if (invalid == DataParseStatus.Invalid) { parseError.Section = WebParseErrorSection.ResponseHeader; parseError.Code = generic; } return invalid; }