/WCFWebApi/src/Microsoft.Net.Http.Formatting/System/Net/Http/InternetMessageFormatHeaderParser.cs
C# | 334 lines | 237 code | 45 blank | 52 comment | 44 complexity | 3d2749bbf88b9359ddb7c187e02be529 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
- // <copyright>
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // </copyright>
-
- namespace System.Net.Http
- {
- using System;
- using System.Diagnostics.CodeAnalysis;
- using System.Diagnostics.Contracts;
- using System.Net.Http.Headers;
- using System.Text;
-
- /// <summary>
- /// Buffer-oriented RFC 5322 style Internet Message Format parser which can be used to pass header
- /// fields used in HTTP and MIME message entities.
- /// </summary>
- internal class InternetMessageFormatHeaderParser
- {
- internal const int MinHeaderSize = 2;
-
- private int totalBytesConsumed;
- private int maxHeaderSize;
-
- private HeaderFieldState headerState;
- private HttpHeaders headers;
- private CurrentHeaderFieldStore currentHeader;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="InternetMessageFormatHeaderParser"/> class.
- /// </summary>
- /// <param name="headers">Concrete <see cref="HttpHeaders"/> instance where header fields are added as they are parsed.</param>
- /// <param name="maxHeaderSize">Maximum length of complete header containing all the individual header fields.</param>
- public InternetMessageFormatHeaderParser(HttpHeaders headers, int maxHeaderSize)
- {
- // The minimum length which would be an empty header terminated by CRLF
- if (maxHeaderSize < InternetMessageFormatHeaderParser.MinHeaderSize)
- {
- throw new ArgumentException(SR.MinParameterSize(InternetMessageFormatHeaderParser.MinHeaderSize), "maxHeaderSize");
- }
-
- if (headers == null)
- {
- throw new ArgumentNullException("headers");
- }
-
- this.headers = headers;
- this.maxHeaderSize = maxHeaderSize;
- this.currentHeader = new CurrentHeaderFieldStore();
- }
-
- private enum HeaderFieldState
- {
- Name = 0,
- Value,
- AfterCarriageReturn,
- FoldingLine
- }
-
- /// <summary>
- /// Parse a buffer of RFC 5322 style header fields and add them to the <see cref="HttpHeaders"/> collection.
- /// Bytes are parsed in a consuming manner from the beginning of the buffer meaning that the same bytes can not be
- /// present in the buffer.
- /// </summary>
- /// <param name="buffer">Request buffer from where request is read</param>
- /// <param name="bytesReady">Size of request buffer</param>
- /// <param name="bytesConsumed">Offset into request buffer</param>
- /// <returns>State of the parser. Call this method with new data until it reaches a final state.</returns>
- [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is translated to parse state.")]
- public ParserState ParseBuffer(
- byte[] buffer,
- int bytesReady,
- ref int bytesConsumed)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException("buffer");
- }
-
- ParserState parseStatus = ParserState.NeedMoreData;
-
- if (bytesConsumed >= bytesReady)
- {
- // We already can tell we need more data
- return parseStatus;
- }
-
- try
- {
- parseStatus = InternetMessageFormatHeaderParser.ParseHeaderFields(
- buffer,
- bytesReady,
- ref bytesConsumed,
- ref this.headerState,
- this.maxHeaderSize,
- ref this.totalBytesConsumed,
- this.currentHeader,
- this.headers);
- }
- catch (Exception)
- {
- parseStatus = ParserState.Invalid;
- }
-
- return parseStatus;
- }
-
- [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This is a parser which cannot be split up for performance reasons.")]
- private static unsafe ParserState ParseHeaderFields(
- byte[] buffer,
- int bytesReady,
- ref int bytesConsumed,
- ref HeaderFieldState requestHeaderState,
- int maximumHeaderLength,
- ref int totalBytesConsumed,
- CurrentHeaderFieldStore currentField,
- HttpHeaders headers)
- {
- Contract.Assert((bytesReady - bytesConsumed) >= 0, "ParseHeaderFields()|(inputBufferLength - bytesParsed) < 0");
- Contract.Assert(maximumHeaderLength <= 0 || totalBytesConsumed <= maximumHeaderLength, "ParseHeaderFields()|Headers already read exceeds limit.");
-
- // Remember where we started.
- int initialBytesParsed = bytesConsumed;
- int segmentStart;
-
- // Set up parsing status with what will happen if we exceed the buffer.
- ParserState parseStatus = ParserState.DataTooBig;
- int effectiveMax = maximumHeaderLength <= 0 ? Int32.MaxValue : maximumHeaderLength - totalBytesConsumed + initialBytesParsed;
- if (bytesReady < effectiveMax)
- {
- parseStatus = ParserState.NeedMoreData;
- effectiveMax = bytesReady;
- }
-
- Contract.Assert(bytesConsumed < effectiveMax, "We have already consumed more than the max header length.");
-
- fixed (byte* inputPtr = buffer)
- {
- switch (requestHeaderState)
- {
- case HeaderFieldState.Name:
- segmentStart = bytesConsumed;
- while (inputPtr[bytesConsumed] != ':')
- {
- if (inputPtr[bytesConsumed] == '\r')
- {
- if (!currentField.IsEmpty())
- {
- parseStatus = ParserState.Invalid;
- goto quit;
- }
- else
- {
- // Move past the '\r'
- requestHeaderState = HeaderFieldState.AfterCarriageReturn;
- if (++bytesConsumed == effectiveMax)
- {
- goto quit;
- }
-
- goto case HeaderFieldState.AfterCarriageReturn;
- }
- }
-
- if (++bytesConsumed == effectiveMax)
- {
- string headerFieldName = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
- currentField.Name.Append(headerFieldName);
- goto quit;
- }
- }
-
- if (bytesConsumed > segmentStart)
- {
- string headerFieldName = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
- currentField.Name.Append(headerFieldName);
- }
-
- // Move past the ':'
- requestHeaderState = HeaderFieldState.Value;
- if (++bytesConsumed == effectiveMax)
- {
- goto quit;
- }
-
- goto case HeaderFieldState.Value;
-
- case HeaderFieldState.Value:
- segmentStart = bytesConsumed;
- while (inputPtr[bytesConsumed] != '\r')
- {
- if (++bytesConsumed == effectiveMax)
- {
- string headerFieldValue = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
- currentField.Value.Append(headerFieldValue);
- goto quit;
- }
- }
-
- if (bytesConsumed > segmentStart)
- {
- string headerFieldValue = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
- currentField.Value.Append(headerFieldValue);
- }
-
- // Move past the CR
- requestHeaderState = HeaderFieldState.AfterCarriageReturn;
- if (++bytesConsumed == effectiveMax)
- {
- goto quit;
- }
-
- goto case HeaderFieldState.AfterCarriageReturn;
-
- case HeaderFieldState.AfterCarriageReturn:
- if (inputPtr[bytesConsumed] != '\n')
- {
- parseStatus = ParserState.Invalid;
- goto quit;
- }
-
- if (currentField.IsEmpty())
- {
- parseStatus = ParserState.Done;
- bytesConsumed++;
- goto quit;
- }
-
- requestHeaderState = HeaderFieldState.FoldingLine;
- if (++bytesConsumed == effectiveMax)
- {
- goto quit;
- }
-
- goto case HeaderFieldState.FoldingLine;
-
- case HeaderFieldState.FoldingLine:
- if (inputPtr[bytesConsumed] != ' ' && inputPtr[bytesConsumed] != '\t')
- {
- currentField.CopyTo(headers);
- requestHeaderState = HeaderFieldState.Name;
- if (bytesConsumed == effectiveMax)
- {
- goto quit;
- }
-
- goto case HeaderFieldState.Name;
- }
-
- // Unfold line by inserting SP instead
- currentField.Value.Append(' ');
-
- // Continue parsing header field value
- requestHeaderState = HeaderFieldState.Value;
- if (++bytesConsumed == effectiveMax)
- {
- goto quit;
- }
-
- goto case HeaderFieldState.Value;
- }
- }
-
- quit:
- totalBytesConsumed += bytesConsumed - initialBytesParsed;
- return parseStatus;
- }
-
- /// <summary>
- /// Maintains information about the current header field being parsed.
- /// </summary>
- private class CurrentHeaderFieldStore
- {
- private const int DefaultFieldNameAllocation = 128;
- private const int DefaultFieldValueAllocation = 2 * 1024;
-
- private static readonly char[] LinearWhiteSpace = new char[] { ' ', '\t' };
-
- private StringBuilder name = new StringBuilder(CurrentHeaderFieldStore.DefaultFieldNameAllocation);
- private StringBuilder value = new StringBuilder(CurrentHeaderFieldStore.DefaultFieldValueAllocation);
-
- /// <summary>
- /// Gets the header field name.
- /// </summary>
- public StringBuilder Name
- {
- get
- {
- return this.name;
- }
- }
-
- /// <summary>
- /// Gets the header field value.
- /// </summary>
- public StringBuilder Value
- {
- get
- {
- return this.value;
- }
- }
-
- /// <summary>
- /// Copies current header field to the provided <see cref="HttpHeaders"/> instance.
- /// </summary>
- /// <param name="headers">The headers.</param>
- public void CopyTo(HttpHeaders headers)
- {
- headers.Add(this.name.ToString(), this.value.ToString().Trim(CurrentHeaderFieldStore.LinearWhiteSpace));
- this.Clear();
- }
-
- /// <summary>
- /// Determines whether this instance is empty.
- /// </summary>
- /// <returns>
- /// <c>true</c> if this instance is empty; otherwise, <c>false</c>.
- /// </returns>
- public bool IsEmpty()
- {
- return this.name.Length == 0 && this.value.Length == 0;
- }
-
- /// <summary>
- /// Clears this instance.
- /// </summary>
- private void Clear()
- {
- this.name.Clear();
- this.value.Clear();
- }
- }
- }
- }