//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ #pragma warning disable 1634, 1691 namespace System.ServiceModel.Web { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Net; using System.Net.Mime; using System.Runtime; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Dispatcher; public class IncomingWebRequestContext { static readonly string HttpGetMethod = "GET"; static readonly string HttpHeadMethod = "HEAD"; static readonly string HttpPutMethod = "PUT"; static readonly string HttpPostMethod = "POST"; static readonly string HttpDeleteMethod = "DELETE"; Collection cachedAcceptHeaderElements; string acceptHeaderWhenHeaderElementsCached; internal const string UriTemplateMatchResultsPropertyName = "UriTemplateMatchResults"; OperationContext operationContext; internal IncomingWebRequestContext(OperationContext operationContext) { Fx.Assert(operationContext != null, "operationContext is null"); this.operationContext = operationContext; } public string Accept { get { return EnsureMessageProperty().Headers[HttpRequestHeader.Accept]; } } public long ContentLength { get { return long.Parse(this.EnsureMessageProperty().Headers[HttpRequestHeader.ContentLength], CultureInfo.InvariantCulture); } } public string ContentType { get { return this.EnsureMessageProperty().Headers[HttpRequestHeader.ContentType]; } } public IEnumerable IfMatch { get { string ifMatchHeader = MessageProperty.Headers[HttpRequestHeader.IfMatch]; return (string.IsNullOrEmpty(ifMatchHeader)) ? null : Utility.QuoteAwareStringSplit(ifMatchHeader); } } public IEnumerable IfNoneMatch { get { string ifNoneMatchHeader = MessageProperty.Headers[HttpRequestHeader.IfNoneMatch]; return (string.IsNullOrEmpty(ifNoneMatchHeader)) ? null : Utility.QuoteAwareStringSplit(ifNoneMatchHeader); } } public DateTime? IfModifiedSince { get { string dateTime = this.MessageProperty.Headers[HttpRequestHeader.IfModifiedSince]; if (!string.IsNullOrEmpty(dateTime)) { DateTime parsedDateTime; if (HttpDateParse.ParseHttpDate(dateTime, out parsedDateTime)) { return parsedDateTime; } } return null; } } public DateTime? IfUnmodifiedSince { get { string dateTime = this.MessageProperty.Headers[HttpRequestHeader.IfUnmodifiedSince]; if (!string.IsNullOrEmpty(dateTime)) { DateTime parsedDateTime; if (HttpDateParse.ParseHttpDate(dateTime, out parsedDateTime)) { return parsedDateTime; } } return null; } } public WebHeaderCollection Headers { get { return this.EnsureMessageProperty().Headers; } } public string Method { get { return this.EnsureMessageProperty().Method; } } public UriTemplateMatch UriTemplateMatch { get { if (this.operationContext.IncomingMessageProperties.ContainsKey(UriTemplateMatchResultsPropertyName)) { return this.operationContext.IncomingMessageProperties[UriTemplateMatchResultsPropertyName] as UriTemplateMatch; } else { return null; } } set { this.operationContext.IncomingMessageProperties[UriTemplateMatchResultsPropertyName] = value; } } public string UserAgent { get { return this.EnsureMessageProperty().Headers[HttpRequestHeader.UserAgent]; } } HttpRequestMessageProperty MessageProperty { get { if (operationContext.IncomingMessageProperties == null) { return null; } if (!operationContext.IncomingMessageProperties.ContainsKey(HttpRequestMessageProperty.Name)) { return null; } return operationContext.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty; } } public void CheckConditionalRetrieve(string entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtagFromString(entityTag); CheckConditionalRetrieveWithValidatedEtag(validEtag); } public void CheckConditionalRetrieve(int entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag); CheckConditionalRetrieveWithValidatedEtag(validEtag); } public void CheckConditionalRetrieve(long entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag); CheckConditionalRetrieveWithValidatedEtag(validEtag); } public void CheckConditionalRetrieve(Guid entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag); CheckConditionalRetrieveWithValidatedEtag(validEtag); } public void CheckConditionalRetrieve(DateTime lastModified) { if (!string.Equals(this.Method, IncomingWebRequestContext.HttpGetMethod, StringComparison.OrdinalIgnoreCase) && !string.Equals(this.Method, IncomingWebRequestContext.HttpHeadMethod, StringComparison.OrdinalIgnoreCase)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.ConditionalRetrieveGetAndHeadOnly, this.Method))); } DateTime? ifModifiedSince = this.IfModifiedSince; if (ifModifiedSince.HasValue) { long ticksDifference = lastModified.ToUniversalTime().Ticks - ifModifiedSince.Value.ToUniversalTime().Ticks; if (ticksDifference < TimeSpan.TicksPerSecond) { WebOperationContext.Current.OutgoingResponse.LastModified = lastModified; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.NotModified)); } } } public void CheckConditionalUpdate(string entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtagFromString(entityTag); CheckConditionalUpdateWithValidatedEtag(validEtag); } public void CheckConditionalUpdate(int entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag); CheckConditionalUpdateWithValidatedEtag(validEtag); } public void CheckConditionalUpdate(long entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag); CheckConditionalUpdateWithValidatedEtag(validEtag); } public void CheckConditionalUpdate(Guid entityTag) { string validEtag = OutgoingWebResponseContext.GenerateValidEtag(entityTag); CheckConditionalUpdateWithValidatedEtag(validEtag); } public Collection GetAcceptHeaderElements() { string acceptHeader = this.Accept; if (cachedAcceptHeaderElements == null || (!string.Equals(acceptHeaderWhenHeaderElementsCached, acceptHeader, StringComparison.OrdinalIgnoreCase))) { if (string.IsNullOrEmpty(acceptHeader)) { cachedAcceptHeaderElements = new Collection(); acceptHeaderWhenHeaderElementsCached = acceptHeader; } else { List contentTypeList = new List(); int offset = 0; while (true) { string nextItem = Utility.QuoteAwareSubString(acceptHeader, ref offset); if (nextItem == null) { break; } ContentType contentType = Utility.GetContentTypeOrNull(nextItem); if (contentType != null) { contentTypeList.Add(contentType); } } contentTypeList.Sort(new AcceptHeaderElementComparer()); cachedAcceptHeaderElements = new Collection(contentTypeList); acceptHeaderWhenHeaderElementsCached = acceptHeader; } } return cachedAcceptHeaderElements; } HttpRequestMessageProperty EnsureMessageProperty() { if (this.MessageProperty == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.HttpContextNoIncomingMessageProperty, typeof(HttpRequestMessageProperty).Name))); } return this.MessageProperty; } void CheckConditionalRetrieveWithValidatedEtag(string entityTag) { if (!string.Equals(this.Method, IncomingWebRequestContext.HttpGetMethod, StringComparison.OrdinalIgnoreCase) && !string.Equals(this.Method, IncomingWebRequestContext.HttpHeadMethod, StringComparison.OrdinalIgnoreCase)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.ConditionalRetrieveGetAndHeadOnly, this.Method))); } if (!string.IsNullOrEmpty(entityTag)) { string entityTagHeader = this.Headers[HttpRequestHeader.IfNoneMatch]; if (!string.IsNullOrEmpty(entityTagHeader)) { if (IsWildCardCharacter(entityTagHeader) || DoesHeaderContainEtag(entityTagHeader, entityTag)) { // set response entityTag directly because it has already been validated WebOperationContext.Current.OutgoingResponse.ETag = entityTag; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.NotModified)); } } } } void CheckConditionalUpdateWithValidatedEtag(string entityTag) { bool isPutMethod = string.Equals(this.Method, IncomingWebRequestContext.HttpPutMethod, StringComparison.OrdinalIgnoreCase); if (!isPutMethod && !string.Equals(this.Method, IncomingWebRequestContext.HttpPostMethod, StringComparison.OrdinalIgnoreCase) && !string.Equals(this.Method, IncomingWebRequestContext.HttpDeleteMethod, StringComparison.OrdinalIgnoreCase)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR2.GetString(SR2.ConditionalUpdatePutPostAndDeleteOnly, this.Method))); } string headerOfInterest; // if the current entityTag is null then the resource doesn't currently exist and the // a PUT request should only succeed if If-None-Match equals '*'. if (isPutMethod && string.IsNullOrEmpty(entityTag)) { headerOfInterest = this.Headers[HttpRequestHeader.IfNoneMatch]; if (string.IsNullOrEmpty(headerOfInterest) || !IsWildCardCharacter(headerOfInterest)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.PreconditionFailed)); } } else { // all remaining cases are with an If-Match header headerOfInterest = this.Headers[HttpRequestHeader.IfMatch]; if (string.IsNullOrEmpty(headerOfInterest) || (!IsWildCardCharacter(headerOfInterest) && !DoesHeaderContainEtag(headerOfInterest, entityTag))) { // set response entityTag directly because it has already been validated WebOperationContext.Current.OutgoingResponse.ETag = entityTag; throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new WebFaultException(HttpStatusCode.PreconditionFailed)); } } } static bool DoesHeaderContainEtag(string header, string entityTag) { int offset = 0; while (true) { string nextEntityTag = Utility.QuoteAwareSubString(header, ref offset); if (nextEntityTag == null) { break; } if (string.Equals(nextEntityTag, entityTag, StringComparison.Ordinal)) { return true; } } return false; } static bool IsWildCardCharacter(string header) { return (header.Trim() == "*"); } } class AcceptHeaderElementComparer : IComparer { static NumberStyles numberStyles = NumberStyles.AllowDecimalPoint; public int Compare(ContentType x, ContentType y) { string[] xTypeSubType = x.MediaType.Split('/'); string[] yTypeSubType = y.MediaType.Split('/'); Fx.Assert(xTypeSubType.Length == 2, "The creation of the ContentType would have failed if there wasn't a type and subtype."); Fx.Assert(yTypeSubType.Length == 2, "The creation of the ContentType would have failed if there wasn't a type and subtype."); if (string.Equals(xTypeSubType[0], yTypeSubType[0], StringComparison.OrdinalIgnoreCase)) { if (string.Equals(xTypeSubType[1], yTypeSubType[1], StringComparison.OrdinalIgnoreCase)) { // need to check the number of parameters to determine which is more specific bool xHasParam = HasParameters(x); bool yHasParam = HasParameters(y); if (xHasParam && !yHasParam) { return 1; } else if (!xHasParam && yHasParam) { return -1; } } else { if (xTypeSubType[1][0] == '*' && xTypeSubType[1].Length == 1) { return 1; } if (yTypeSubType[1][0] == '*' && yTypeSubType[1].Length == 1) { return -1; } } } else if (xTypeSubType[0][0] == '*' && xTypeSubType[0].Length == 1) { return 1; } else if (yTypeSubType[0][0] == '*' && yTypeSubType[0].Length == 1) { return -1; } decimal qualityDifference = GetQualityFactor(x) - GetQualityFactor(y); if (qualityDifference < 0) { return 1; } else if (qualityDifference > 0) { return -1; } return 0; } decimal GetQualityFactor(ContentType contentType) { decimal result; foreach (string key in contentType.Parameters.Keys) { if (string.Equals("q", key, StringComparison.OrdinalIgnoreCase)) { if (decimal.TryParse(contentType.Parameters[key], numberStyles, CultureInfo.InvariantCulture, out result) && (result <= (decimal)1.0)) { return result; } } } return (decimal)1.0; } bool HasParameters(ContentType contentType) { int number = 0; foreach (string param in contentType.Parameters.Keys) { if (!string.Equals("q", param, StringComparison.OrdinalIgnoreCase)) { number++; } } return (number > 0); } } }