|
//------------------------------------------------------------
// 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<ContentType> 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<string> IfMatch
{
get
{
string ifMatchHeader = MessageProperty.Headers[HttpRequestHeader.IfMatch];
return (string.IsNullOrEmpty(ifMatchHeader)) ? null : Utility.QuoteAwareStringSplit(ifMatchHeader);
}
}
public IEnumerable<string> 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<ContentType> GetAcceptHeaderElements()
{
string acceptHeader = this.Accept;
if (cachedAcceptHeaderElements == null ||
(!string.Equals(acceptHeaderWhenHeaderElementsCached, acceptHeader, StringComparison.OrdinalIgnoreCase)))
{
if (string.IsNullOrEmpty(acceptHeader))
{
cachedAcceptHeaderElements = new Collection<ContentType>();
acceptHeaderWhenHeaderElementsCached = acceptHeader;
}
else
{
List<ContentType> contentTypeList = new List<ContentType>();
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<ContentType>(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<ContentType>
{
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);
}
}
}
|