321 lines
12 KiB
C#
321 lines
12 KiB
C#
|
//------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//------------------------------------------------------------
|
||
|
#pragma warning disable 1634, 1691
|
||
|
|
||
|
namespace System.ServiceModel.Web
|
||
|
{
|
||
|
using System;
|
||
|
using System.Globalization;
|
||
|
using System.Net;
|
||
|
using System.Runtime;
|
||
|
using System.ServiceModel;
|
||
|
using System.ServiceModel.Channels;
|
||
|
using System.ServiceModel.Description;
|
||
|
using System.Text;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
public class OutgoingWebResponseContext
|
||
|
{
|
||
|
internal static readonly string WebResponseFormatPropertyName = "WebResponseFormatProperty";
|
||
|
internal static readonly string AutomatedFormatSelectionContentTypePropertyName = "AutomatedFormatSelectionContentTypePropertyName";
|
||
|
|
||
|
Encoding bindingWriteEncoding = null;
|
||
|
|
||
|
OperationContext operationContext;
|
||
|
internal OutgoingWebResponseContext(OperationContext operationContext)
|
||
|
{
|
||
|
Fx.Assert(operationContext != null, "operationContext is null");
|
||
|
this.operationContext = operationContext;
|
||
|
}
|
||
|
|
||
|
public long ContentLength
|
||
|
{
|
||
|
get { return long.Parse(this.MessageProperty.Headers[HttpResponseHeader.ContentLength], CultureInfo.InvariantCulture); }
|
||
|
set { this.MessageProperty.Headers[HttpResponseHeader.ContentLength] = value.ToString(CultureInfo.InvariantCulture); }
|
||
|
}
|
||
|
|
||
|
public string ContentType
|
||
|
{
|
||
|
get { return this.MessageProperty.Headers[HttpResponseHeader.ContentType]; }
|
||
|
set { this.MessageProperty.Headers[HttpResponseHeader.ContentType] = value; }
|
||
|
}
|
||
|
|
||
|
public string ETag
|
||
|
{
|
||
|
get { return this.MessageProperty.Headers[HttpResponseHeader.ETag]; }
|
||
|
set { this.MessageProperty.Headers[HttpResponseHeader.ETag] = value; }
|
||
|
}
|
||
|
|
||
|
public WebHeaderCollection Headers
|
||
|
{
|
||
|
get { return this.MessageProperty.Headers; }
|
||
|
}
|
||
|
|
||
|
public DateTime LastModified
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
string dateTime = this.MessageProperty.Headers[HttpRequestHeader.LastModified];
|
||
|
if (!string.IsNullOrEmpty(dateTime))
|
||
|
{
|
||
|
DateTime parsedDateTime;
|
||
|
if (DateTime.TryParse(dateTime, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDateTime))
|
||
|
{
|
||
|
return parsedDateTime;
|
||
|
}
|
||
|
}
|
||
|
return DateTime.MinValue;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.MessageProperty.Headers[HttpResponseHeader.LastModified] =
|
||
|
(value.Kind == DateTimeKind.Utc ?
|
||
|
value.ToString("R", CultureInfo.InvariantCulture) :
|
||
|
value.ToUniversalTime().ToString("R", CultureInfo.InvariantCulture));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public string Location
|
||
|
{
|
||
|
get { return this.MessageProperty.Headers[HttpResponseHeader.Location]; }
|
||
|
set { this.MessageProperty.Headers[HttpResponseHeader.Location] = value; }
|
||
|
}
|
||
|
|
||
|
public HttpStatusCode StatusCode
|
||
|
{
|
||
|
get { return this.MessageProperty.StatusCode; }
|
||
|
set { this.MessageProperty.StatusCode = value; }
|
||
|
}
|
||
|
|
||
|
public string StatusDescription
|
||
|
{
|
||
|
get { return this.MessageProperty.StatusDescription; }
|
||
|
set { this.MessageProperty.StatusDescription = value; }
|
||
|
}
|
||
|
|
||
|
public bool SuppressEntityBody
|
||
|
{
|
||
|
get { return this.MessageProperty.SuppressEntityBody; }
|
||
|
set { this.MessageProperty.SuppressEntityBody = value; }
|
||
|
}
|
||
|
|
||
|
public WebMessageFormat? Format
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (!operationContext.OutgoingMessageProperties.ContainsKey(WebResponseFormatPropertyName))
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
return operationContext.OutgoingMessageProperties[WebResponseFormatPropertyName] as WebMessageFormat?;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
if (value.HasValue)
|
||
|
{
|
||
|
if (!WebMessageFormatHelper.IsDefined(value.Value))
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value"));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
operationContext.OutgoingMessageProperties[WebResponseFormatPropertyName] = value.Value;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
operationContext.OutgoingMessageProperties[WebResponseFormatPropertyName] = null;
|
||
|
}
|
||
|
this.AutomatedFormatSelectionContentType = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This is an internal property because we need to carry the content-type that was selected by the FormatSelectingMessageInspector
|
||
|
// forward so that the formatter has access to it. However, we dond't want to use the ContentType property on this, because then
|
||
|
// developers would have to clear the ContentType property manually when overriding the format set by the
|
||
|
// FormatSelectingMessageInspector
|
||
|
internal string AutomatedFormatSelectionContentType
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (!operationContext.OutgoingMessageProperties.ContainsKey(AutomatedFormatSelectionContentTypePropertyName))
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
return operationContext.OutgoingMessageProperties[AutomatedFormatSelectionContentTypePropertyName] as string;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
operationContext.OutgoingMessageProperties[AutomatedFormatSelectionContentTypePropertyName] = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Encoding BindingWriteEncoding
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (this.bindingWriteEncoding == null)
|
||
|
{
|
||
|
string endpointId = this.operationContext.EndpointDispatcher.Id;
|
||
|
Fx.Assert(endpointId != null, "There should always be an valid EndpointDispatcher.Id");
|
||
|
foreach (ServiceEndpoint endpoint in this.operationContext.Host.Description.Endpoints)
|
||
|
{
|
||
|
if (endpoint.Id == endpointId)
|
||
|
{
|
||
|
WebMessageEncodingBindingElement encodingElement = endpoint.Binding.CreateBindingElements().Find<WebMessageEncodingBindingElement>() as WebMessageEncodingBindingElement;
|
||
|
if (encodingElement != null)
|
||
|
{
|
||
|
this.bindingWriteEncoding = encodingElement.WriteEncoding;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return this.bindingWriteEncoding;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal HttpResponseMessageProperty MessageProperty
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (!operationContext.OutgoingMessageProperties.ContainsKey(HttpResponseMessageProperty.Name))
|
||
|
{
|
||
|
operationContext.OutgoingMessageProperties.Add(HttpResponseMessageProperty.Name, new HttpResponseMessageProperty());
|
||
|
}
|
||
|
return operationContext.OutgoingMessageProperties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetETag(string entityTag)
|
||
|
{
|
||
|
this.ETag = GenerateValidEtagFromString(entityTag);
|
||
|
}
|
||
|
|
||
|
public void SetETag(int entityTag)
|
||
|
{
|
||
|
this.ETag = GenerateValidEtag(entityTag);
|
||
|
}
|
||
|
|
||
|
public void SetETag(long entityTag)
|
||
|
{
|
||
|
this.ETag = GenerateValidEtag(entityTag);
|
||
|
}
|
||
|
|
||
|
public void SetETag(Guid entityTag)
|
||
|
{
|
||
|
this.ETag = GenerateValidEtag(entityTag);
|
||
|
}
|
||
|
|
||
|
public void SetStatusAsCreated(Uri locationUri)
|
||
|
{
|
||
|
if (locationUri == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("locationUri");
|
||
|
}
|
||
|
this.StatusCode = HttpStatusCode.Created;
|
||
|
this.Location = locationUri.ToString();
|
||
|
}
|
||
|
|
||
|
public void SetStatusAsNotFound()
|
||
|
{
|
||
|
this.StatusCode = HttpStatusCode.NotFound;
|
||
|
}
|
||
|
|
||
|
public void SetStatusAsNotFound(string description)
|
||
|
{
|
||
|
this.StatusCode = HttpStatusCode.NotFound;
|
||
|
this.StatusDescription = description;
|
||
|
}
|
||
|
|
||
|
internal static string GenerateValidEtagFromString(string entityTag)
|
||
|
{
|
||
|
// This method will generate a valid entityTag from a string by doing the following:
|
||
|
// 1) Adding surrounding double quotes if the string doesn't already start and end with them
|
||
|
// 2) Escaping any internal double quotes that aren't already escaped (preceded with a backslash)
|
||
|
// 3) If a string starts with a double quote but doesn't end with one, or vice-versa, then the
|
||
|
// double quote is considered internal and is escaped.
|
||
|
|
||
|
if (string.IsNullOrEmpty(entityTag))
|
||
|
{
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if (entityTag.StartsWith("W/\"", StringComparison.OrdinalIgnoreCase) &&
|
||
|
entityTag.EndsWith("\"", StringComparison.OrdinalIgnoreCase))
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
|
||
|
SR2.GetString(SR2.WeakEntityTagsNotSupported, entityTag)));
|
||
|
}
|
||
|
|
||
|
List<int> escapeCharacterInsertIndices = null;
|
||
|
int lastEtagIndex = entityTag.Length - 1;
|
||
|
bool startsWithQuote = entityTag[0] == '\"';
|
||
|
bool endsWithQuote = entityTag[lastEtagIndex] == '\"';
|
||
|
|
||
|
// special case where the entityTag is a single character, a double quote, '"'
|
||
|
if (lastEtagIndex == 0 && startsWithQuote)
|
||
|
{
|
||
|
endsWithQuote = false;
|
||
|
}
|
||
|
|
||
|
bool needsSurroundingQuotes = !startsWithQuote || !endsWithQuote;
|
||
|
|
||
|
if (startsWithQuote && !endsWithQuote)
|
||
|
{
|
||
|
if (escapeCharacterInsertIndices == null)
|
||
|
{
|
||
|
escapeCharacterInsertIndices = new List<int>();
|
||
|
}
|
||
|
escapeCharacterInsertIndices.Add(0);
|
||
|
}
|
||
|
|
||
|
for (int x = 1; x < lastEtagIndex; x++)
|
||
|
{
|
||
|
if (entityTag[x] == '\"' && entityTag[x - 1] != '\\')
|
||
|
{
|
||
|
if (escapeCharacterInsertIndices == null)
|
||
|
{
|
||
|
escapeCharacterInsertIndices = new List<int>();
|
||
|
}
|
||
|
escapeCharacterInsertIndices.Add(x + escapeCharacterInsertIndices.Count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Possible that the ending internal quote is already escaped so must check the character before it
|
||
|
if (!startsWithQuote && endsWithQuote && entityTag[lastEtagIndex - 1] != '\\')
|
||
|
{
|
||
|
if (escapeCharacterInsertIndices == null)
|
||
|
{
|
||
|
escapeCharacterInsertIndices = new List<int>();
|
||
|
}
|
||
|
escapeCharacterInsertIndices.Add(lastEtagIndex + escapeCharacterInsertIndices.Count);
|
||
|
}
|
||
|
|
||
|
if (needsSurroundingQuotes || escapeCharacterInsertIndices != null)
|
||
|
{
|
||
|
int escapeCharacterInsertIndicesCount = (escapeCharacterInsertIndices == null) ? 0 : escapeCharacterInsertIndices.Count;
|
||
|
StringBuilder editedEtag = new StringBuilder(entityTag, entityTag.Length + escapeCharacterInsertIndicesCount + 2);
|
||
|
for (int x = 0; x < escapeCharacterInsertIndicesCount; x++)
|
||
|
{
|
||
|
editedEtag.Insert(escapeCharacterInsertIndices[x], '\\');
|
||
|
}
|
||
|
if (needsSurroundingQuotes)
|
||
|
{
|
||
|
editedEtag.Insert(entityTag.Length + escapeCharacterInsertIndicesCount, '\"');
|
||
|
editedEtag.Insert(0, '\"');
|
||
|
}
|
||
|
entityTag = editedEtag.ToString();
|
||
|
}
|
||
|
|
||
|
return entityTag;
|
||
|
}
|
||
|
|
||
|
internal static string GenerateValidEtag(object entityTag)
|
||
|
{
|
||
|
return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", entityTag.ToString());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|