//----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//---------------------------------------------------------------------------
namespace System.ServiceModel
{
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime;
using System.Runtime.Diagnostics;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.ServiceModel.Activation;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Activation.Diagnostics;
using System.ServiceModel.Description;
using System.Threading;
using System.Web;
using System.Web.Compilation;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web.Routing;
using System.Xaml.Hosting.Configuration;
using System.Text;
using SR2 = System.ServiceModel.Activation.SR;
using TD2 = System.ServiceModel.Diagnostics.Application.TD;
[TypeForwardedFrom("System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public static class ServiceHostingEnvironment
{
static object syncRoot = new object();
static HostingManager hostingManager;
static bool isHosted;
static bool isSimpleApplicationHost;
static Int64 requestCount;
static bool didAssemblyCheck;
static bool isApplicationDomainHosted;
static bool canGetHtmlErrorMessage = true;
static string siteName;
static string applicationVirtualPath;
static string serviceActivationElementPath;
internal const string VerbPost = "POST";
internal const string ISAPIApplicationIdPrefix = "/LM/W3SVC/";
internal const string RelativeVirtualPathPrefix = "~";
internal const string ServiceParserDelimiter = "|";
internal const string RootVirtualPath = "~/";
internal const string PathSeparatorString = "/";
const char FileExtensionSeparator = '.';
const char UriSchemeSeparator = ':';
const char PathSeparator = '/';
const string SystemWebComma = "System.Web,";
[Fx.Tag.SecurityNote(Critical = "Calls into an unsafe UnsafeLogEvent method.",
Safe = "Event identities cannot be spoofed as they are constants determined inside the method.")]
[SecuritySafeCritical]
static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (DiagnosticUtility.ShouldTraceError)
{
Exception exception = e.ExceptionObject as Exception;
DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, System.ServiceModel.Diagnostics.EventLogCategory.WebHost, System.ServiceModel.Diagnostics.EventLogEventId.WebHostUnhandledException, true,
TraceUtility.CreateSourceString(sender),
exception == null ? string.Empty : exception.ToString());
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ProcessRequest outside of the restricted SecurityContext.")]
public static bool AspNetCompatibilityEnabled
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
get
{
if (!IsHosted)
{
return false;
}
return IsAspNetCompatibilityEnabled();
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ProcessRequest outside of the restricted SecurityContext.")]
public static bool MultipleSiteBindingsEnabled
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
get
{
if (!IsHosted)
return false;
return IsMultipleSiteBindingsEnabledEnabled();
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ServiceHostFactory.CreateServiceHost.")]
internal static Uri[] PrefixFilters
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview")]
get
{
if (!IsHosted)
{
return null;
}
return GetBaseAddressPrefixFilters();
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
[MethodImpl(MethodImplOptions.NoInlining)]
static bool IsAspNetCompatibilityEnabled()
{
return hostingManager.AspNetCompatibilityEnabled;
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
[MethodImpl(MethodImplOptions.NoInlining)]
static bool IsMultipleSiteBindingsEnabledEnabled()
{
return hostingManager.MultipleSiteBindingsEnabled;
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
[MethodImpl(MethodImplOptions.NoInlining)]
static Uri[] GetBaseAddressPrefixFilters()
{
return hostingManager.BaseAddressPrefixFilters;
}
public static void EnsureServiceAvailable(string virtualPath)
{
if (string.IsNullOrEmpty(virtualPath))
{
throw FxTrace.Exception.ArgumentNull("virtualPath");
}
if (virtualPath.IndexOf(UriSchemeSeparator) > 0)
{
throw FxTrace.Exception.Argument("virtualPath", SR2.Hosting_AddressIsAbsoluteUri(virtualPath));
}
EnsureInitialized();
virtualPath = NormalizeVirtualPath(virtualPath);
EnsureServiceAvailableFast(virtualPath);
}
internal static void EnsureServiceAvailableFast(string relativeVirtualPath)
{
try
{
hostingManager.EnsureServiceAvailable(relativeVirtualPath);
}
catch (ServiceActivationException exception)
{
LogServiceActivationException(exception);
throw;
}
}
[Fx.Tag.SecurityNote(Critical = "Calls into an unsafe UnsafeLogEvent method.",
Safe = "Event identities cannot be spoofed as they are constants determined inside the method.")]
[SecuritySafeCritical]
private static void LogServiceActivationException(ServiceActivationException exception)
{
if (exception.InnerException is HttpException)
{
string messageAsString = SafeTryGetHtmlErrorMessage((HttpException)exception.InnerException);
if (string.IsNullOrEmpty(messageAsString))
{
messageAsString = exception.Message;
}
DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, System.ServiceModel.Diagnostics.EventLogCategory.WebHost, System.ServiceModel.Diagnostics.EventLogEventId.WebHostHttpError, true,
TraceUtility.CreateSourceString(hostingManager),
messageAsString, exception.ToString());
}
else
{
DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, System.ServiceModel.Diagnostics.EventLogCategory.WebHost, System.ServiceModel.Diagnostics.EventLogEventId.WebHostFailedToProcessRequest, true,
TraceUtility.CreateSourceString(hostingManager), exception.ToString());
}
if (TD2.ServiceExceptionIsEnabled())
{
TD2.ServiceException(exception.ToString(), typeof(ServiceActivationException).FullName);
}
}
static string SafeTryGetHtmlErrorMessage(HttpException exception)
{
if (exception != null && canGetHtmlErrorMessage)
{
try
{
return exception.GetHtmlErrorMessage();
}
catch (SecurityException e)
{
canGetHtmlErrorMessage = false;
// not re-throwing on purpose
if (DiagnosticUtility.ShouldTraceWarning)
{
DiagnosticUtility.ExceptionUtility.TraceHandledException(e, TraceEventType.Warning);
}
}
}
return null;
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
internal static void IncrementRequestCount()
{
Interlocked.Increment(ref requestCount);
if (TD.WebHostRequestStartIsEnabled())
{
TD.WebHostRequestStart();
}
}
internal static void DecrementRequestCount()
{
Interlocked.Decrement(ref requestCount);
Fx.Assert(requestCount >= 0, "Request count should always be non-nagative.");
if (requestCount == 0)
{
if (hostingManager != null)
{
hostingManager.NotifyAllRequestDone();
}
}
if (TD.WebHostRequestStopIsEnabled())
{
TD.WebHostRequestStop();
}
}
internal static string CurrentVirtualPath
{
get
{
Fx.Assert(IsHosted, "CurrentVirtualPath should not be called from non web-hosted environment.");
return HostingManager.CurrentVirtualPath;
}
}
internal static string ServiceActivationElementPath
{
get
{
if (ServiceHostingEnvironment.serviceActivationElementPath == null)
{
ServiceHostingEnvironment.serviceActivationElementPath = string.Format(CultureInfo.CurrentCulture, "{0}/{1}",
ConfigurationStrings.ServiceHostingEnvironmentSectionPath, ConfigurationStrings.ServiceActivations);
}
return ServiceHostingEnvironment.serviceActivationElementPath;
}
}
internal static string SiteName
{
get
{
if (ServiceHostingEnvironment.siteName == null)
{
ServiceHostingEnvironment.siteName = HostingEnvironment.SiteName;
}
return ServiceHostingEnvironment.siteName;
}
}
internal static string ApplicationVirtualPath
{
get
{
if (ServiceHostingEnvironment.applicationVirtualPath == null)
{
ServiceHostingEnvironment.applicationVirtualPath = HostingEnvironment.ApplicationVirtualPath;
}
return ServiceHostingEnvironment.applicationVirtualPath;
}
}
internal static string FullVirtualPath
{
get
{
Fx.Assert(IsHosted, "FullVirtualPath should not be called from non web-hosted environment.");
return HostingManager.FullVirtualPath;
}
}
internal static string XamlFileBaseLocation
{
get
{
Fx.Assert(IsHosted, "XamlFileBaseLocation should not be called from non web-hosted environment.");
return HostingManager.XamlFileBaseLocation;
}
}
internal static bool IsConfigurationBased
{
get
{
Fx.Assert(IsHosted, "IsConfigurationBased should not be called from non web-hosted environment.");
return HostingManager.IsConfigurationBased;
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ProcessRequest outside of the restricted SecurityContext.")]
internal static ServiceType GetServiceType(string extension)
{
Fx.Assert(IsHosted, "GetServiceType should not be called from non web-hosted environment.");
return hostingManager.GetServiceType(extension);
}
internal static bool EnsureWorkflowService(string path)
{
Fx.Assert(IsHosted, "EnsureWorkflowService should not be called from non web-hosted environment.");
PathInfo pathInfo = PathCache.EnsurePathInfo(path);
return pathInfo.IsWorkflowService();
}
internal static bool IsRecycling
{
get
{
Fx.Assert(IsHosted, "IsRecycling should not be called from non web-hosted environment.");
return hostingManager.IsRecycling;
}
}
static object ThisLock
{
get
{
return syncRoot;
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ProcessRequest outside of the restricted SecurityContext.")]
internal static bool IsConfigurationBasedService(HttpApplication application)
{
Fx.Assert(IsHosted, "IsConfigurationBased should not be called from non web-hosted environment.");
string dummyString;
return IsConfigurationBasedService(application, out dummyString);
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by MsmqHostedTransportManager outside of the restricted SecurityContext.")]
internal static bool IsConfigurationBasedService(string virtualPath)
{
Fx.Assert(IsHosted, "IsConfigurationBased should not be called from non web-hosted environment.");
return hostingManager.IsConfigurationBasedServiceVirtualPath(virtualPath);
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ProcessRequest outside of the restricted SecurityContext.")]
internal static bool IsConfigurationBasedService(HttpApplication application, out string matchedVirtualPath)
{
Fx.Assert(IsHosted, "IsConfigurationBased should not be called from non web-hosted environment.");
bool isCBAService = false;
matchedVirtualPath = null;
string virtualPath = application.Request.AppRelativeCurrentExecutionFilePath;
if (!string.IsNullOrEmpty(virtualPath) && hostingManager.IsConfigurationBasedServiceVirtualPath(virtualPath))
{
matchedVirtualPath = virtualPath;
isCBAService = true;
}
return isCBAService;
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - called by ProcessRequest outside of the restricted SecurityContext.")]
internal static void SafeEnsureInitialized()
{
if (hostingManager == null)
{
AspNetPartialTrustHelpers.PartialTrustInvoke(new ContextCallback(OnEnsureInitialized), null);
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
internal static void EnsureAllReferencedAssemblyLoaded()
{
BuildManager.GetReferencedAssemblies();
}
static void OnEnsureInitialized(object state)
{
EnsureInitialized();
}
internal static void EnsureInitialized()
{
System.ServiceModel.Diagnostics.TraceUtility.SetEtwProviderId();
if (hostingManager != null)
{
return;
}
FxTrace.Trace.SetAnnotation(() => System.ServiceModel.Diagnostics.TraceUtility.GetAnnotation(OperationContext.Current));
lock (ThisLock)
{
if (hostingManager != null)
{
return;
}
if (!HostingEnvironmentWrapper.IsHosted)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString(SR.Hosting_ProcessNotExecutingUnderHostedContext, "ServiceHostingEnvironment.EnsureServiceAvailable")));
}
HostingManager tempHostingManager = new HostingManager();
// register the following code when we use the service environment class
// the first time
HookADUnhandledExceptionEvent();
Thread.MemoryBarrier();
isSimpleApplicationHost = GetIsSimpleApplicationHost();
HostedAspNetEnvironment.Enable();
hostingManager = tempHostingManager;
isHosted = true;
}
}
[Fx.Tag.SecurityNote(Critical = "Satisfies a LinkDemand for SecurityPermission(ControlAppDomain) on HookADUnhandledExceptionEvent.",
Safe = "No control flow in for handler.")]
[SecuritySafeCritical]
static void HookADUnhandledExceptionEvent()
{
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
}
[Fx.Tag.SecurityNote(Critical = "Uses SecurityCritical property UnsafeApplicationID to get application id with an elevation.",
Safe = "Processes result into a simple bool which is not protected.")]
[SecuritySafeCritical]
static bool GetIsSimpleApplicationHost()
{
// ASPNET won't provide API to check Cassini. But it's safe and performant to check only
// the ApplicationID prefix (MessageBus Bug 24832).
return (string.Compare(ISAPIApplicationIdPrefix, 0,
HostingEnvironmentWrapper.UnsafeApplicationID, 0, ISAPIApplicationIdPrefix.Length, StringComparison.OrdinalIgnoreCase) != 0);
}
// customer input can be "/appname//filename" or "~//filename, we will normalize them to application relative one
// i.e., "~//filename
internal static string NormalizeVirtualPath(string virtualPath)
{
string processedVirtualPath = null;
try
{
// Convert the virtual path to relative if not already is.
processedVirtualPath = VirtualPathUtility.ToAppRelative(virtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath);
}
catch (HttpException exception)
{
// We want to throw an ArgumentException.
throw FxTrace.Exception.AsError(new ArgumentException(exception.Message, "virtualPath", exception));
}
if (string.IsNullOrEmpty(processedVirtualPath) ||
!processedVirtualPath.StartsWith(RelativeVirtualPathPrefix, StringComparison.Ordinal))
{
throw FxTrace.Exception.Argument("virtualPath",
SR2.Hosting_AddressPointsOutsideTheVirtualDirectory(virtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath));
}
// Find the position to start.
int pos = processedVirtualPath.IndexOf(FileExtensionSeparator);
while (pos > 0)
{
// Search inside the processedVirtualPath to find the extension.
pos = processedVirtualPath.IndexOf(PathSeparator, pos + 1);
string subVirtualPath = (pos == -1) ? processedVirtualPath : processedVirtualPath.Substring(0, pos);
string extension = VirtualPathUtility.GetExtension(subVirtualPath);
if ((!string.IsNullOrEmpty(extension)) &&
ServiceHostingEnvironment.GetServiceType(extension)!=ServiceType.Unknown)
{
// Remove the pathinfo.
return subVirtualPath;
}
}
throw FxTrace.Exception.AsError(new EndpointNotFoundException(SR2.Hosting_ServiceNotExist(virtualPath)));
}
internal static bool IsHosted
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
get
{
return isHosted;
}
}
internal static bool IsSimpleApplicationHost
{
get
{
Fx.Assert(IsHosted, "IsSimpleApplicationHost should not be called from non web-hosted environment.");
return isSimpleApplicationHost;
}
}
internal static bool ApplicationDomainHosted
{
get
{
if (didAssemblyCheck)
{
return isApplicationDomainHosted;
}
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
if (string.Compare(assemblies[i].FullName, 0, SystemWebComma, 0, SystemWebComma.Length, StringComparison.OrdinalIgnoreCase) == 0)
{
isApplicationDomainHosted = IsApplicationDomainHosted();
break;
}
}
didAssemblyCheck = true;
return isApplicationDomainHosted;
}
}
[Fx.Tag.SecurityNote(Critical = "Assert a demand for AspNetHostingPermission.",
Safe = "Only queries if we are hosted - no actual action is initiated, no critical information is leaking.")]
[MethodImpl(MethodImplOptions.NoInlining)]
[SecuritySafeCritical]
[AspNetHostingPermission(SecurityAction.Assert, Level = AspNetHostingPermissionLevel.Minimal)]
static bool IsApplicationDomainHosted()
{
return HostingEnvironment.IsHosted;
}
internal enum ServiceType
{
Unknown = 0,
WCF,
Workflow
}
class HostingManager : IRegisteredObject
{
readonly Hashtable directory;
readonly ExtensionHelper extensions;
bool aspNetCompatibilityEnabled;
bool multipleSiteBindingsEnabled;
bool isUnregistered;
bool isRecycling;
bool isStopStarted;
static bool canDebugPrint = true;
static object syncRoot = new object();
Uri[] baseAddressPrefixFilters;
Hashtable serviceActivations;
//used to track if HostingEnvironment.RegisterObject has been called.
bool isRegistered;
// One instance per appdomain, don't need to be disposed.
ManualResetEvent allRequestDoneInStop = new ManualResetEvent(false);
[Fx.Tag.SecurityNote(Critical = "Admin-provided value that allows for machine resource allocation.")]
[SecurityCritical]
int minFreeMemoryPercentageToActivateService;
[ThreadStatic]
static string currentVirtualPath;
[ThreadStatic]
static string fullVirtualPath;
[ThreadStatic]
static string xamlFileBaseLocation;
[ThreadStatic]
static bool isConfigurationBased;
[ThreadStatic]
static bool isAspNetRoutedRequest;
internal HostingManager()
{
this.directory = new Hashtable(16, StringComparer.OrdinalIgnoreCase);
this.extensions = new ExtensionHelper();
LoadConfigParameters();
}
[Fx.Tag.SecurityNote(Critical = "Uses SecurityCritical method UnsafeGetSection to get config with an elevation. Sets minFreeMemoryPercentageToActivateService",
Safe = "Does not leak config objects.")]
[SecuritySafeCritical]
void LoadConfigParameters()
{
ServiceHostingEnvironmentSection section = ServiceHostingEnvironmentSection.UnsafeGetSection();
this.aspNetCompatibilityEnabled = section.AspNetCompatibilityEnabled;
this.multipleSiteBindingsEnabled = section.MultipleSiteBindingsEnabled;
this.minFreeMemoryPercentageToActivateService = section.MinFreeMemoryPercentageToActivateService;
List prefixFilters = new List();
foreach (BaseAddressPrefixFilterElement element in section.BaseAddressPrefixFilters)
{
prefixFilters.Add(element.Prefix);
}
this.baseAddressPrefixFilters = prefixFilters.ToArray();
this.serviceActivations = new Hashtable(StringComparer.CurrentCultureIgnoreCase);
foreach (ServiceActivationElement element in section.ServiceActivations)
{
if (string.IsNullOrEmpty(element.Factory) && string.IsNullOrEmpty(element.Service))
{
throw FxTrace.Exception.AsError(new ConfigurationErrorsException(SR2.Hosting_NoServiceAndFactorySpecifiedForFilelessService(ConfigurationStrings.Factory, ConfigurationStrings.Service, element.RelativeAddress, ServiceActivationElementPath)));
}
string normalizedRelativeAddress = NormalizedRelativeAddress(element.RelativeAddress);
string value = string.Format(CultureInfo.CurrentCulture, "{0}|{1}|{2}", normalizedRelativeAddress, element.Factory, element.Service);
try
{
this.serviceActivations.Add(normalizedRelativeAddress, value);
if (TD.CBAEntryReadIsEnabled())
{
TD.CBAEntryRead(element.RelativeAddress, normalizedRelativeAddress);
}
}
catch (ArgumentException)
{
throw FxTrace.Exception.AsError(new ConfigurationErrorsException(SR2.Hosting_RelativeAddressHasBeenAdded(element.RelativeAddress, ServiceActivationElementPath)));
}
}
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
internal ServiceType GetServiceType(string extension)
{
return extensions.GetServiceType(extension);
}
internal bool AspNetCompatibilityEnabled
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
get
{
return this.aspNetCompatibilityEnabled;
}
}
internal bool MultipleSiteBindingsEnabled
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
get
{
return this.multipleSiteBindingsEnabled;
}
}
internal Uri[] BaseAddressPrefixFilters
{
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
get
{
return this.baseAddressPrefixFilters;
}
}
internal static string CurrentVirtualPath
{
get
{
return currentVirtualPath;
}
}
internal static string FullVirtualPath
{
get
{
return fullVirtualPath;
}
}
internal static string XamlFileBaseLocation
{
get
{
return xamlFileBaseLocation;
}
}
internal static bool IsConfigurationBased
{
get
{
return isConfigurationBased;
}
}
internal static object ThisLock
{
get
{
return syncRoot;
}
}
internal bool IsRecycling
{
get
{
return isRecycling;
}
}
internal string NormalizedRelativeAddress(string relativeAddress)
{
// since it is almost impossible for us to validate the format of a relativeAddress
// we just take what users' inputs but we need to normalize them with a formal format
// so that we can index them in a table.
// we will convert "[folder/]filename.extension" to "~/[folder/]filename.extension"
string originalRelativeAddress = relativeAddress;
try
{
if (VirtualPathUtility.IsAbsolute(relativeAddress))
{
throw FxTrace.Exception.AsError(new ConfigurationErrorsException(SR2.Hosting_RelativeAddressFormatError(relativeAddress)));
}
relativeAddress = VirtualPathUtility.Combine(RootVirtualPath, relativeAddress);
string extension = VirtualPathUtility.GetExtension(relativeAddress);
if (string.IsNullOrEmpty(extension))
{
throw FxTrace.Exception.AsError(new ConfigurationErrorsException(SR2.Hosting_NoValidExtensionFoundForRegistedFilelessService(originalRelativeAddress, ServiceActivationElementPath)));
}
else if (GetServiceType(extension)==ServiceType.Unknown)
{
throw FxTrace.Exception.AsError(new ConfigurationErrorsException(SR2.Hosting_RelativeAddressExtensionNotSupportError(extension, originalRelativeAddress, ServiceActivationElementPath)));
}
}
// since we did Empty/Null string checking in configuration element validator, we should not hit ArgumentException, just catch HttpException for invalid characher
catch (HttpException ex)
{
throw FxTrace.Exception.AsError(new ConfigurationErrorsException(SR2.Hosting_RelativeAddressFormatError(originalRelativeAddress), ex));
}
return relativeAddress;
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
internal bool IsConfigurationBasedServiceVirtualPath(string normalizedVirtualPath)
{
return this.serviceActivations.ContainsKey(normalizedVirtualPath);
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
internal bool TryGetCompiledCustomStringFromCBA(string normalizedVirtualPath, out string compiledCustomString)
{
compiledCustomString = null;
bool isCBAService = false;
if (isConfigurationBased)
{
compiledCustomString = (string)serviceActivations[normalizedVirtualPath];
isCBAService = true;
}
return isCBAService;
}
internal void EnsureServiceAvailable(string normalizedVirtualPath)
{
TryDebugPrint("HostingManager.EnsureServiceAvailable(" + normalizedVirtualPath + ")");
ServiceActivationInfo activationInfo = null;
// 1. Try finding the service without a lock.
activationInfo = (ServiceActivationInfo)this.directory[normalizedVirtualPath];
if (activationInfo != null && activationInfo.Service != null)
{
return;
}
isAspNetRoutedRequest = ServiceRouteHandler.IsActiveAspNetRoute(normalizedVirtualPath);
isConfigurationBased = IsConfigurationBasedServiceVirtualPath(normalizedVirtualPath);
// 2. Use global lock to create ServiceActivationInfo if necessary.
lock (ThisLock)
{
if (!isRegistered)
{
RegisterObject();
isRegistered = true;
}
activationInfo = (ServiceActivationInfo)this.directory[normalizedVirtualPath];
if (activationInfo != null && activationInfo.Service != null)
{
return;
}
FailActivationIfRecyling(normalizedVirtualPath);
if (activationInfo == null)
{
// Check service file existence if not config based activation or aspnet routing.
if (!isAspNetRoutedRequest && !isConfigurationBased
&& !HostingEnvironmentWrapper.ServiceFileExists(normalizedVirtualPath))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new EndpointNotFoundException(
SR2.Hosting_ServiceNotExist(
VirtualPathUtility.ToAbsolute(normalizedVirtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath))));
}
activationInfo = new ServiceActivationInfo(normalizedVirtualPath);
directory.Add(normalizedVirtualPath, activationInfo);
}
}
// 3. Use local lock to activate the service.
ServiceHostBase newService = null;
lock (activationInfo)
{
if (activationInfo.Service != null)
{
// The service has been activated by another thread.
return;
}
FailActivationIfRecyling(normalizedVirtualPath);
try
{
CheckMemoryGates();
newService = ActivateService(normalizedVirtualPath);
// We need to lock and check IsRecycling here because it could ---- with Abort method.
lock (ThisLock)
{
if (!IsRecycling)
{
activationInfo.Service = newService;
}
}
if (DiagnosticUtility.ShouldTraceInformation)
{
TraceUtility.TraceEvent(
TraceEventType.Information, TraceCode.WebHostServiceActivated, SR2.TraceCodeWebHostServiceActivated,
new StringTraceRecord("VirtualPath", VirtualPathUtility.ToAbsolute(normalizedVirtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath)), this, (Exception)null);
}
if (TD.ServiceHostStartedIsEnabled())
{
string serviceName = string.Empty;
ServiceHostBase host = newService as ServiceHostBase;
if (host != null)
{
if (null != host.Description.ServiceType)
{
serviceName = host.Description.ServiceType.FullName;
}
else
{
serviceName = host.Description.Namespace + host.Description.Name;
}
}
if(string.IsNullOrEmpty(serviceName))
{
serviceName = SR2.ServiceTypeUnknown;
}
string servicePath = normalizedVirtualPath.Replace("~", ServiceHostingEnvironment.ApplicationVirtualPath + "|");
string hostReference = string.Format(CultureInfo.InvariantCulture, "{0}{1}|{2}", ServiceHostingEnvironment.SiteName, servicePath, host.Description.Name);
TD.ServiceHostStarted(serviceName, hostReference);
}
}
catch (HttpCompileException ex)
{
throw FxTrace.Exception.AsError(
new ServiceActivationException(SR2.Hosting_ServiceCannotBeActivated(VirtualPathUtility.ToAbsolute(normalizedVirtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath), ex.Message), ex));
}
catch (ServiceActivationException)
{
throw;
}
catch (Exception ex)
{
// If it is a fatal exception, don't wrap it.
if (Fx.IsFatal(ex))
{
throw;
}
throw FxTrace.Exception.AsError(
new ServiceActivationException(SR2.Hosting_ServiceCannotBeActivated(VirtualPathUtility.ToAbsolute(normalizedVirtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath), ex.Message), ex));
}
finally
{
currentVirtualPath = null;
fullVirtualPath = null;
xamlFileBaseLocation = null;
}
}
if (activationInfo.Service == null)
{
Fx.Assert(
IsRecycling && (newService != null),
"Must happen in recycling state, also new service must has been created.");
newService.Abort();
}
FailActivationIfRecyling(normalizedVirtualPath);
}
[Fx.Tag.SecurityNote(Critical = "Accesses minFreeMemoryPercentageToActivateService, calls Check.",
Safe = "No input / output, safe operation if called with administrator-provided value.")]
[SecuritySafeCritical]
void CheckMemoryGates()
{
ServiceMemoryGates.Check(this.minFreeMemoryPercentageToActivateService);
}
ServiceHostBase ActivateService(string normalizedVirtualPath)
{
ServiceHostBase service = CreateService(normalizedVirtualPath);
service.Closed += this.OnServiceClosed;
service.Faulted += this.OnServiceFaulted;
FailActivationIfRecyling(normalizedVirtualPath);
try
{
if (TD.ServiceHostOpenStartIsEnabled())
{
TD.ServiceHostOpenStart();
}
service.Open();
if (TD.ServiceHostOpenStopIsEnabled())
{
TD.ServiceHostOpenStop();
}
}
finally
{
if (service.State != CommunicationState.Opened)
{
// Abort the service to clear possible cached information.
service.Abort();
}
}
if (TD.AspNetRoutingServiceIsEnabled() && isAspNetRoutedRequest)
{
TD.AspNetRoutingService(normalizedVirtualPath);
}
return service;
}
// Why this triple try blocks instead of using "using" statement:
// 1. "using" will do the impersonation prior to entering the try,
// which leaves an opertunity to Thread.Abort this thread and get it to exit the method still impersonated.
// 2. put the assignment of unsafeImpersonate in a finally block
// in order to prevent Threat.Abort after impersonation but before the assignment.
// 3. the finally of a "using" doesn't run until exception filters higher up the stack have executed.
// they will do so in the impersonated context if an exception is thrown inside the try.
// In sumary, this should prevent the thread from existing this method well still impersonated.
[Fx.Tag.SecurityNote(Critical = "Uses SecurityCritical method UnsafeImpersonate to establish the impersonation context.",
Safe = "Does not leak anything, does not let caller influence impersonation.")]
[SecuritySafeCritical]
string GetCompiledCustomString(string normalizedVirtualPath)
{
try
{
IDisposable unsafeImpersonate = null;
try
{
string result = null;
if (!this.TryGetCompiledCustomStringFromCBA(normalizedVirtualPath, out result))
{
try
{
}
finally
{
unsafeImpersonate = HostingEnvironmentWrapper.UnsafeImpersonate();
}
result = BuildManager.GetCompiledCustomString(normalizedVirtualPath);
}
return result;
}
finally
{
if (null != unsafeImpersonate)
{
unsafeImpersonate.Dispose();
}
}
}
catch
{
throw;
}
}
[SecuritySafeCritical]
internal Type GetCompiledType(string normalizedVirtualPath)
{
try
{
IDisposable unsafeImpersonate = null;
try
{
try
{
}
finally
{
unsafeImpersonate = HostingEnvironmentWrapper.UnsafeImpersonate();
}
return BuildManager.GetCompiledType(normalizedVirtualPath);
}
finally
{
if (null != unsafeImpersonate)
{
unsafeImpersonate.Dispose();
}
}
}
catch
{
throw;
}
}
static Uri[] FilterBaseAddressList(Uri[] baseAddresses, Uri[] prefixFilters)
{
// Precondition assumption:
// filterAddresses only contains one Uri per scheme.
// Enforced by throwing exception when duplicates found.
List results = new List();
Dictionary schemeMappings = new Dictionary();
foreach (Uri filterUri in prefixFilters)
{
if (!schemeMappings.ContainsKey(filterUri.Scheme))
{
schemeMappings.Add(filterUri.Scheme, filterUri);
}
else
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString(SR.BaseAddressDuplicateScheme, filterUri.Scheme)));
}
}
foreach (Uri baseUri in baseAddresses)
{
string scheme = baseUri.Scheme;
if (schemeMappings.ContainsKey(scheme))
{
Uri filterUri = schemeMappings[scheme];
if ((baseUri.Port == filterUri.Port) &&
(string.Compare(baseUri.Host, filterUri.Host, StringComparison.OrdinalIgnoreCase) == 0))
{
results.Add(baseUri);
}
}
else
{
results.Add(baseUri);
}
}
return results.ToArray();
}
ServiceHostBase CreateService(string normalizedVirtualPath)
{
string virtualPath;
string factoryType = "";
string constructorString;
ServiceHostBase service = null;
ServiceHostFactoryBase factory = null;
string[] compiledStrings = null;
string compiledString = "";
if (TD.CompilationStartIsEnabled())
{
TD.CompilationStart();
}
// 0. Check AspNet Routing vs CBA
// check whether there is a conflict between CBA and AspNetRouting
// if there is a conflict, using AspNet routing policy to decide which service should be activated
// we treat CBA as file. RouteExistingFiles is false means Routing should not override File
// Todo: when there is a conflict between file/CBA adn route and routing policy was changed dynamically, we still use the old service CSD105890
if (isAspNetRoutedRequest && isConfigurationBased)
{
if (!RouteTable.Routes.RouteExistingFiles)
{
ServiceRouteHandler.MarkARouteAsInactive(normalizedVirtualPath);
isAspNetRoutedRequest = false;
}
else
{
isConfigurationBased = false;
}
}
// 1. Compile the service
// The expected format is:
// ||
// The first two cannot be empty.
if (!isAspNetRoutedRequest)
{
compiledString = GetCompiledCustomString(normalizedVirtualPath);
if (string.IsNullOrEmpty(compiledString))
{
// Assume it is a workflow service - optimize by not calling BuildManager.GetCompiledType
// but we need to convert the filename to case sensitive one from the physical file
// e.g., incoming request with ~/file.xamlx but physical file has name FiLe.Xamlx
// we should convert the virtualPath to ~/FiLe.Xamlx, so that mex can show right case
// we cannot make directory path case sensitive as we cannot get this path info with right case
string fileName = HostingEnvironmentWrapper.GetServiceFile(normalizedVirtualPath).Name;
string pathSegment = normalizedVirtualPath.Substring(0, normalizedVirtualPath.LastIndexOf(PathSeparator) + 1);
normalizedVirtualPath = String.Format(CultureInfo.CurrentCulture, "{0}{1}", pathSegment, fileName);
constructorString = virtualPath = normalizedVirtualPath;
factory = CreateWorkflowServiceHostFactory(normalizedVirtualPath);
}
else
{
TryDebugPrint("HostingManager.CreateService() BuildManager.GetCompiledCustomString() returned compiledString: " + compiledString);
compiledStrings = compiledString.Split(ServiceParserDelimiter.ToCharArray());
if (compiledStrings.Length < 3)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR2.Hosting_CompilationResultInvalid(normalizedVirtualPath)));
}
virtualPath = compiledStrings[0];
factoryType = compiledStrings[1];
constructorString = compiledStrings[2];
}
}
else
{
ServiceDeploymentInfo serviceInfo = ServiceRouteHandler.GetServiceInfo(normalizedVirtualPath);
// use the registered virtualpath to ensure correct case in asp.net route
virtualPath = serviceInfo.VirtualPath;
constructorString = serviceInfo.ServiceType;
factory = serviceInfo.ServiceHostFactory;
}
// We get the virtual path from compiled string so that it will have the correct case.
// normalizedVirtualPath should be application relative e.g., ~/service.svc
// absolute path start with / and application name, e.g., /appName/service.svc
normalizedVirtualPath = virtualPath;
// convert relative virtualpath to app absolute one for consistency, since we gave an absolute path in compiledcustomstring previously
// xamlx, CBA, and AspNet routing use relative virtualpath, while configuration/administration needs an absolute one
virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
// 2. Add the base addresses
Uri[] baseAddresses = HostedTransportConfigurationManager.GetBaseAddresses(virtualPath);
Uri[] prefixFilters = ServiceHostingEnvironment.PrefixFilters;
if (!this.multipleSiteBindingsEnabled && prefixFilters != null && prefixFilters.Length > 0)
{
baseAddresses = FilterBaseAddressList(baseAddresses, prefixFilters);
}
fullVirtualPath = virtualPath;
if (fullVirtualPath.Length == 0)
{
fullVirtualPath = "/";
}
// Get the current virtual path (full path except for the .svc file name).
currentVirtualPath = virtualPath.Substring(0, virtualPath.LastIndexOf(PathSeparator));
if (currentVirtualPath.Length == 0)
{
currentVirtualPath = "/";
xamlFileBaseLocation = RootVirtualPath;
}
else
{
// add trailing slash to support ../a.xamlx in the case .xamlx file is wrapped with .svc
// otherwise when combining ~/sub with ../a.xamlx, VirtualPathUtility will return wrong value ~/a.xamlx
xamlFileBaseLocation = VirtualPathUtility.AppendTrailingSlash(currentVirtualPath);
}
if (isConfigurationBased)
{
xamlFileBaseLocation = RootVirtualPath;
if (TD.CBAMatchFoundIsEnabled())
{
TD.CBAMatchFound(normalizedVirtualPath);
}
}
if (TD.ServiceHostFactoryCreationStartIsEnabled())
{
TD.ServiceHostFactoryCreationStart();
}
// 3. Create service
if (factory == null)
{
if (string.IsNullOrEmpty(factoryType))
{
Fx.Assert(!string.IsNullOrEmpty(compiledString), "The compiled string can't be null or empty");
factory = new ServiceHostFactory();
}
else
{
Type compiledType = Type.GetType(factoryType);
//check the type from the assemblies in current domain
//since compiledcustomstring does not contain fullname for configured virtual path
if (compiledType == null && isConfigurationBased)
{
EnsureAllReferencedAssemblyLoaded();
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
compiledType = assemblies[i].GetType(factoryType, false);
if (compiledType != null)
{
break;
}
}
}
if (compiledType == null)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR2.Hosting_FactoryTypeNotResolved(factoryType)));
}
if (!typeof(ServiceHostFactoryBase).IsAssignableFrom(compiledType))
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR2.Hosting_IServiceHostNotImplemented(factoryType)));
}
ConstructorInfo ctor = compiledType.GetConstructor(new Type[] { });
if (ctor == null)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR2.Hosting_NoDefaultCtor(factoryType)));
}
factory = (ServiceHostFactoryBase)ctor.Invoke(new object[] { });
}
}
if (TD.ServiceHostFactoryCreationStopIsEnabled())
{
TD.ServiceHostFactoryCreationStop();
}
// Push assembly context into ServiceHostFactory
// it is OK for us to ignore CBA case here since no referenced assembly in compiledString for CBA
// but do not do it for AspNet routing, since there is no compiledString
if (factory is ServiceHostFactory && !isConfigurationBased && !isAspNetRoutedRequest)
{
Fx.Assert(!string.IsNullOrEmpty(compiledString), "The compiled string can't be null or empty");
for (int index = 3; index < compiledStrings.Length; ++index)
{
((ServiceHostFactory)factory).AddAssemblyReference(compiledStrings[index]);
}
}
if (TD.CreateServiceHostStartIsEnabled())
{
TD.CreateServiceHostStart();
}
service = factory.CreateServiceHost(constructorString, baseAddresses);
if (TD.CreateServiceHostStopIsEnabled())
{
TD.CreateServiceHostStop();
}
if (service == null)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR2.Hosting_ServiceHostBaseIsNull(constructorString)));
}
// 4. Create VirtualPathExtension for ServiceHostBase
service.Extensions.Add(new VirtualPathExtension(normalizedVirtualPath, ServiceHostingEnvironment.ApplicationVirtualPath, ServiceHostingEnvironment.SiteName));
if (service.Description != null)
{
service.Description.Behaviors.Add(new ApplyHostConfigurationBehavior());
if (this.multipleSiteBindingsEnabled &&
service.Description.Behaviors.Find() == null)
{
service.Description.Behaviors.Add(new UseRequestHeadersForMetadataAddressBehavior());
}
}
if (TD.CompilationStopIsEnabled())
{
TD.CompilationStop();
}
return service;
}
//NoInlining - we don't want to load Workflow dlls while activating 3.0 services
[MethodImpl(MethodImplOptions.NoInlining)]
ServiceHostFactoryBase CreateWorkflowServiceHostFactory(string path)
{
return PathCache.EnsurePathInfo(path).ServiceModelActivationHandler.GetFactory();
}
void FailActivationIfRecyling(string normalizedVirtualPath)
{
if (IsRecycling)
{
InvalidOperationException exception = new InvalidOperationException(
SR2.Hosting_EnvironmentShuttingDown(normalizedVirtualPath,
HostingEnvironmentWrapper.ApplicationVirtualPath));
throw FxTrace.Exception.AsError(new ServiceActivationException(exception.Message, exception));
}
}
public void Stop(bool immediate)
{
if (!immediate)
{
// Try to wait for all requests to be done, then close all the ServiceHosts.
ActionItem.Schedule(new Action