// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http { public abstract class FrameHeaders : IHeaderDictionary { protected bool _isReadOnly; protected Dictionary MaybeUnknown; protected Dictionary Unknown => MaybeUnknown ?? (MaybeUnknown = new Dictionary(StringComparer.OrdinalIgnoreCase)); StringValues IHeaderDictionary.this[string key] { get { StringValues value; TryGetValueFast(key, out value); return value; } set { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } SetValueFast(key, value); } } StringValues IDictionary.this[string key] { get { // Unlike the IHeaderDictionary version, this getter will throw a KeyNotFoundException. return GetValueFast(key); } set { ((IHeaderDictionary)this)[key] = value; } } protected void ThrowHeadersReadOnlyException() { throw new InvalidOperationException("Headers are read-only, response has already started."); } protected void ThrowArgumentException() { throw new ArgumentException(); } protected void ThrowKeyNotFoundException() { throw new KeyNotFoundException(); } protected void ThrowDuplicateKeyException() { throw new ArgumentException("An item with the same key has already been added."); } int ICollection>.Count => GetCountFast(); bool ICollection>.IsReadOnly => _isReadOnly; ICollection IDictionary.Keys => ((IDictionary)this).Select(pair => pair.Key).ToList(); ICollection IDictionary.Values => ((IDictionary)this).Select(pair => pair.Value).ToList(); public void SetReadOnly() { _isReadOnly = true; } public void Reset() { _isReadOnly = false; ClearFast(); } protected static StringValues AppendValue(StringValues existing, string append) { return StringValues.Concat(existing, append); } protected static int BitCount(long value) { // see https://github.com/dotnet/corefx/blob/5965fd3756bc9dd9c89a27621eb10c6931126de2/src/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BitArithmetic.cs const ulong Mask01010101 = 0x5555555555555555UL; const ulong Mask00110011 = 0x3333333333333333UL; const ulong Mask00001111 = 0x0F0F0F0F0F0F0F0FUL; const ulong Mask00000001 = 0x0101010101010101UL; var v = (ulong)value; v = v - ((v >> 1) & Mask01010101); v = (v & Mask00110011) + ((v >> 2) & Mask00110011); return (int)(unchecked(((v + (v >> 4)) & Mask00001111) * Mask00000001) >> 56); } protected virtual int GetCountFast() { throw new NotImplementedException(); } protected virtual StringValues GetValueFast(string key) { throw new NotImplementedException(); } protected virtual bool TryGetValueFast(string key, out StringValues value) { throw new NotImplementedException(); } protected virtual void SetValueFast(string key, StringValues value) { throw new NotImplementedException(); } protected virtual void AddValueFast(string key, StringValues value) { throw new NotImplementedException(); } protected virtual bool RemoveFast(string key) { throw new NotImplementedException(); } protected virtual void ClearFast() { throw new NotImplementedException(); } protected virtual void CopyToFast(KeyValuePair[] array, int arrayIndex) { throw new NotImplementedException(); } protected virtual IEnumerator> GetEnumeratorFast() { throw new NotImplementedException(); } void ICollection>.Add(KeyValuePair item) { ((IDictionary)this).Add(item.Key, item.Value); } void IDictionary.Add(string key, StringValues value) { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } AddValueFast(key, value); } void ICollection>.Clear() { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } ClearFast(); } bool ICollection>.Contains(KeyValuePair item) { StringValues value; return TryGetValueFast(item.Key, out value) && value.Equals(item.Value); } bool IDictionary.ContainsKey(string key) { StringValues value; return TryGetValueFast(key, out value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { CopyToFast(array, arrayIndex); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumeratorFast(); } IEnumerator> IEnumerable>.GetEnumerator() { return GetEnumeratorFast(); } bool ICollection>.Remove(KeyValuePair item) { StringValues value; return TryGetValueFast(item.Key, out value) && value.Equals(item.Value) && RemoveFast(item.Key); } bool IDictionary.Remove(string key) { if (_isReadOnly) { ThrowHeadersReadOnlyException(); } return RemoveFast(key); } bool IDictionary.TryGetValue(string key, out StringValues value) { return TryGetValueFast(key, out value); } public static void ValidateHeaderCharacters(StringValues headerValues) { foreach (var value in headerValues) { ValidateHeaderCharacters(value); } } public static void ValidateHeaderCharacters(string headerCharacters) { if (headerCharacters != null) { foreach (var ch in headerCharacters) { if (ch < 0x20 || ch > 0x7E) { ThrowInvalidHeaderCharacter(ch); } } } } public static unsafe long ParseContentLength(StringValues value) { var input = value.ToString(); var parsed = 0L; fixed (char* ptr = input) { var ch = (ushort*)ptr; var end = ch + input.Length; if (ch == end) { ThrowInvalidContentLengthException(value); } ushort digit = 0; while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9) { parsed *= 10; parsed += digit; ch++; } if (ch != end) { ThrowInvalidContentLengthException(value); } } return parsed; } private static void ThrowInvalidContentLengthException(string value) { throw new InvalidOperationException($"Invalid Content-Length: \"{value}\". Value must be a positive integral number."); } private static void ThrowInvalidHeaderCharacter(char ch) { throw new InvalidOperationException(string.Format("Invalid non-ASCII or control character in header: 0x{0:X4}", (ushort)ch)); } } }