1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Diagnostics;
6 using System.Runtime.InteropServices;
7 
8 namespace System.Net.Http
9 {
10     internal readonly struct CurlResponseHeaderReader
11     {
12         private const string HttpPrefix = "HTTP/";
13 
14         private readonly HeaderBufferSpan _span;
15 
CurlResponseHeaderReaderSystem.Net.Http.CurlResponseHeaderReader16         public CurlResponseHeaderReader(IntPtr buffer, ulong size)
17         {
18             Debug.Assert(buffer != IntPtr.Zero);
19             Debug.Assert(size <= int.MaxValue);
20 
21             _span = new HeaderBufferSpan(buffer, (int)size).Trim();
22         }
23 
ReadStatusLineSystem.Net.Http.CurlResponseHeaderReader24         public bool ReadStatusLine(HttpResponseMessage response)
25         {
26             if (!_span.StartsWithHttpPrefix())
27             {
28                 return false;
29             }
30 
31             int index = HttpPrefix.Length;
32             int majorVersion = _span.ReadInt(ref index);
33             CheckResponseMsgFormat(majorVersion != 0);
34             CheckResponseMsgFormat(index < _span.Length);
35 
36             int minorVersion;
37             if (_span[index] == '.')
38             {
39                 index++;
40 
41                 CheckResponseMsgFormat(index < _span.Length && _span[index] >= '0' && _span[index] <= '9');
42                 minorVersion = _span.ReadInt(ref index);
43             }
44             else
45             {
46                 minorVersion = 0;
47             }
48 
49             CheckResponseMsgFormat(_span.SkipSpace(ref index));
50 
51             // Parse status code.
52             int statusCode = _span.ReadInt(ref index);
53             CheckResponseMsgFormat(statusCode >= 100 && statusCode < 1000);
54 
55             bool foundSpace = _span.SkipSpace(ref index);
56             CheckResponseMsgFormat(index <= _span.Length);
57             CheckResponseMsgFormat(foundSpace || index == _span.Length);
58 
59             // Set the response HttpVersion.
60             response.Version =
61                 (majorVersion == 1 && minorVersion == 1) ? HttpVersionInternal.Version11 :
62                 (majorVersion == 1 && minorVersion == 0) ? HttpVersionInternal.Version10 :
63                 (majorVersion == 2 && minorVersion == 0) ? HttpVersionInternal.Version20 :
64                 HttpVersionInternal.Unknown;
65 
66             response.StatusCode = (HttpStatusCode)statusCode;
67 
68             // Try to use a known reason phrase instead of allocating a new string.
69             HeaderBufferSpan reasonPhraseSpan = _span.Substring(index);
70             string knownReasonPhrase = HttpStatusDescription.Get(response.StatusCode);
71             response.ReasonPhrase = reasonPhraseSpan.EqualsOrdinal(knownReasonPhrase) ?
72                 knownReasonPhrase :
73                 reasonPhraseSpan.ToString();
74 
75             return true;
76         }
77 
ReadHeaderSystem.Net.Http.CurlResponseHeaderReader78         public bool ReadHeader(out string headerName, out string headerValue)
79         {
80             int index = 0;
81             while (index < _span.Length && ValidHeaderNameChar(_span[index]))
82             {
83                 index++;
84             }
85 
86             if (index > 0)
87             {
88                 // For compatability, skip past any whitespace before the colon, even though
89                 // the RFC suggests there shouldn't be any.
90                 int headerNameLength = index;
91                 while (index < _span.Length && IsWhiteSpaceLatin1(_span[index]))
92                 {
93                     index++;
94                 }
95 
96                 CheckResponseMsgFormat(index < _span.Length);
97                 CheckResponseMsgFormat(_span[index] == ':');
98                 HeaderBufferSpan headerNameSpan = _span.Substring(0, headerNameLength);
99                 if (!HttpKnownHeaderNames.TryGetHeaderName(headerNameSpan.Buffer, headerNameSpan.Length, out headerName))
100                 {
101                     headerName = headerNameSpan.ToString();
102                 }
103                 CheckResponseMsgFormat(headerName.Length > 0);
104 
105                 index++;
106                 headerValue = _span.Substring(index).Trim().ToString();
107                 return true;
108             }
109 
110             headerName = null;
111             headerValue = null;
112             return false;
113         }
114 
CheckResponseMsgFormatSystem.Net.Http.CurlResponseHeaderReader115         private static void CheckResponseMsgFormat(bool condition)
116         {
117             if (!condition)
118             {
119                 throw new HttpRequestException(SR.net_http_invalid_response);
120             }
121         }
122 
ValidHeaderNameCharSystem.Net.Http.CurlResponseHeaderReader123         private static bool ValidHeaderNameChar(byte c)
124         {
125             const string invalidChars = "()<>@,;:\\\"/[]?={}";
126             return c > ' ' && invalidChars.IndexOf((char)c) < 0;
127         }
128 
IsWhiteSpaceLatin1System.Net.Http.CurlResponseHeaderReader129         internal static bool IsWhiteSpaceLatin1(byte c)
130         {
131             // SPACE
132             // U+0009 = <control> HORIZONTAL TAB
133             // U+000a = <control> LINE FEED
134             // U+000b = <control> VERTICAL TAB
135             // U+000c = <control> FORM FEED
136             // U+000d = <control> CARRIAGE RETURN
137             // U+0085 = <control> NEXT LINE
138             // U+00a0 = NO-BREAK SPACE
139             return c == ' ' || (c >= '\x0009' && c <= '\x000d') || c == '\x00a0' || c == '\x0085';
140         }
141 
142         private unsafe readonly struct HeaderBufferSpan
143         {
144             private readonly byte* _pointer;
145             public readonly int Length;
146 
147             public static readonly HeaderBufferSpan Empty = default(HeaderBufferSpan);
148 
HeaderBufferSpanSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan149             public HeaderBufferSpan(IntPtr pointer, int length)
150                 : this((byte*)pointer, length)
151             {
152             }
153 
HeaderBufferSpanSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan154             public HeaderBufferSpan(byte* pointer, int length)
155             {
156                 Debug.Assert(pointer != null);
157                 Debug.Assert(length >= 0);
158 
159                 _pointer = pointer;
160                 Length = length;
161             }
162 
163             public IntPtr Buffer => new IntPtr(_pointer);
164 
165             public byte this[int index]
166             {
167                 get
168                 {
169                     Debug.Assert(index >= 0 && index < Length);
170                     return _pointer[index];
171                 }
172             }
173 
TrimSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan174             public HeaderBufferSpan Trim()
175             {
176                 if (Length == 0)
177                 {
178                     return Empty;
179                 }
180 
181                 int index = 0;
182                 while (index < Length && IsWhiteSpaceLatin1(_pointer[index]))
183                 {
184                     index++;
185                 }
186 
187                 int end = Length - 1;
188                 while (end >= index && IsWhiteSpaceLatin1(_pointer[end]))
189                 {
190                     end--;
191                 }
192 
193                 byte* pointer = _pointer + index;
194                 int length = end - index + 1;
195 
196                 return new HeaderBufferSpan(pointer, length);
197             }
198 
StartsWithHttpPrefixSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan199             public bool StartsWithHttpPrefix()
200             {
201                 if (Length < HttpPrefix.Length)
202                 {
203                     return false;
204                 }
205 
206                 return
207                     (_pointer[0] == 'H' || _pointer[0] == 'h') &&
208                     (_pointer[1] == 'T' || _pointer[1] == 't') &&
209                     (_pointer[2] == 'T' || _pointer[2] == 't') &&
210                     (_pointer[3] == 'P' || _pointer[3] == 'p') &&
211                     (_pointer[4] == '/');
212             }
213 
ReadIntSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan214             public int ReadInt(ref int index)
215             {
216                 int value = 0;
217                 for (; index < Length; index++)
218                 {
219                     byte c = _pointer[index];
220                     if (c < '0' || c > '9')
221                     {
222                         break;
223                     }
224 
225                     value = (value * 10) + (c - '0');
226                 }
227 
228                 return value;
229             }
230 
SkipSpaceSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan231             public bool SkipSpace(ref int index)
232             {
233                 bool foundSpace = false;
234                 for (; index < Length; index++)
235                 {
236                     if (_pointer[index] == ' ' || _pointer[index] == '\t')
237                     {
238                         foundSpace = true;
239                     }
240                     else
241                     {
242                         break;
243                     }
244                 }
245                 return foundSpace;
246             }
247 
EqualsOrdinalSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan248             public bool EqualsOrdinal(string value)
249             {
250                 if (value == null)
251                 {
252                     return false;
253                 }
254 
255                 if (Length != value.Length)
256                 {
257                     return false;
258                 }
259 
260                 for (int i = 0; i < Length; i++)
261                 {
262                     if (_pointer[i] != value[i])
263                     {
264                         return false;
265                     }
266                 }
267 
268                 return true;
269             }
270 
SubstringSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan271             public HeaderBufferSpan Substring(int startIndex)
272             {
273                 return Substring(startIndex, Length - startIndex);
274             }
275 
SubstringSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan276             public HeaderBufferSpan Substring(int startIndex, int length)
277             {
278                 Debug.Assert(startIndex >= 0);
279                 Debug.Assert(length >= 0);
280                 Debug.Assert(startIndex <= Length - length);
281 
282                 if (length == 0)
283                 {
284                     return Empty;
285                 }
286 
287                 if (startIndex == 0 && length == Length)
288                 {
289                     return this;
290                 }
291 
292                 return new HeaderBufferSpan(_pointer + startIndex, length);
293             }
294 
ToStringSystem.Net.Http.CurlResponseHeaderReader.HeaderBufferSpan295             public override string ToString()
296             {
297                 return Length == 0 ? string.Empty : HttpRuleParser.DefaultHttpEncoding.GetString(_pointer, Length);
298             }
299         }
300     }
301 }
302