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);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|