PageRenderTime 19ms CodeModel.GetById 8ms app.highlight 8ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  1// <copyright>
  2//   Copyright (c) Microsoft Corporation.  All rights reserved.
  3// </copyright>
  4
  5namespace System.Net.Http
  6{
  7    using System;
  8    using System.Diagnostics.CodeAnalysis;
  9    using System.Diagnostics.Contracts;
 10    using System.Net.Http.Headers;
 11    using System.Text;
 12
 13    /// <summary>
 14    /// Buffer-oriented RFC 5322 style Internet Message Format parser which can be used to pass header 
 15    /// fields used in HTTP and MIME message entities. 
 16    /// </summary>
 17    internal class InternetMessageFormatHeaderParser
 18    {
 19        internal const int MinHeaderSize = 2;
 20
 21        private int totalBytesConsumed;
 22        private int maxHeaderSize;
 23
 24        private HeaderFieldState headerState;
 25        private HttpHeaders headers;
 26        private CurrentHeaderFieldStore currentHeader;
 27
 28        /// <summary>
 29        /// Initializes a new instance of the <see cref="InternetMessageFormatHeaderParser"/> class.
 30        /// </summary>
 31        /// <param name="headers">Concrete <see cref="HttpHeaders"/> instance where header fields are added as they are parsed.</param>
 32        /// <param name="maxHeaderSize">Maximum length of complete header containing all the individual header fields.</param>
 33        public InternetMessageFormatHeaderParser(HttpHeaders headers, int maxHeaderSize)
 34        {
 35            // The minimum length which would be an empty header terminated by CRLF
 36            if (maxHeaderSize < InternetMessageFormatHeaderParser.MinHeaderSize)
 37            {
 38                throw new ArgumentException(SR.MinParameterSize(InternetMessageFormatHeaderParser.MinHeaderSize), "maxHeaderSize");
 39            }
 40
 41            if (headers == null)
 42            {
 43                throw new ArgumentNullException("headers");
 44            }
 45
 46            this.headers = headers;
 47            this.maxHeaderSize = maxHeaderSize;
 48            this.currentHeader = new CurrentHeaderFieldStore();
 49        }
 50
 51        private enum HeaderFieldState
 52        {
 53            Name = 0,
 54            Value,
 55            AfterCarriageReturn,
 56            FoldingLine
 57        }
 58
 59        /// <summary>
 60        /// Parse a buffer of RFC 5322 style header fields and add them to the <see cref="HttpHeaders"/> collection.
 61        /// Bytes are parsed in a consuming manner from the beginning of the buffer meaning that the same bytes can not be 
 62        /// present in the buffer.
 63        /// </summary>
 64        /// <param name="buffer">Request buffer from where request is read</param>
 65        /// <param name="bytesReady">Size of request buffer</param>
 66        /// <param name="bytesConsumed">Offset into request buffer</param>
 67        /// <returns>State of the parser. Call this method with new data until it reaches a final state.</returns>
 68        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is translated to parse state.")]
 69        public ParserState ParseBuffer(
 70            byte[] buffer,
 71            int bytesReady,
 72            ref int bytesConsumed)
 73        {
 74            if (buffer == null)
 75            {
 76                throw new ArgumentNullException("buffer");
 77            }
 78
 79            ParserState parseStatus = ParserState.NeedMoreData;
 80
 81            if (bytesConsumed >= bytesReady)
 82            {
 83                // We already can tell we need more data
 84                return parseStatus;
 85            }
 86
 87            try
 88            {
 89                parseStatus = InternetMessageFormatHeaderParser.ParseHeaderFields(
 90                    buffer,
 91                    bytesReady,
 92                    ref bytesConsumed,
 93                    ref this.headerState,
 94                    this.maxHeaderSize,
 95                    ref this.totalBytesConsumed,
 96                    this.currentHeader,
 97                    this.headers);
 98            }
 99            catch (Exception)
100            {
101                parseStatus = ParserState.Invalid;
102            }
103
104            return parseStatus;
105        }
106
107        [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This is a parser which cannot be split up for performance reasons.")]
108        private static unsafe ParserState ParseHeaderFields(
109            byte[] buffer,
110            int bytesReady,
111            ref int bytesConsumed,
112            ref HeaderFieldState requestHeaderState,
113            int maximumHeaderLength,
114            ref int totalBytesConsumed,
115            CurrentHeaderFieldStore currentField,
116            HttpHeaders headers)
117        {
118            Contract.Assert((bytesReady - bytesConsumed) >= 0, "ParseHeaderFields()|(inputBufferLength - bytesParsed) < 0");
119            Contract.Assert(maximumHeaderLength <= 0 || totalBytesConsumed <= maximumHeaderLength, "ParseHeaderFields()|Headers already read exceeds limit.");
120
121            // Remember where we started.
122            int initialBytesParsed = bytesConsumed;
123            int segmentStart;
124
125            // Set up parsing status with what will happen if we exceed the buffer.
126            ParserState parseStatus = ParserState.DataTooBig;
127            int effectiveMax = maximumHeaderLength <= 0 ? Int32.MaxValue : maximumHeaderLength - totalBytesConsumed + initialBytesParsed;
128            if (bytesReady < effectiveMax)
129            {
130                parseStatus = ParserState.NeedMoreData;
131                effectiveMax = bytesReady;
132            }
133
134            Contract.Assert(bytesConsumed < effectiveMax, "We have already consumed more than the max header length.");
135
136            fixed (byte* inputPtr = buffer)
137            {
138                switch (requestHeaderState)
139                {
140                    case HeaderFieldState.Name:
141                        segmentStart = bytesConsumed;
142                        while (inputPtr[bytesConsumed] != ':')
143                        {
144                            if (inputPtr[bytesConsumed] == '\r')
145                            {
146                                if (!currentField.IsEmpty())
147                                {
148                                    parseStatus = ParserState.Invalid;
149                                    goto quit;
150                                }
151                                else
152                                {
153                                    // Move past the '\r'
154                                    requestHeaderState = HeaderFieldState.AfterCarriageReturn;
155                                    if (++bytesConsumed == effectiveMax)
156                                    {
157                                        goto quit;
158                                    }
159
160                                    goto case HeaderFieldState.AfterCarriageReturn;
161                                }
162                            }
163
164                            if (++bytesConsumed == effectiveMax)
165                            {
166                                string headerFieldName = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
167                                currentField.Name.Append(headerFieldName);
168                                goto quit;
169                            }
170                        }
171
172                        if (bytesConsumed > segmentStart)
173                        {
174                            string headerFieldName = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
175                            currentField.Name.Append(headerFieldName);
176                        }
177
178                        // Move past the ':'
179                        requestHeaderState = HeaderFieldState.Value;
180                        if (++bytesConsumed == effectiveMax)
181                        {
182                            goto quit;
183                        }
184
185                        goto case HeaderFieldState.Value;
186
187                    case HeaderFieldState.Value:
188                        segmentStart = bytesConsumed;
189                        while (inputPtr[bytesConsumed] != '\r')
190                        {
191                            if (++bytesConsumed == effectiveMax)
192                            {
193                                string headerFieldValue = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
194                                currentField.Value.Append(headerFieldValue);
195                                goto quit;
196                            }
197                        }
198
199                        if (bytesConsumed > segmentStart)
200                        {
201                            string headerFieldValue = Encoding.UTF8.GetString(buffer, segmentStart, bytesConsumed - segmentStart);
202                            currentField.Value.Append(headerFieldValue);
203                        }
204
205                        // Move past the CR
206                        requestHeaderState = HeaderFieldState.AfterCarriageReturn;
207                        if (++bytesConsumed == effectiveMax)
208                        {
209                            goto quit;
210                        }
211
212                        goto case HeaderFieldState.AfterCarriageReturn;
213
214                    case HeaderFieldState.AfterCarriageReturn:
215                        if (inputPtr[bytesConsumed] != '\n')
216                        {
217                            parseStatus = ParserState.Invalid;
218                            goto quit;
219                        }
220
221                        if (currentField.IsEmpty())
222                        {
223                            parseStatus = ParserState.Done;
224                            bytesConsumed++;
225                            goto quit;
226                        }
227
228                        requestHeaderState = HeaderFieldState.FoldingLine;
229                        if (++bytesConsumed == effectiveMax)
230                        {
231                            goto quit;
232                        }
233
234                        goto case HeaderFieldState.FoldingLine;
235
236                    case HeaderFieldState.FoldingLine:
237                        if (inputPtr[bytesConsumed] != ' ' && inputPtr[bytesConsumed] != '\t')
238                        {
239                            currentField.CopyTo(headers);
240                            requestHeaderState = HeaderFieldState.Name;
241                            if (bytesConsumed == effectiveMax)
242                            {
243                                goto quit;
244                            }
245
246                            goto case HeaderFieldState.Name;
247                        }
248
249                        // Unfold line by inserting SP instead
250                        currentField.Value.Append(' ');
251
252                        // Continue parsing header field value
253                        requestHeaderState = HeaderFieldState.Value;
254                        if (++bytesConsumed == effectiveMax)
255                        {
256                            goto quit;
257                        }
258
259                        goto case HeaderFieldState.Value;
260                }
261            }
262
263        quit:
264            totalBytesConsumed += bytesConsumed - initialBytesParsed;
265            return parseStatus;
266        }
267
268        /// <summary>
269        /// Maintains information about the current header field being parsed. 
270        /// </summary>
271        private class CurrentHeaderFieldStore
272        {
273            private const int DefaultFieldNameAllocation = 128;
274            private const int DefaultFieldValueAllocation = 2 * 1024;
275
276            private static readonly char[] LinearWhiteSpace = new char[] { ' ', '\t' };
277
278            private StringBuilder name = new StringBuilder(CurrentHeaderFieldStore.DefaultFieldNameAllocation);
279            private StringBuilder value = new StringBuilder(CurrentHeaderFieldStore.DefaultFieldValueAllocation);
280
281            /// <summary>
282            /// Gets the header field name.
283            /// </summary>
284            public StringBuilder Name
285            {
286                get
287                {
288                    return this.name;
289                }
290            }
291
292            /// <summary>
293            /// Gets the header field value.
294            /// </summary>
295            public StringBuilder Value
296            {
297                get
298                {
299                    return this.value;
300                }
301            }
302
303            /// <summary>
304            /// Copies current header field to the provided <see cref="HttpHeaders"/> instance.
305            /// </summary>
306            /// <param name="headers">The headers.</param>
307            public void CopyTo(HttpHeaders headers)
308            {
309                headers.Add(this.name.ToString(), this.value.ToString().Trim(CurrentHeaderFieldStore.LinearWhiteSpace));
310                this.Clear();
311            }
312
313            /// <summary>
314            /// Determines whether this instance is empty.
315            /// </summary>
316            /// <returns>
317            ///   <c>true</c> if this instance is empty; otherwise, <c>false</c>.
318            /// </returns>
319            public bool IsEmpty()
320            {
321                return this.name.Length == 0 && this.value.Length == 0;
322            }
323
324            /// <summary>
325            /// Clears this instance.
326            /// </summary>
327            private void Clear()
328            {
329                this.name.Clear();
330                this.value.Clear();
331            }
332        }
333    }
334}