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