1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
|
//------------------------------------------------------------------------------
// <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 DOS mitigation mode.
// 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);
}
}
|