// // Copyright (c) Microsoft Corporation. All rights reserved. // namespace System.ServiceModel.Channels { using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Http.Headers; using System.Runtime; using System.Text; internal class HttpHeaderInfo { private static readonly HttpHeaderInfo[] knownContentHeaders = new HttpHeaderInfo[] { new HttpHeaderInfo("Allow") { IsContentHeader = true }, new HttpHeaderInfo("Content-Encoding") { IsContentHeader = true }, new HttpHeaderInfo("Content-Language") { IsContentHeader = true }, new HttpHeaderInfo("Content-Length") { IsContentHeader = true }, new HttpHeaderInfo("Content-Location") { IsContentHeader = true }, new HttpHeaderInfo("Content-MD5") { IsContentHeader = true }, new HttpHeaderInfo("Content-Range") { IsContentHeader = true }, new HttpHeaderInfo("Content-Type") { IsContentHeader = true }, new HttpHeaderInfo("Expires") { IsContentHeader = true }, new HttpHeaderInfo("Last-Modified") { IsContentHeader = true }, new HttpHeaderInfo("Content-Disposition") { IsContentHeader = true } }; private static readonly Type httpRequestHeaderType = typeof(HttpRequestHeader); private static readonly Type httpResponseHeaderType = typeof(HttpResponseHeader); private static ConcurrentDictionary knownHeadersInfos; private bool isUnknownHeader; static HttpHeaderInfo() { // Create the known headers list with the content headers knownHeadersInfos = new ConcurrentDictionary( knownContentHeaders.ToDictionary(headerInfo => headerInfo.Name), StringComparer.OrdinalIgnoreCase); // Add the request and response headers to the known headers list AddKnownHeaders(Enum.GetNames(httpRequestHeaderType).Select(enumString => GetHeaderString(enumString)), true); AddKnownHeaders(Enum.GetNames(httpResponseHeaderType).Select(enumString => GetHeaderString(enumString)), false); } private HttpHeaderInfo(string name, bool isUnknownHeader = false) { Fx.Assert(!string.IsNullOrWhiteSpace(name), "The 'name' parameter should not be null or whitespace."); this.Name = name; this.isUnknownHeader = isUnknownHeader; if (this.isUnknownHeader) { this.IsRequestHeader = true; this.IsResponseHeader = true; this.IsContentHeader = true; } } public string Name { get; private set; } public bool IsRequestHeader { get; private set; } public bool IsResponseHeader { get; private set; } public bool IsContentHeader { get; private set; } public static HttpHeaderInfo Create(string headerName) { Fx.Assert(!string.IsNullOrWhiteSpace(headerName), "The 'headerName' should not be null or whitespace."); HttpHeaderInfo headerInfo; if (!knownHeadersInfos.TryGetValue(headerName, out headerInfo)) { headerInfo = new HttpHeaderInfo(headerName, true); } return headerInfo; } public bool TryAddHeader(HttpHeaders headers, string value) { Fx.Assert(headers != null, "The 'headers' parameter should never be null."); if (!headers.TryAddWithoutValidation(this.Name, value)) { this.UpdateHeaderInfo(headers); return false; } return true; } public bool TryRemoveHeader(HttpHeaders headers) { Fx.Assert(headers != null, "The 'headers' parameter should never be null."); try { headers.Remove(this.Name); return true; } catch (InvalidOperationException ex) { FxTrace.Exception.TraceHandledException(ex, TraceEventType.Information); this.UpdateHeaderInfo(headers); } return false; } public IEnumerable TryGetHeader(HttpHeaders headers) { Fx.Assert(headers != null, "The 'headers' parameter should never be null."); IEnumerable values = null; if (!headers.TryGetValues(this.Name, out values)) { values = null; this.UpdateHeaderInfo(headers); } return values; } private static void AddKnownHeaders(IEnumerable headers, bool asRequestHeader) { foreach (string header in headers) { HttpHeaderInfo headerInfo = null; if (knownHeadersInfos.TryGetValue(header, out headerInfo)) { if (headerInfo.IsContentHeader) { // this header is actually a content header so continue continue; } } if (headerInfo == null) { headerInfo = new HttpHeaderInfo(header); knownHeadersInfos.TryAdd(headerInfo.Name, headerInfo); } if (asRequestHeader) { headerInfo.IsRequestHeader = true; } else { headerInfo.IsResponseHeader = true; } } } private static string GetHeaderString(string headerEnumString) { // Have to special case 'ETag' as it should not be hypenated if (string.Equals(headerEnumString, HttpResponseHeader.ETag.ToString(), StringComparison.Ordinal)) { return headerEnumString; } StringBuilder asStringBuilder = new StringBuilder(headerEnumString); // Note that we are not considering the first and last characters of the string // as headers never start or end with '-' for (int i = asStringBuilder.Length - 2; i > 0; i--) { if (char.IsUpper(asStringBuilder[i]) && char.IsLower(asStringBuilder[i + 1])) { asStringBuilder.Insert(i, '-'); } } return asStringBuilder.ToString(); } private void UpdateHeaderInfo(HttpHeaders headers) { Fx.Assert(headers != null, "The 'headers' parameter should never be null."); if (headers is HttpContentHeaders) { this.IsContentHeader = false; } else if (headers is HttpRequestHeaders) { this.IsRequestHeader = false; } else if (headers is HttpResponseHeaders) { this.IsResponseHeader = false; } if (this.isUnknownHeader) { this.isUnknownHeader = !knownHeadersInfos.TryAdd(this.Name, this); } } } }