e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
334 lines
15 KiB
C#
334 lines
15 KiB
C#
//------------------------------------------------------------------------------
|
||
// <copyright file="ServerProtocol.cs" company="Microsoft">
|
||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
// </copyright>
|
||
//------------------------------------------------------------------------------
|
||
|
||
namespace System.Web.Services.Protocols {
|
||
using System;
|
||
using System.Diagnostics;
|
||
using System.Collections;
|
||
using System.IO;
|
||
using System.Reflection;
|
||
using System.Xml.Serialization;
|
||
using System.Web.Caching;
|
||
using System.ComponentModel;
|
||
using System.Text;
|
||
using System.Net;
|
||
using System.Web.Services;
|
||
using System.Threading;
|
||
using System.Security.Permissions;
|
||
using System.Web.Services.Diagnostics;
|
||
|
||
[PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
|
||
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
|
||
public abstract class ServerProtocol {
|
||
Type type;
|
||
HttpRequest request;
|
||
HttpResponse response;
|
||
HttpContext context;
|
||
object target;
|
||
WebMethodAttribute methodAttr;
|
||
|
||
private static Object s_InternalSyncObject;
|
||
internal static Object InternalSyncObject {
|
||
get {
|
||
if (s_InternalSyncObject == null) {
|
||
Object o = new Object();
|
||
Interlocked.CompareExchange(ref s_InternalSyncObject, o, null);
|
||
}
|
||
return s_InternalSyncObject;
|
||
}
|
||
}
|
||
|
||
internal void SetContext(Type type, HttpContext context, HttpRequest request, HttpResponse response) {
|
||
PartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
|
||
this.type = type;
|
||
this.context = context;
|
||
this.request = request;
|
||
this.response = response;
|
||
Initialize();
|
||
}
|
||
|
||
internal virtual void CreateServerInstance() {
|
||
target = Activator.CreateInstance(ServerType.Type);
|
||
WebService service = target as WebService;
|
||
if (service != null)
|
||
service.SetContext(context);
|
||
}
|
||
|
||
internal virtual void DisposeServerInstance() {
|
||
if (target == null) return;
|
||
IDisposable disposable = target as IDisposable;
|
||
if (disposable != null)
|
||
disposable.Dispose();
|
||
target = null;
|
||
}
|
||
|
||
protected internal HttpContext Context {
|
||
get { return context; }
|
||
}
|
||
|
||
protected internal HttpRequest Request {
|
||
get { return request; }
|
||
}
|
||
|
||
protected internal HttpResponse Response {
|
||
get { return response; }
|
||
}
|
||
|
||
internal Type Type {
|
||
get { return type; }
|
||
}
|
||
|
||
protected virtual internal object Target {
|
||
get { return target; }
|
||
}
|
||
|
||
internal virtual bool WriteException(Exception e, Stream outputStream) {
|
||
// return true if exception should not be re-thrown to ASP.NET
|
||
return false;
|
||
}
|
||
|
||
internal abstract bool Initialize();
|
||
internal abstract object[] ReadParameters();
|
||
internal abstract void WriteReturns(object[] returns, Stream outputStream);
|
||
internal abstract LogicalMethodInfo MethodInfo { get; }
|
||
internal abstract ServerType ServerType { get; }
|
||
internal abstract bool IsOneWay { get; }
|
||
internal virtual Exception OnewayInitException { get { return null; } }
|
||
|
||
internal WebMethodAttribute MethodAttribute {
|
||
get {
|
||
if (methodAttr == null)
|
||
methodAttr = MethodInfo.MethodAttribute;
|
||
return methodAttr;
|
||
}
|
||
}
|
||
|
||
internal string GenerateFaultString(Exception e) {
|
||
return GenerateFaultString(e, false);
|
||
}
|
||
|
||
internal static void SetHttpResponseStatusCode(HttpResponse httpResponse, int statusCode) {
|
||
// We skip IIS custom errors for HTTP requests.
|
||
httpResponse.TrySkipIisCustomErrors = true;
|
||
httpResponse.StatusCode = statusCode;
|
||
}
|
||
|
||
//
|
||
|
||
internal string GenerateFaultString(Exception e, bool htmlEscapeMessage) {
|
||
bool isDevelopmentServer = Context != null && !Context.IsCustomErrorEnabled;
|
||
if (isDevelopmentServer && !htmlEscapeMessage) {
|
||
//If the user has specified it's a development server (versus a production server) in ASP.NET config,
|
||
//then we should just return e.ToString instead of extracting the list of messages.
|
||
return e.ToString();
|
||
}
|
||
StringBuilder builder = new StringBuilder();
|
||
if (isDevelopmentServer) {
|
||
// we are dumping the ecseption directly to IE, need to encode
|
||
GenerateFaultString(e, builder);
|
||
}
|
||
else {
|
||
for (Exception inner = e; inner != null; inner = inner.InnerException) {
|
||
string text = htmlEscapeMessage ? HttpUtility.HtmlEncode(inner.Message) : inner.Message;
|
||
if (text.Length == 0) text = e.GetType().Name;
|
||
builder.Append(text);
|
||
if (inner.InnerException != null) builder.Append(" ---> ");
|
||
}
|
||
}
|
||
return builder.ToString();
|
||
}
|
||
|
||
static void GenerateFaultString(Exception e, StringBuilder builder) {
|
||
builder.Append(e.GetType().FullName);
|
||
if (e.Message != null && e.Message.Length > 0) {
|
||
builder.Append(": ");
|
||
builder.Append(HttpUtility.HtmlEncode(e.Message));
|
||
}
|
||
if (e.InnerException != null) {
|
||
builder.Append(" ---> ");
|
||
GenerateFaultString(e.InnerException, builder);
|
||
builder.Append(Environment.NewLine);
|
||
builder.Append(" ");
|
||
builder.Append(Res.GetString(Res.StackTraceEnd));
|
||
}
|
||
if (e.StackTrace != null) {
|
||
builder.Append(Environment.NewLine);
|
||
builder.Append(e.StackTrace);
|
||
}
|
||
}
|
||
|
||
internal void WriteOneWayResponse() {
|
||
context.Response.ContentType = null;
|
||
Response.StatusCode = (int)HttpStatusCode.Accepted;
|
||
}
|
||
|
||
delegate string CreateCustomKeyForAspNetWebServiceMetadataCache(Type protocolType, Type serverType, string originalKey);
|
||
|
||
static string DefaultCreateCustomKeyForAspNetWebServiceMetadataCache(Type protocolType, Type serverType, string originalKey) {
|
||
return originalKey;
|
||
}
|
||
|
||
static CreateCustomKeyForAspNetWebServiceMetadataCache GetCreateCustomKeyForAspNetWebServiceMetadataCacheDelegate(Type serverType) {
|
||
PartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
|
||
string key = "CreateCustomKeyForAspNetWebServiceMetadataCache-" + serverType.FullName;
|
||
CreateCustomKeyForAspNetWebServiceMetadataCache result = (CreateCustomKeyForAspNetWebServiceMetadataCache)HttpRuntime.Cache.Get(key);
|
||
if (result == null) {
|
||
MethodInfo createKeyMethod = serverType.GetMethod(
|
||
"CreateCustomKeyForAspNetWebServiceMetadataCache",
|
||
BindingFlags.Public | BindingFlags.Static | BindingFlags.ExactBinding | BindingFlags.FlattenHierarchy,
|
||
null,
|
||
new Type[] { typeof(Type), typeof(Type), typeof(string) },
|
||
null);
|
||
|
||
if (createKeyMethod == null) {
|
||
result = ServerProtocol.DefaultCreateCustomKeyForAspNetWebServiceMetadataCache;
|
||
|
||
} else {
|
||
result = delegate(Type pt, Type st, string originalString)
|
||
{
|
||
return (string)createKeyMethod.Invoke(null, new object[] { pt, st, originalString });
|
||
};
|
||
}
|
||
|
||
HttpRuntime.Cache.Add(key, result, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
string CreateKey(Type protocolType, Type serverType, bool excludeSchemeHostPort = false, string keySuffix = null) {
|
||
//
|
||
// we want to use the hostname to cache since for documentation, WSDL
|
||
// contains the cache hostname, but we definitely don't want to cache the query string!
|
||
//
|
||
string protocolTypeName = protocolType.FullName;
|
||
string serverTypeName = serverType.FullName;
|
||
string typeHandleString = serverType.TypeHandle.Value.ToString();
|
||
string url = excludeSchemeHostPort ? Request.Url.AbsolutePath : Request.Url.GetLeftPart(UriPartial.Path);
|
||
int length = protocolTypeName.Length + url.Length + serverTypeName.Length + typeHandleString.Length;
|
||
StringBuilder sb = new StringBuilder(length);
|
||
sb.Append(protocolTypeName);
|
||
sb.Append(url);
|
||
sb.Append(serverTypeName);
|
||
sb.Append(typeHandleString);
|
||
if (keySuffix != null) {
|
||
sb.Append(keySuffix);
|
||
}
|
||
|
||
CreateCustomKeyForAspNetWebServiceMetadataCache createKey = ServerProtocol.GetCreateCustomKeyForAspNetWebServiceMetadataCacheDelegate(serverType);
|
||
|
||
return createKey(protocolType, serverType, sb.ToString());
|
||
}
|
||
|
||
protected void AddToCache(Type protocolType, Type serverType, object value) {
|
||
this.AddToCache(protocolType, serverType, value, false);
|
||
}
|
||
|
||
// See comment on the ServerProtocol.IsCacheUnderPressure method for explanation of the excludeSchemeHostPort logic.
|
||
internal void AddToCache(Type protocolType, Type serverType, object value, bool excludeSchemeHostPort) {
|
||
PartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
|
||
HttpRuntime.Cache.Insert(CreateKey(protocolType, serverType, excludeSchemeHostPort),
|
||
value,
|
||
null,
|
||
Cache.NoAbsoluteExpiration,
|
||
Cache.NoSlidingExpiration,
|
||
CacheItemPriority.NotRemovable,
|
||
null);
|
||
}
|
||
|
||
protected object GetFromCache(Type protocolType, Type serverType) {
|
||
return this.GetFromCache(protocolType, serverType, false);
|
||
}
|
||
|
||
internal object GetFromCache(Type protocolType, Type serverType, bool excludeSchemeHostPort) {
|
||
PartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
|
||
return HttpRuntime.Cache.Get(CreateKey(protocolType, serverType, excludeSchemeHostPort));
|
||
}
|
||
|
||
// IsCacheUnderPressure is part of a DOS mitigation mechanism addressing CSDMain#195148. Original problem: when a large number of
|
||
// HTTP requests for WSDL or the ASMX documentation page is made, each request with a unique value of the HOST header, a unique response
|
||
// is generated and cached for each of the requests (responses contain the scheme/host/port of the request).
|
||
// This leads to ever growing memory consumption and eventual crash of the process.
|
||
// The mitigation for this DOS attack uses the following mechanism:
|
||
// 1. The behavior of the system remains unchanged for the first 10 requests for WSDL of a given ASMX service that have differing
|
||
// scheme/host/port combination of the request URI. This is to avoid breaking behavioral changes in the 99.99% case,
|
||
// since the DOS attack cannot be generically fixed without breaking behavioral changes. The value of 10 is baked in,
|
||
// and we consider it a reasonable default based on the assumption that ASMX services in most circumstances cannot be
|
||
// reached using more than 10 different values of the scheme/host/port.
|
||
// 2. For any requests for WSDL going beyond the 10 limit of scheme/host/port combination, we go into a <20>DOS mitigation mode<64>.
|
||
// The mode prevents the eventual process crash while introducing marginal breaking behavioral changes:
|
||
// a. We create a single service description and cache it using the AbsolutePath of the request URI alone
|
||
// (as opposed to scheme/host/port + AbsolutePath).
|
||
// b. For every request for WSDL/disco/documentation document, we fix up the URLs in the returned document to match the
|
||
// scheme/host/port of the actual request for WSDL/disco. This fixup only applies to the WSDL extensions we have shipped in .NET
|
||
// and does not apply to custom extensions implemented externally, hence the breaking behavioral change.
|
||
// This mechamism affects the DiscoveryServerProtocol and DocumentationServerProtocol.
|
||
internal bool IsCacheUnderPressure(Type protocolType, Type serverType) {
|
||
PartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
|
||
|
||
const int threshold = 10;
|
||
string key = this.CreateKey(protocolType, serverType, true, "CachePressure");
|
||
ServerProtocolCachePressure item = (ServerProtocolCachePressure)HttpRuntime.Cache.Get(key);
|
||
|
||
// There is a potential race condition in creating a new entry or increasing the value of an existing entry,
|
||
// but it is acceptable since DOS threshold enforcement need not be exact.
|
||
|
||
if (item != null) {
|
||
return item.Pressure < threshold ? Interlocked.Increment(ref item.Pressure) >= threshold : false;
|
||
}
|
||
else {
|
||
HttpRuntime.Cache.Insert(
|
||
key,
|
||
new ServerProtocolCachePressure { Pressure = 1 },
|
||
null,
|
||
Cache.NoAbsoluteExpiration,
|
||
Cache.NoSlidingExpiration,
|
||
CacheItemPriority.NotRemovable,
|
||
null);
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
class ServerProtocolCachePressure {
|
||
public int Pressure;
|
||
}
|
||
}
|
||
|
||
[PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
|
||
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
|
||
public abstract class ServerProtocolFactory {
|
||
internal ServerProtocol Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, out bool abortProcessing) {
|
||
ServerProtocol serverProtocol = null;
|
||
abortProcessing = false;
|
||
serverProtocol = CreateIfRequestCompatible(request);
|
||
try {
|
||
if (serverProtocol != null)
|
||
serverProtocol.SetContext(type, context, request, response);
|
||
return serverProtocol;
|
||
}
|
||
catch (Exception e) {
|
||
abortProcessing = true;
|
||
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
||
throw;
|
||
}
|
||
if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "Create", e);
|
||
if (serverProtocol != null) {
|
||
// give the protocol a shot at handling the error in a custom way
|
||
if (!serverProtocol.WriteException(e, serverProtocol.Response.OutputStream))
|
||
throw new InvalidOperationException(Res.GetString(Res.UnableToHandleRequest0), e);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
}
|
||
|
||
protected abstract ServerProtocol CreateIfRequestCompatible(HttpRequest request);
|
||
|
||
}
|
||
|
||
}
|