//---------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- namespace System.ServiceModel { using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.Diagnostics; using System.Security; using System.ServiceModel.Activation; using System.ServiceModel.Activation.Diagnostics; using System.ServiceModel.Configuration; 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 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(); // Double-checked locking pattern requires volatile for read/write synchronization static volatile HostingManager hostingManager; static bool isHosted; static bool isSimpleApplicationHost; static Int64 requestCount; static bool canGetHtmlErrorMessage = true; static string siteName; static string applicationVirtualPath; static string serviceActivationElementPath; static int insufficientMemoryLogCount; static DateTime insufficientMemoryLogStartInterval = DateTime.MinValue; static readonly TimeSpan InsufficientMemoryLogIntervalDuration = TimeSpan.FromHours(1); 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,"; const int MaxInsufficientMemoryLogCount = 10; [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, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost, (uint)System.Runtime.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)); } EventTraceActivity eventTraceActivity = null; if (Fx.Trace.IsEtwProviderEnabled) { eventTraceActivity = EventTraceActivity.GetFromThreadOrCreate(); } EnsureInitialized(); virtualPath = NormalizeVirtualPath(virtualPath); EnsureServiceAvailableFast(virtualPath, eventTraceActivity); } internal static void EnsureServiceAvailableFast(string relativeVirtualPath, EventTraceActivity eventTraceActivity) { try { hostingManager.EnsureServiceAvailable(relativeVirtualPath, eventTraceActivity); } catch (ServiceActivationException exception) { LogServiceActivationException(exception, eventTraceActivity); 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, EventTraceActivity eventTraceActivity) { if (exception.InnerException is HttpException) { string messageAsString = SafeTryGetHtmlErrorMessage((HttpException)exception.InnerException); if (string.IsNullOrEmpty(messageAsString)) { messageAsString = exception.Message; } DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost, (uint)System.Runtime.Diagnostics.EventLogEventId.WebHostHttpError, true, TraceUtility.CreateSourceString(hostingManager), messageAsString, exception.ToString()); } else if (exception.InnerException is InsufficientMemoryException) { //Fix for CSDMain #113776 //This logic prevents InsufficientMemoryExceptions from flooding the event log by logging at most a fixed number ('MaxInsufficientMemoryLogCount') of these exceptions //per fixed time interval ('InsufficientMemoryLogIntervalDuration'). If this limit is hit, no exceptions of this type are logged for a full time interval. DateTime now = DateTime.UtcNow; bool shouldLog = false; bool reachedMax = false; if (now - insufficientMemoryLogStartInterval > InsufficientMemoryLogIntervalDuration || insufficientMemoryLogCount < MaxInsufficientMemoryLogCount) { //This lock ensures that the log count is only reset once, and that no race conditions exist for the log count and insufficientMemoryLogStartInterval //These 2 static variables are only modified within this lock, and only read in this lock and in its containing if statement. lock (ThisLock) { if (now - insufficientMemoryLogStartInterval > InsufficientMemoryLogIntervalDuration) { insufficientMemoryLogCount = 0; insufficientMemoryLogStartInterval = now; } if (insufficientMemoryLogCount < MaxInsufficientMemoryLogCount) { insufficientMemoryLogCount++; shouldLog = true; } if (insufficientMemoryLogCount == MaxInsufficientMemoryLogCount) { //We set the 'insufficientMemoryLogStartInterval' to DateTime.Now so that no InsufficientMemoryExceptions are logged for a full time interval of duration "InsufficientMemoryLogIntervalDuration" insufficientMemoryLogStartInterval = now; reachedMax = true; } } } if (shouldLog) { //The lock above ensures that this line is hit no more than MaxInsufficientMemoryLogCount per time interval. DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost, (uint)System.Runtime.Diagnostics.EventLogEventId.WebHostFailedToProcessRequest, true, TraceUtility.CreateSourceString(hostingManager), exception.ToString()); //The lock above ensures that this if statement is entered exactly once if >= MaxInsufficientMemoryLogCount InsufficientMemoryExceptions are thrown in one time interval. if (reachedMax) { DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Warning, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost, (uint)System.Runtime.Diagnostics.EventLogEventId.WebHostNotLoggingInsufficientMemoryExceptionsOnActivationForNextTimeInterval, true, SR2.Hosting_NotLoggingInsufficientMemoryExceptionsOnActivationForNextTimeInterval(InsufficientMemoryLogIntervalDuration.ToString()), TraceUtility.CreateSourceString(hostingManager)); } } } else { DiagnosticUtility.UnsafeEventLog.UnsafeLogEvent(TraceEventType.Error, (ushort)System.Runtime.Diagnostics.EventLogCategory.WebHost, (uint)System.Runtime.Diagnostics.EventLogEventId.WebHostFailedToProcessRequest, true, TraceUtility.CreateSourceString(hostingManager), exception.ToString()); } if (TD2.ServiceExceptionIsEnabled()) { TD2.ServiceException(eventTraceActivity, 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 DiagnosticUtility.TraceHandledException(e, TraceEventType.Warning); } } return null; } [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")] internal static void IncrementRequestCount(ref EventTraceActivity eventTraceActivity, string requestUrl) { Interlocked.Increment(ref requestCount); if (Fx.Trace.IsEtwProviderEnabled) { // aspnet provider might provide a guid. We will transfer this over // We use a new id since the thread that comes might be reusing it. Guid relatedId = EventTraceActivity.GetActivityIdFromThread(); eventTraceActivity = new EventTraceActivity(); if (TD.WebHostRequestStartIsEnabled()) { TD.WebHostRequestStart(eventTraceActivity, AppDomain.CurrentDomain.FriendlyName, requestUrl, relatedId); } } } internal static void DecrementRequestCount(EventTraceActivity eventTraceActivity) { 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(eventTraceActivity); } } 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() { AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet(); BuildManager.GetReferencedAssemblies(); } static void OnEnsureInitialized(object state) { EnsureInitialized(); } [EditorBrowsable(EditorBrowsableState.Never)] public 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(); isSimpleApplicationHost = GetIsSimpleApplicationHost(); HostedAspNetEnvironment.Enable(); isHosted = true; hostingManager = tempHostingManager; } } [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) { AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet(); 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 enum ServiceType { Unknown = 0, WCF, Workflow } class HostingManager : IRegisteredObject { readonly CollectibleLRUCache directory; readonly ExtensionHelper extensions; bool aspNetCompatibilityEnabled; bool multipleSiteBindingsEnabled; bool isUnregistered; bool isRecycling; bool isStopStarted; static bool canDebugPrint = true; object activationLock = 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; bool closeIdleServicesAtLowMemory; [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 CollectibleLRUCache(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; this.closeIdleServicesAtLowMemory = section.CloseIdleServicesAtLowMemory; 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; } } object ActivationLock { get { return this.activationLock; } } 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, EventTraceActivity eventTraceActivity) { TryDebugPrint("HostingManager.EnsureServiceAvailable(" + normalizedVirtualPath + ")"); ServiceActivationInfo activationInfo = null; // 1. Try finding the service without a lock. activationInfo = (ServiceActivationInfo)this.directory[normalizedVirtualPath]; if (activationInfo != null && activationInfo.Initialized) { return; } if (TD.ServiceActivationStartIsEnabled()) { TD.ServiceActivationStart(eventTraceActivity); } // 2. Special casing two cases (Routing and Config-based activation) isAspNetRoutedRequest = ServiceRouteHandler.IsActiveAspNetRoute(normalizedVirtualPath); isConfigurationBased = IsConfigurationBasedServiceVirtualPath(normalizedVirtualPath); // 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)))); } // 3. Use global lock to create ServiceActivationInfo if necessary. using (directory.CreateWriterLockScope()) { FailActivationIfRecyling(normalizedVirtualPath); // We need to call RegisterObject inside the WriterLockScope because it would ---- with UnregisterObject if (!isRegistered) { RegisterObject(); isRegistered = true; } activationInfo = (ServiceActivationInfo)this.directory.UnsafeGet(normalizedVirtualPath); if (activationInfo != null) { if (activationInfo.Initialized) { return; } if (activationInfo.LastException != null) { // Remove the last failed item from the cache directory.UnsafeRemove(activationInfo); activationInfo = null; } } if (activationInfo == null) { activationInfo = new ServiceActivationInfo(hostingManager, normalizedVirtualPath); directory.UnsafeAdd(activationInfo); } } // 4. Use local lock to activate the service. lock (activationInfo) { if (activationInfo.Initialized) { return; } if (activationInfo.LastException != null) { // The previous activation for the same service has failed while the current request // is being processed. There is no need to re-activate the service. throw FxTrace.Exception.AsError(activationInfo.LastException); } Exception lastException = null; try { FailActivationIfRecyling(normalizedVirtualPath); CheckMemoryCloseIdleServices(eventTraceActivity); ActivateService(activationInfo, eventTraceActivity); FailActivationIfRecyling(normalizedVirtualPath); 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 = activationInfo.Value 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(eventTraceActivity, serviceName, hostReference); } activationInfo.SetInitialized(); } catch (HttpCompileException ex) { lastException = new ServiceActivationException(SR2.Hosting_ServiceCannotBeActivated(VirtualPathUtility.ToAbsolute(normalizedVirtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath), ex.Message), ex); throw FxTrace.Exception.AsError(lastException); } catch (ServiceActivationException ex) { lastException = ex; throw; } catch (Exception ex) { // If it is a fatal exception, don't wrap it. if (Fx.IsFatal(ex)) { lastException = ex; throw; } lastException = new ServiceActivationException(SR2.Hosting_ServiceCannotBeActivated(VirtualPathUtility.ToAbsolute(normalizedVirtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath), ex.Message), ex); throw FxTrace.Exception.AsError(lastException); } finally { currentVirtualPath = null; fullVirtualPath = null; xamlFileBaseLocation = null; if (lastException != null) { activationInfo.SetLastException(lastException); } } } FailActivationIfRecyling(normalizedVirtualPath); if (TD.ServiceActivationStopIsEnabled()) { TD.ServiceActivationStop(eventTraceActivity); } } void CheckMemoryCloseIdleServices(EventTraceActivity eventTraceActivity) { lock (ActivationLock) { bool shouldWaitForCollectComplete = false; ulong availableMemoryBytes; if (!CheckMemoryGates(out availableMemoryBytes)) { using (directory.CreateWriterLockScope()) { int totalCount = directory.Count; if (!directory.UnsafeBeginBatchCollect()) { throw FxTrace.Exception.AsError(new InsufficientMemoryException( System.ServiceModel.Activation.SR.Hosting_MemoryGatesCheckFailed(availableMemoryBytes, this.minFreeMemoryPercentageToActivateService))); } if (directory.Count < totalCount) { if (TD.IdleServicesClosedIsEnabled()) { TD.IdleServicesClosed(eventTraceActivity, totalCount - directory.Count, totalCount); } shouldWaitForCollectComplete = true; } } } // Recycling needs to happen outside of the WriterLockScope if (shouldWaitForCollectComplete) { directory.EndBatchCollect(); } } } [Fx.Tag.SecurityNote(Critical = "Accesses minFreeMemoryPercentageToActivateService, calls Check.", Safe = "No input / output, safe operation if called with administrator-provided value.")] [SecuritySafeCritical] bool CheckMemoryGates(out ulong availableMemoryBytes) { return ServiceMemoryGates.Check(this.minFreeMemoryPercentageToActivateService, !this.closeIdleServicesAtLowMemory, out availableMemoryBytes); } void ActivateService(ServiceActivationInfo serviceActivationInfo, EventTraceActivity eventTraceActivity) { string normalizedVirtualPath = serviceActivationInfo.GetKey(); ServiceHostBase service = CreateService(normalizedVirtualPath, eventTraceActivity); FailActivationIfRecyling(normalizedVirtualPath); serviceActivationInfo.SetService(service, this.closeIdleServicesAtLowMemory); try { service.Open(); } finally { if (service.State != CommunicationState.Opened) { // Abort the service to clear possible cached information. service.Abort(); } } if (TD.AspNetRoutingServiceIsEnabled() && isAspNetRoutedRequest) { TD.AspNetRoutingService(eventTraceActivity, normalizedVirtualPath); } } // 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) { AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet(); 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) { AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet(); 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, EventTraceActivity eventTraceActivity) { string virtualPath; string factoryType = ""; string constructorString; ServiceHostBase service = null; ServiceHostFactoryBase factory = null; string[] compiledStrings = null; string compiledString = ""; if (TD.CompilationStartIsEnabled()) { TD.CompilationStart(eventTraceActivity); } // 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(eventTraceActivity, normalizedVirtualPath); } } if (TD.ServiceHostFactoryCreationStartIsEnabled()) { TD.ServiceHostFactoryCreationStart(eventTraceActivity); } // 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(eventTraceActivity); } // 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(eventTraceActivity); } service = factory.CreateServiceHost(constructorString, baseAddresses); if (TD.CreateServiceHostStopIsEnabled()) { TD.CreateServiceHostStop(eventTraceActivity); } 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(eventTraceActivity); } 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(WaitAndCloseCallback), this); } else { // Will execute here only if HostingEnvironment.UnregisterObject hasn't been called. Abort(); } } [Conditional("DEBUG")] static void TryDebugPrint(string message) { if (canDebugPrint) { try { Debug.Print(message); } catch (SecurityException e) { canDebugPrint = false; // not re-throwing on purpose DiagnosticUtility.TraceHandledException(e, TraceEventType.Warning); } } } void OnServiceClosed(ServiceActivationInfo serviceActivationInfo) { if (!isRecycling) { using (this.directory.CreateWriterLockScope()) { this.directory.UnsafeRemove(serviceActivationInfo); // At the time when we just removed all the service, we will unregister // from HostingEnvironement. UnregisterObject(); } } } void OnServiceFaulted(ServiceHostBase host) { host.Abort(); } void OnServiceBusyCountIncremented(ServiceActivationInfo serviceActivationInfo) { this.directory.Touch(serviceActivationInfo.GetKey()); } internal void NotifyAllRequestDone() { if (isStopStarted) { allRequestDoneInStop.Set(); } } void Abort() { allRequestDoneInStop.Set(); directory.Abort(); using (directory.CreateWriterLockScope()) { // We need to set isRecycling inside lock because we want to make sure no // new request will be handed once we start to shut down. isRecycling = true; if (UnregisterObject()) { return; } } } void WaitAndCloseCallback(object obj) { isStopStarted = true; if (ServiceHostingEnvironment.requestCount != 0) { allRequestDoneInStop.WaitOne(); } using (directory.CreateWriterLockScope()) { // We need to set isRecycling inside lock because we want to make sure no // new request will be handed once we start to shut down. isRecycling = true; directory.UnsafeBeginBatchCollect(true); } directory.EndBatchCollect(); using (directory.CreateWriterLockScope()) { UnregisterObject(); } } internal static void LogServiceCloseError(string virtualPath, Exception exception, object source) { if (DiagnosticUtility.ShouldTraceError) { TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.WebHostServiceCloseFailed, SR2.TraceCodeWebHostServiceCloseFailed, new StringTraceRecord("VirtualPath", VirtualPathUtility.ToAbsolute(virtualPath, HostingEnvironmentWrapper.ApplicationVirtualPath)), source, exception); } } [Fx.Tag.SecurityNote(Critical = "Uses HostingEnvironmentWrapper.UnsafeRegisterObject which is critical.", Safe = "Does not allow the caller to control the variable -- only registers 'this'.")] [SecuritySafeCritical] void RegisterObject() { HostingEnvironmentWrapper.UnsafeRegisterObject(this); } // Note : this method should only be called under lock of the global lock. [Fx.Tag.SecurityNote(Critical = "Uses HostingEnvironmentWrapper.UnsafeRegisterObject which is critical.", Safe = "Does not allow the caller to control the variable -- only registers 'this'.")] [SecuritySafeCritical] bool UnregisterObject() { if (directory.Count == 0) { if (!isUnregistered) { isUnregistered = true; HostingEnvironmentWrapper.UnsafeUnregisterObject(this); } return true; } return false; } class ExtensionHelper { readonly IDictionary buildProviders; [Fx.Tag.SecurityNote(Critical = "Loads config through an elevation and stores results.", Safe = "Stores results in BuildProviderInfo instances which restrict access to the BuildProvider config object.")] [SecuritySafeCritical] public ExtensionHelper() { AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet(); buildProviders = new Dictionary(8, StringComparer.OrdinalIgnoreCase); CompilationSection compilationSection = (CompilationSection)HostedAspNetEnvironment.UnsafeGetSectionFromWebConfigurationManager("system.web/compilation", null); foreach (System.Web.Configuration.BuildProvider buildProvider in compilationSection.BuildProviders) { buildProviders.Add(buildProvider.Extension, new BuildProviderInfo(buildProvider)); } } [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")] public ServiceType GetServiceType(string extension) { ServiceType serviceType = ServiceType.Unknown; BuildProviderInfo info; if (buildProviders.TryGetValue(extension, out info)) { if (info.IsSupported) { serviceType = ServiceType.WCF; } else if (info.IsXamlBuildProvider) { serviceType = ServiceType.Workflow; } } return serviceType; } } class ServiceActivationInfo : CollectibleLRUCache.CollectibleNode { HostingManager manager; string virtualPath; EventHandler serviceClosedHandler; EventHandler serviceFaultedHandler; bool initialized; Exception lastException; public ServiceActivationInfo(HostingManager manager, string virtualPath) { this.manager = manager; this.virtualPath = virtualPath; } public bool Initialized { get { return this.initialized; } } public Exception LastException { get { return this.lastException; } } public void SetLastException(Exception lastException) { Fx.Assert(!this.initialized, "The ServiceActivationInfo should not be in the initialized state"); Abort(); this.lastException = lastException; } public void SetInitialized() { this.initialized = true; } public override string GetKey() { return this.virtualPath; } public void SetService(ServiceHostBase service, bool shouldTrackBusyCountIncrement) { this.Value = service; this.serviceClosedHandler = new EventHandler(OnServiceClosed); this.serviceFaultedHandler = new EventHandler(OnServiceFaulted); service.Closed += this.serviceClosedHandler; service.Faulted += this.serviceFaultedHandler; if (shouldTrackBusyCountIncrement) { // If recycling is enabled, we record the busy count events for best LRU statistics service.BusyCountIncremented += OnServiceBusyCountIncremented; } } public override bool CanClose() { if (this.lastException != null) { return true; } // We can remove the entry from the cache only if it is initialized. if (!this.initialized) { return false; } if (this.Value != null) { return (this.Value.BusyCount == 0); } return true; } // The caller of this method needs to remove the entry from the cache explicitly. public override IAsyncResult BeginClose(AsyncCallback callback, object state) { ServiceHostBase service = this.Value; if (service == null) return null; UnregisterEvents(service); try { return service.BeginClose(callback, state); } catch (Exception exception) { // If BeginClose throw an exception, abort should already have been called. if (!Fx.IsFatal(exception)) { HostingManager.LogServiceCloseError(this.virtualPath, exception, this); } if (!(exception is CommunicationException)) { throw; } } return new CompletedAsyncResult(callback, state); } public override void EndClose(IAsyncResult result) { if (result is CompletedAsyncResult) { CompletedAsyncResult.End(result); return; } ServiceHostBase service = this.Value; if (service != null) { try { service.EndClose(result); } catch (Exception exception) { //If EndClose throw an exception, abort should already have been called. if (Fx.IsFatal(exception)) { throw; } // Ignore exceptions occurred when exception happened HostingManager.LogServiceCloseError(this.virtualPath, exception, this); } this.Value = null; } } public void OnServiceBusyCountIncremented(object sender, EventArgs args) { manager.OnServiceBusyCountIncremented(this); } public override void Abort() { ServiceHostBase service = this.Value; if (service != null) { UnregisterEvents(service); service.Abort(); this.Value = null; } } void OnServiceClosed(object sender, EventArgs args) { if ((ServiceHostBase)sender == this.Value) { manager.OnServiceClosed(this); } } void OnServiceFaulted(object sender, EventArgs args) { manager.OnServiceFaulted((ServiceHostBase)sender); } void UnregisterEvents(ServiceHostBase service) { if (this.serviceClosedHandler != null) { service.Closed -= this.serviceClosedHandler; this.serviceClosedHandler = null; } if (this.serviceFaultedHandler != null) { service.Faulted -= this.serviceFaultedHandler; this.serviceFaultedHandler = null; } } } } class BuildProviderInfo { [Fx.Tag.SecurityNote(Critical = "Stores the result of an elevation.")] [SecurityCritical] System.Web.Configuration.BuildProvider buildProvider; // Double-checked locking pattern requires volatile for read/write synchronization volatile bool initialized; bool isSupported; bool isXamlBuildProvider; object thisLock = new object(); [Fx.Tag.SecurityNote(Critical = "Stores the result of an elevation.", Safe = "Stores it in a Critical field.")] [SecuritySafeCritical] public BuildProviderInfo(System.Web.Configuration.BuildProvider buildProvider) { this.buildProvider = buildProvider; } string BuildProviderType { [Fx.Tag.SecurityNote(Critical = "Accesses the SecurityCritical buildProvider field.", Safe = "Returns the Type property, which is allowed; doesn't leak the BuildProvider instance.")] [SecuritySafeCritical] get { return buildProvider.Type; } } [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")] public bool IsSupported { get { EnsureInitialized(); return this.isSupported; } } [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")] public bool IsXamlBuildProvider { get { EnsureInitialized(); return this.isXamlBuildProvider; } } [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")] void EnsureInitialized() { if (initialized) { return; } lock (thisLock) { if (initialized) { return; } Type type = Type.GetType(BuildProviderType, false); if (type == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { type = assemblies[i].GetType(BuildProviderType, false); if (type != null) { break; } } } if (type != null) { object[] attributes = ServiceReflector.GetCustomAttributes(type, typeof(ServiceActivationBuildProviderAttribute), true); if (attributes.Length > 0) { this.isSupported = true; } else { //to accomodate for subclasses of XamlBuildProvider if (typeof(System.Xaml.Hosting.XamlBuildProvider).IsAssignableFrom(type)) { this.isXamlBuildProvider = true; } } } ClearBuildProvider(); initialized = true; } } [Fx.Tag.SecurityNote(Critical = "Accesses the SecurityCritical buildProvider field. Can be called outside user context.", Safe = "Just clears it, doesn't leak anything.")] [SecuritySafeCritical] void ClearBuildProvider() { this.buildProvider = null; } } static class PathCache { static Hashtable pathCache = new Hashtable(StringComparer.OrdinalIgnoreCase); static object writeLock = new object(); public static PathInfo EnsurePathInfo(string path) { PathInfo pathInfo = (PathInfo)pathCache[path]; if (pathInfo != null) { return pathInfo; } lock (writeLock) { pathInfo = (PathInfo)pathCache[path]; if (pathInfo != null) { return pathInfo; } if (HostingEnvironmentWrapper.ServiceFileExists(path)) { pathInfo = new PathInfo(path); pathCache.Add(path, pathInfo); return pathInfo; } else { throw FxTrace.Exception.AsError(new EndpointNotFoundException(SR2.Hosting_ServiceNotExist(path))); } } } } class PathInfo { string path; // Double-checked locking pattern requires volatile for read/write synchronization volatile PathType type; object writeLock; Type hostedXamlType; Type serviceModelActivationHandlerType; IServiceModelActivationHandler serviceModelActivationHandler; public PathInfo(string path) { this.type = PathType.Unknown; this.path = path; this.writeLock = new object(); } public IServiceModelActivationHandler ServiceModelActivationHandler { get { if (this.serviceModelActivationHandler == null) { if (IsWorkflowService()) { this.serviceModelActivationHandler = CreateServiceModelActivationHandler(serviceModelActivationHandlerType) as IServiceModelActivationHandler; } else { //The control can come here when the hosted file is a valid XAML (service OR otherwise) but is configured with //a handler that does NOT implement IServiceModelActivationHandler and aspnetCompat=true throw FxTrace.Exception.AsError( new EndpointNotFoundException(SR2.Hosting_InvalidHandlerForWorkflowService( this.serviceModelActivationHandlerType.FullName, this.hostedXamlType.FullName, this.path))); } } return this.serviceModelActivationHandler; } } static object CreateServiceModelActivationHandler(Type type) { //The handler/factory should have an empty constructor but need not be public return Activator.CreateInstance(type, BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, null, null); } public bool IsWorkflowService() { if (this.type == PathType.Unknown) { //Cache won't be available if it is invoked first time //Use a local "lock" specifically for this url lock (this.writeLock) { if (this.type == PathType.Unknown) { hostedXamlType = hostingManager.GetCompiledType(this.path); if (IsConfiguredWithSMActivationHandler()) { this.type = PathType.WorkflowService; } else { this.type = PathType.NotWorkflowService; } } } } if (this.type == PathType.WorkflowService) { return true; } return false; } [SuppressMessage(FxCop.Category.Security, FxCop.Rule.AptcaMethodsShouldOnlyCallAptcaMethods, Justification = "This method doesn't allow callers to access sensitive information, operations, or resources that can be used in a destructive manner.")] bool IsConfiguredWithSMActivationHandler() { if (XamlHostingConfiguration.TryGetHttpHandlerType(this.path, this.hostedXamlType, out this.serviceModelActivationHandlerType)) { if (typeof(IServiceModelActivationHandler).IsAssignableFrom(this.serviceModelActivationHandlerType)) { return true; } } return false; } enum PathType { Unknown, WorkflowService, NotWorkflowService } } } }