| File: System\Net\Http\Headers\HttpHeaderValueCollection.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Http\src\System.Net.Http.csproj (System.Net.Http) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; namespace System.Net.Http.Headers { // This type is used for headers supporting a list of values. It essentially just forwards calls to // the actual header-store in HttpHeaders. // // This type can deal with a so called "special value": The RFC defines some headers which are collection of // values, but the RFC only defines 1 value, e.g. Transfer-Encoding: chunked, Connection: close, // Expect: 100-continue. // We expose strongly typed properties for these special values: TransferEncodingChunked, ConnectionClose, // ExpectContinue. // So we have 2 properties for each of these headers ('Transfer-Encoding' => TransferEncoding, // TransferEncodingChunked; 'Connection' => Connection, ConnectionClose; 'Expect' => Expect, ExpectContinue) // // The following solution was chosen: // - Keep HttpHeaders clean: HttpHeaders is unaware of these "special values"; it just stores the collection of // headers. // - It is the responsibility of "higher level" components (HttpHeaderValueCollection, HttpRequestHeaders, // HttpResponseHeaders) to deal with special values. // - HttpHeaderValueCollection can be configured with an IEqualityComparer and a "special value". // // Example: Server sends header "Transfer-Encoding: gzip, custom, chunked" to the client. // - HttpHeaders: HttpHeaders will have an entry in the header store for "Transfer-Encoding" with values // "gzip", "custom", "chunked" // - HttpGeneralHeaders: // - Property TransferEncoding: has three values "gzip", "custom", and "chunked" // - Property TransferEncodingChunked: is set to "true". public sealed class HttpHeaderValueCollection<T> : ICollection<T> where T : class { private readonly HeaderDescriptor _descriptor; private readonly HttpHeaders _store; public int Count { get { return GetCount(); } } public bool IsReadOnly { get { return false; } } internal HttpHeaderValueCollection(HeaderDescriptor descriptor, HttpHeaders store) { _store = store; _descriptor = descriptor; } public void Add(T item) { CheckValue(item); _store.AddParsedValue(_descriptor, item); } public void ParseAdd(string? input) { _store.Add(_descriptor, input); } public bool TryParseAdd(string? input) { return _store.TryParseAndAddValue(_descriptor, input); } public void Clear() { _store.Remove(_descriptor); } public bool Contains(T item) { CheckValue(item); return _store.ContainsParsedValue(_descriptor, item); } public void CopyTo(T[] array, int arrayIndex) { ArgumentNullException.ThrowIfNull(array); // Allow arrayIndex == array.Length in case our own collection is empty ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); ArgumentOutOfRangeException.ThrowIfGreaterThan(arrayIndex, array.Length); object? storeValue = _store.GetParsedAndInvalidValues(_descriptor); if (storeValue == null) { return; } List<object>? storeValues = storeValue as List<object>; if (storeValues == null) { if (storeValue is not HttpHeaders.InvalidValue) { Debug.Assert(storeValue is T); if (arrayIndex == array.Length) { throw new ArgumentException(SR.net_http_copyto_array_too_small); } array[arrayIndex] = (T)storeValue; } } else { foreach (object item in storeValues) { if (item is not HttpHeaders.InvalidValue) { Debug.Assert(item is T); if (arrayIndex == array.Length) { throw new ArgumentException(SR.net_http_copyto_array_too_small); } array[arrayIndex++] = (T)item; } } } } public bool Remove(T item) { CheckValue(item); return _store.RemoveParsedValue(_descriptor, item); } #region IEnumerable<T> Members public IEnumerator<T> GetEnumerator() { object? storeValue = _store.GetParsedAndInvalidValues(_descriptor); return storeValue is null || storeValue is HttpHeaders.InvalidValue ? ((IEnumerable<T>)Array.Empty<T>()).GetEnumerator() : // use singleton empty array enumerator Iterate(storeValue); static IEnumerator<T> Iterate(object storeValue) { if (storeValue is List<object> storeValues) { // We have multiple values. Iterate through the values and return them. foreach (object item in storeValues) { if (item is HttpHeaders.InvalidValue) { continue; } Debug.Assert(item is T); yield return (T)item; } } else { Debug.Assert(storeValue is T); yield return (T)storeValue; } } } #endregion #region IEnumerable Members Collections.IEnumerator Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion public override string ToString() { return _store.GetHeaderString(_descriptor); } private void CheckValue(T item) { ArgumentNullException.ThrowIfNull(item); if (_descriptor.Parser == GenericHeaderParser.TokenListParser) { // The collection expects valid HTTP tokens, which are typed as string. // Unlike other parsed values (which are always valid by construction), // we can't assume the provided string is a valid token. So validate it before we use it. Debug.Assert(typeof(T) == typeof(string)); HeaderUtilities.CheckValidToken((string)(object)item, nameof(item)); } } private int GetCount() { // This is an O(n) operation. object? storeValue = _store.GetParsedAndInvalidValues(_descriptor); if (storeValue == null) { return 0; } List<object>? storeValues = storeValue as List<object>; if (storeValues == null) { if (storeValue is not HttpHeaders.InvalidValue) { return 1; } return 0; } else { int count = 0; foreach (object item in storeValues) { if (item is not HttpHeaders.InvalidValue) { count++; } } return count; } } } }