CachedPathData.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / xsp / System / Web / CachedPathData.cs / 1605387 / CachedPathData.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web { 
    using System.Collections; 
    using System.Configuration;
    using System.Configuration.Internal; 
    using System.Globalization;
    using System.Security.Principal;
    using System.Threading;
    using System.Web.Security; 
    using System.Web.SessionState;
    using System.Web.Configuration; 
    using System.Web.Caching; 
    using System.Web.Hosting;
    using System.Web.Util; 
    using System.Web.UI;
    using System.Security.Permissions;

    // Data about a path that is cached across requests 
    class CachedPathData {
        internal const int FInited                  = 0x0001; 
        internal const int FCompletedFirstRequest   = 0x0002; 
        internal const int FExists                  = 0x0004;
        internal const int FOwnsConfigRecord        = 0x0010;   // is this the highest ancestor pointing to the config record? 
        internal const int FClosed                  = 0x0020;   // Has item been closed already?
        internal const int FCloseNeeded             = 0x0040;   // Should we close?
        internal const int FAnonymousAccessChecked  = 0x0100;
        internal const int FAnonymousAccessAllowed  = 0x0200; 

        static CacheItemRemovedCallback s_callback = new CacheItemRemovedCallback(CachedPathData.OnCacheItemRemoved); 
        // initialize the URL metadata cache expiration here, just in case there's an issue with HttpRuntime.HostingInit 
        private static TimeSpan s_urlMetadataSlidingExpiration = HostingEnvironmentSection.DefaultUrlMetadataSlidingExpiration;
        private static bool s_doNotCacheUrlMetadata = false; 
        private static int s_appConfigPathLength = 0;

        #pragma warning disable 0649
        SafeBitVector32         _flags; 
        #pragma warning restore 0649
        string                  _configPath; 
        VirtualPath             _virtualPath; 
        string                  _physicalPath;
        RuntimeConfig           _runtimeConfig; 
        HandlerMappingMemo      _handlerMemo;


        // 
        // Constructor
        // 
        internal CachedPathData(string configPath, VirtualPath virtualPath, string physicalPath, bool exists) { 
            // Guarantee that we return a non-null config record
            // if an error occurs during initialization. 
            _runtimeConfig = RuntimeConfig.GetErrorRuntimeConfig();
            _configPath = configPath;
            _virtualPath = virtualPath;
            _physicalPath = physicalPath; 
            _flags[FExists] = exists;
 
            // VSWhidbey 607683: Config loading for web app has a dependency on CachedPathData. 
            // On the other hand, Config also has a dependency on Uri class which has
            // a new static constructor that calls config, and eventually to CachedPathData again. 
            // We need a dummy reference to Uri class so the static constructor would be involved
            // first to initialize config.
            string dummy = System.Uri.SchemeDelimiter;
 
        }
 
        // 
        // Called by HttpRuntime.HostingInit to initialize UrlMetadataSlidingExpiration
        // 
        static internal void InitializeUrlMetadataSlidingExpiration(HostingEnvironmentSection section) {
            TimeSpan slidingExp = section.UrlMetadataSlidingExpiration;
            if (slidingExp == TimeSpan.Zero) {
                // a value of TimeSpan.Zero means don't cache 
                // this "feature" was added for Bing, because they
                // have scenarios where the same URL is never seen twice 
                s_doNotCacheUrlMetadata = true; 
            }
            else if (slidingExp == TimeSpan.MaxValue) { 
                // a value of TimeSpan.MaxValue means use Cache.NoSlidingExpiration,
                // which is how CachedPathData used to be cached, so this effectively
                // reverts to v2.0 behavior for caching CachedPathData
                s_urlMetadataSlidingExpiration = Cache.NoSlidingExpiration; 
                s_doNotCacheUrlMetadata = false;
            } 
            else { 
                // anything in between means cache with that sliding expiration
                s_urlMetadataSlidingExpiration = slidingExp; 
                s_doNotCacheUrlMetadata = false;
            }
        }
 
        //
        // Get CachedPathData for the machine.config level 
        // 
        static internal CachedPathData GetMachinePathData() {
            return GetConfigPathData(WebConfigurationHost.MachineConfigPath); 
        }

        //
        // Get CachedPathData for the root web.config path 
        //
        static internal CachedPathData GetRootWebPathData() { 
            return GetConfigPathData(WebConfigurationHost.RootWebConfigPath); 
        }
 
        //
        // Get CachedPathData for the application.
        //
        static internal CachedPathData GetApplicationPathData() { 
            if (!HostingEnvironment.IsHosted) {
                return GetRootWebPathData(); 
            } 

            return GetConfigPathData(HostingEnvironment.AppConfigPath); 
        }

        //
        // Get CachedPathData for a virtual path. 
        // The path may be supplied by user code, so check that it is valid.
        // 
        static internal CachedPathData GetVirtualPathData(VirtualPath virtualPath, bool permitPathsOutsideApp) { 
            if (!HostingEnvironment.IsHosted) {
                return GetRootWebPathData(); 
            }

            // Make sure it's not relative
            if (virtualPath != null) { 
                virtualPath.FailIfRelativePath();
            } 
 
            // Check if the path is within the application.
            if (virtualPath == null || !virtualPath.IsWithinAppRoot) { 
                if (permitPathsOutsideApp) {
                    return GetApplicationPathData();
                }
                else { 
                    throw new ArgumentException(SR.GetString(
                        SR.Cross_app_not_allowed, 
                        (virtualPath != null) ? virtualPath.VirtualPathString : "null")); 
                }
            } 

            // Construct a configPath based on the unvalidated virtualPath.
            string configPath = WebConfigurationHost.GetConfigPathFromSiteIDAndVPath(HostingEnvironment.SiteID, virtualPath);
 
            // Pass the virtualPath to GetConfigPathData to validate in the case where the
            // CachedPathData for the unsafeConfigPath is not found. 
            return GetConfigPathData(configPath); 
        }
 
        // Dev10 862204: AppDomain does not restart when the application's web.config is touched 2 minutes after the last request
        static private bool IsCachedPathDataRemovable(string configPath) {
            // have we initialized yet?
            if (s_appConfigPathLength == 0) { 
                // when hosted use AppConfigPath, otherwise use RootWebConfigPath
                s_appConfigPathLength = (HostingEnvironment.IsHosted) ? HostingEnvironment.AppConfigPath.Length : WebConfigurationHost.RootWebConfigPath.Length; 
            } 
            // Only config paths beneath the application config path can be removed from the cache.
            return (configPath.Length > s_appConfigPathLength); 
        }

        // Example of configPath = "machine/webroot/1/fxtest/sub/foo.aspx"
        // The configPath parameter must be lower case. 
        static private CachedPathData GetConfigPathData(string configPath) {
            Debug.Assert(ConfigPathUtility.IsValid(configPath), "ConfigPathUtility.IsValid(configPath)"); 
            Debug.Assert(configPath == configPath.ToLower(CultureInfo.InvariantCulture), "configPath == configPath.ToLower(CultureInfo.InvariantCulture)"); 
            bool exists = false;
            bool isDirectory = false; 
            bool isRemovable = IsCachedPathDataRemovable(configPath);
            // if the sliding expiration is zero, we won't cache it unless it is a configPath for root web.config or above
            if (isRemovable && DoNotCacheUrlMetadata) {
                string pathSiteID = null; 
                VirtualPath virtualFilePath = null;
                string physicalFilePath = null; 
                WebConfigurationHost.GetSiteIDAndVPathFromConfigPath(configPath, out pathSiteID, out virtualFilePath); 
                physicalFilePath = GetPhysicalPath(virtualFilePath);
 
                string parentConfigPath = ConfigPathUtility.GetParent(configPath);
                CachedPathData pathParentData = GetConfigPathData(parentConfigPath);
                if (!String.IsNullOrEmpty(physicalFilePath)) {
                    FileUtil.PhysicalPathStatus(physicalFilePath, false, false, out exists, out isDirectory); 
                }
                CachedPathData pathData = new CachedPathData(configPath, virtualFilePath, physicalFilePath, exists); 
                pathData.Init(pathParentData); 

                return pathData; 
            }

            //
            // First, see if the CachedPathData is in the cache. 
            // we don't use Add for this lookup, as doing so requires
            // creating a CacheDependency, which can be slow as it may hit 
            // the filesystem. 
            //
            string key = CreateKey(configPath); 
            CacheInternal cacheInternal = HttpRuntime.CacheInternal;
            CachedPathData data = (CachedPathData) cacheInternal.Get(key);

            // if found, return the data 
            if (data != null) {
                data.WaitForInit(); 
                return data; 
            }
 
            // WOS


            bool cacheEntryIsNotRemovable = false; 

            // if not found, try to add it 
            string siteID = null; 
            VirtualPath virtualPath = null;
            CachedPathData parentData = null; 
            CacheDependency dependency = null;
            string physicalPath = null;
            string[] fileDependencies = null;
            string[] cacheItemDependencies = null; 

            if (WebConfigurationHost.IsMachineConfigPath(configPath)) { 
                cacheEntryIsNotRemovable = true; 
            }
            else { 
                // Make sure we have the parent data so we can create a dependency on the parent.
                // The parent dependency will ensure that configuration data in the parent
                // will be referenced by a cache hit on the child. (see UtcUpdateUsageRecursive in Cache.cs)
                string parentConfigPath = ConfigPathUtility.GetParent(configPath); 
                parentData = GetConfigPathData(parentConfigPath);
                string parentKey = CreateKey(parentConfigPath); 
                cacheItemDependencies = new string[1] {parentKey}; 

                if (!WebConfigurationHost.IsVirtualPathConfigPath(configPath)) { 
                    // assume hardcoded levels above the path, such as root web.config, exist
                    cacheEntryIsNotRemovable = true;
                }
                else { 
                    cacheEntryIsNotRemovable = !isRemovable;
                    WebConfigurationHost.GetSiteIDAndVPathFromConfigPath(configPath, out siteID, out virtualPath); 
                    physicalPath = GetPhysicalPath(virtualPath); 

                    // Add a dependency on the path itself, if it is a file, 
                    // to handle the case where a file is deleted and replaced
                    // with a directory of the same name.
                    if (!String.IsNullOrEmpty(physicalPath)) {
                        FileUtil.PhysicalPathStatus(physicalPath, false, false, out exists, out isDirectory); 
                        if (exists && !isDirectory) {
                            fileDependencies = new string[1] {physicalPath}; 
                        } 
                    }
                } 

                try {
                    dependency = new CacheDependency(0, fileDependencies, cacheItemDependencies);
                } 
                catch {
                    // CacheDependency ctor could fail because of bogus file path 
                    // and it is ok not to watch those 
                }
            } 

            // Try to add the CachedPathData to the cache.
            CachedPathData    dataAdd = null;
            bool              isDataCreator = false; 
            bool              initCompleted = false;
            CacheItemPriority priority = cacheEntryIsNotRemovable ? CacheItemPriority.NotRemovable : CacheItemPriority.Normal; 
            TimeSpan          slidingExpiration = cacheEntryIsNotRemovable ? Cache.NoSlidingExpiration : UrlMetadataSlidingExpiration; 
            try {
                using (dependency) { 
                    dataAdd = new CachedPathData(configPath, virtualPath, physicalPath, exists);
                    try {
                    }
                    finally { 
                        data = (CachedPathData) cacheInternal.UtcAdd(key, dataAdd, dependency,
                            Cache.NoAbsoluteExpiration, slidingExpiration, 
                            priority, s_callback); 

                        if (data == null) { 
                            isDataCreator = true;
                        }
                    }
                } 

                // If another thread added it first, return the data 
                if (!isDataCreator) { 
                    data.WaitForInit();
                    return data; 
                }

                // This thread is the creator of the CachedPathData, initialize it
                lock (dataAdd) { 
                    try {
                        dataAdd.Init(parentData); 
                        initCompleted = true; 
                    }
                    finally { 
                        // free waiters
                        dataAdd._flags[FInited] = true;

                        // Wake up waiters. 
                        Monitor.PulseAll(dataAdd);
 
                        if (dataAdd._flags[FCloseNeeded]) { 
                            // If we have received a call back to close, then lets
                            // make sure that our config object is cleaned up 
                            dataAdd.Close();
                        }
                    }
                } 
            }
            finally { 
                // All the work in this finally block is for the case where we're the 
                // creator of the CachedPathData.
                if (isDataCreator) { 

                    //

 

 
                    if (!dataAdd._flags[FInited]) { 
                        lock (dataAdd) {
                            // free waiters 
                            dataAdd._flags[FInited] = true;

                            // Wake up waiters.
                            Monitor.PulseAll(dataAdd); 

                            if (dataAdd._flags[FCloseNeeded]) { 
                                // If we have received a call back to close, then lets 
                                // make sure that our config object is cleaned up
                                dataAdd.Close(); 
                            }
                        }
                    }
 
                    //
                    // Even though there is a try/catch handler surrounding the call to Init, 
                    // a ThreadAbortException can still cause the handler to be bypassed. 
                    //
                    // If there is an error, either a thread abort or an error in the config 
                    // file itself, we do want to leave the item cached for a short period
                    // so that we do not revisit the error and potentially reparse the config file
                    // on every request.
                    // 
                    // The reason we simply do not leave the item in the cache forever is that the
                    // problem that caused the configuration exception may be fixed without touching 
                    // the config file in a way that causes a file change notification (for example, an 
                    // acl change in a parent directory, or a change of path mapping in the metabase).
                    // 
                    // NOTE: It is important to reinsert the item into the cache AFTER dropping
                    // the lock on dataAdd, in order to prevent the possibility of deadlock.
                    //
                    Debug.Assert(dataAdd._flags[FInited], "_flags[FInited]"); 
                    if (!initCompleted || (dataAdd.ConfigRecord != null && dataAdd.ConfigRecord.HasInitErrors)) {
                        // 
                        // Create a new dependency object as the old one cannot be reused. 
                        // Do not include a file dependency if initialization could not be completed,
                        // as invoking the file system could lead to further errors during a thread abort. 
                        //
                        if (dependency != null) {
                            if (!initCompleted) {
                                dependency = new CacheDependency(0, null, cacheItemDependencies); 
                            }
                            else { 
                                dependency = new CacheDependency(0, fileDependencies, cacheItemDependencies); 
                            }
                        } 

                        using (dependency) {
                            cacheInternal.UtcInsert(key, dataAdd, dependency,
                                DateTime.UtcNow.AddSeconds(5), Cache.NoSlidingExpiration, 
                                CacheItemPriority.Normal, s_callback);
                        } 
                    } 

                } 
            }

            return dataAdd;
        } 

        // Ensure that the physical path does not look suspicious (MSRC 5556). 
        static private string GetPhysicalPath(VirtualPath virtualPath) { 
            string physicalPath = null;
            try { 
                physicalPath = virtualPath.MapPathInternal(true);
            }
            catch (HttpException e) {
                // 
                // Treat exceptions that are thrown because the path is suspicious
                // as "404 Not Found" exceptions. Implementations of MapPath 
                // will throw HttpException with no error code if the path is 
                // suspicious.
                // 
                if (e.GetHttpCode() == 500) {
                    throw new HttpException(404, String.Empty);
                }
                else { 
                    throw;
                } 
            } 

            // 
            // Throw "404 Not Found" if the path is suspicious and
            // the implementation of MapPath has not already done so.
            //
            FileUtil.CheckSuspiciousPhysicalPath(physicalPath); 

            return physicalPath; 
        } 

        // Remove CachedPathData when the first request for the path results in a 
        // 400 range error. We need to remove all data up the path to account for
        // virtual files.
        // An example of a 400 range error is "path not found".
        static internal void RemoveBadPathData(CachedPathData pathData) { 
            CacheInternal cacheInternal = HttpRuntime.CacheInternal;
 
            string configPath = pathData._configPath; 
            string key = CreateKey(configPath);
            while (pathData != null && !pathData.CompletedFirstRequest && !pathData.Exists) { 

                cacheInternal.Remove(key);

                configPath = ConfigPathUtility.GetParent(configPath); 
                if (configPath == null)
                    break; 
 
                key = CreateKey(configPath);
                pathData = (CachedPathData) cacheInternal.Get(key); 
            }
        }

        // Mark CachedPathData as completed when the first request for the path results in a 
        // status outside the 400 range. We need to mark all data up the path to account for
        // virtual files. 
        static internal void MarkCompleted(CachedPathData pathData) { 
            CacheInternal cacheInternal = HttpRuntime.CacheInternal;
 
            string configPath = pathData._configPath;
            do {
                pathData.CompletedFirstRequest = true;
 
                configPath = ConfigPathUtility.GetParent(configPath);
                if (configPath == null) 
                    break; 

                string key = CreateKey(configPath); 
                pathData = (CachedPathData) cacheInternal.Get(key);
            } while (pathData != null && !pathData.CompletedFirstRequest);
        }
 
        // Close
        // 
        // Close the object.  This does not mean it can not be used anymore, 
        // it just means that the cleanup as been done, so we don't have
        // to worry about closing it anymore 
        //
        void Close() {
            // Only close if we are propertly initialized
            if (_flags[FInited]) { 

                // Only close if we haven't already closed 
                if (_flags.ChangeValue(FClosed, true)) { 

                    // Remove the config record if we own it 
                    // N.B. ConfigRecord.Remove is safe to call more than once.
                    if (_flags[FOwnsConfigRecord]) {
                        ConfigRecord.Remove();
                    } 
                }
            } 
        } 

        // OnCacheItemRemoved 
        //
        // Notification the items has been removed from the cache.  Flag
        // the item to be cleaned up, and then try cleanup
        // 
        static void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason) {
            CachedPathData data = (CachedPathData) value; 
 
            data._flags[FCloseNeeded] = true;
            data.Close(); 
        }

        static string CreateKey(string configPath) {
            Debug.Assert(configPath == configPath.ToLower(CultureInfo.InvariantCulture), "configPath == configPath.ToLower(CultureInfo.InvariantCulture)"); 
            return CacheInternal.PrefixPathData + configPath;
        } 
 
        // Initialize the data
        void Init(CachedPathData parentData) { 
            // Note that _runtimeConfig will be set to the singleton instance of ErrorRuntimeConfig
            // if a ThreadAbortException is thrown during this method.
            Debug.Assert(_runtimeConfig == RuntimeConfig.GetErrorRuntimeConfig(), "_runtimeConfig == RuntimeConfig.GetErrorRuntimeConfig()");
 
            if (!HttpConfigurationSystem.UseHttpConfigurationSystem) {
                // 
                // configRecord may legitimately be null if we are not using the HttpConfigurationSystem. 
                //
                _runtimeConfig = null; 
            }
            else {
                IInternalConfigRecord configRecord = HttpConfigurationSystem.GetUniqueConfigRecord(_configPath);
                Debug.Assert(configRecord != null, "configRecord != null"); 

                if (configRecord.ConfigPath.Length == _configPath.Length) { 
                    // 
                    // The config is unique to this path, so this make this record the owner of the config.
                    // 
                    _flags[FOwnsConfigRecord] = true;
                    _runtimeConfig = new RuntimeConfig(configRecord);
                }
                else { 
                    //
                    // The config record is the same as an ancestor's, so use the parent's RuntimeConfig. 
                    // 
                    Debug.Assert(parentData != null, "parentData != null");
                    _runtimeConfig = parentData._runtimeConfig; 
                }
            }
        }
 
        void WaitForInit() {
            // Wait for the data to be initialized. 
            if (!_flags[FInited]) { 
                lock (this) {
                    if (!_flags[FInited]) { 
                        Monitor.Wait(this);
                    }
                }
            } 
        }
 
        // Ensure that Request.PhysicalPath is valid (canonical, not too long, and contains valid characters). 
        // The work is done by CheckSuspiciousPhysicalPath, but as a perf optimization, we can compare
        // Request.PhysicalPath with the cached path result.  The cached path result is validated before 
        // it is cached.  As long as the cached path result is identical to Request.PhysicalPath, we don't
        // have to call CheckSuspiciousPhysicalPath again.
        internal void ValidatePath(String physicalPath) {
            if (String.IsNullOrEmpty(_physicalPath) && String.IsNullOrEmpty(physicalPath)) { 
                return;
            } 
            if (!String.IsNullOrEmpty(_physicalPath) && !String.IsNullOrEmpty(physicalPath)) { 
                if (_physicalPath.Length == physicalPath.Length) {
                    // if identical, we don't have to call CheckSuspiciousPhysicalPath 
                    if (0 == String.Compare(_physicalPath, 0, physicalPath, 0, physicalPath.Length, StringComparison.OrdinalIgnoreCase)) {
                        return;
                    }
                } 
                else if (_physicalPath.Length - physicalPath.Length == 1) {
                    // if they differ by a trailing slash, we shouldn't call CheckSuspiciousPhysicalPath again 
                    if (_physicalPath[_physicalPath.Length-1] == System.IO.Path.DirectorySeparatorChar 
                        && (0 == String.Compare(_physicalPath, 0, physicalPath, 0, physicalPath.Length, StringComparison.OrdinalIgnoreCase))) {
                        return; 
                    }
                }
                else if (physicalPath.Length - _physicalPath.Length == 1) {
                    // if they differ by a trailing slash, we shouldn't call CheckSuspiciousPhysicalPath again 
                    if (physicalPath[physicalPath.Length-1] == System.IO.Path.DirectorySeparatorChar
                        && (0 == String.Compare(_physicalPath, 0, physicalPath, 0, _physicalPath.Length, StringComparison.OrdinalIgnoreCase))) { 
                        return; 
                    }
                } 
            }

            // If we're here, the paths were different, which normally should not happen.
            Debug.Assert(false, "ValidatePath optimization failed: Request.PhysicalPath=" 
                         + physicalPath + "; _physicalPath=" + _physicalPath);
            FileUtil.CheckSuspiciousPhysicalPath(physicalPath); 
        } 

        internal bool CompletedFirstRequest { 
            get {return _flags[FCompletedFirstRequest];}
            set {
                _flags[FCompletedFirstRequest] = value;
            } 
        }
 
        internal VirtualPath Path { 
            get {return _virtualPath;}
        } 

        internal string PhysicalPath {
            get { return _physicalPath; }
        } 

        internal bool AnonymousAccessChecked { 
            get { return _flags[FAnonymousAccessChecked]; } 
            set { _flags[FAnonymousAccessChecked] = value; }
        } 

        internal bool AnonymousAccessAllowed {
            get { return _flags[FAnonymousAccessAllowed]; }
            set { _flags[FAnonymousAccessAllowed] = value; } 
        }
 
        internal bool Exists { 
            get {return _flags[FExists];}
        } 

        internal HandlerMappingMemo CachedHandler {
            get {return _handlerMemo;}
            set {_handlerMemo = value;} 
        }
 
 
        internal IInternalConfigRecord ConfigRecord {
            get { 
                // _runtimeConfig may be null if we are not using the HttpConfigurationSystem.
                return (_runtimeConfig != null) ? _runtimeConfig.ConfigRecord : null;
            }
        } 

        internal RuntimeConfig RuntimeConfig { 
            get { 
                return _runtimeConfig;
            } 
        }

        // Any time we cache metadata for the URL, we should use this
        // sliding expiration, unless DoNotCacheUrlMetadata is true. 
        // This is currently used by CachedPathData, MapPathBasedVirtualPathProvider,
        // and FileAuthorizationModule 
        internal static TimeSpan UrlMetadataSlidingExpiration { 
            get {
                return s_urlMetadataSlidingExpiration; 
            }
        }

        // if true, do not cache at all. 
        internal static bool DoNotCacheUrlMetadata {
            get { return s_doNotCacheUrlMetadata; } 
        } 
    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web { 
    using System.Collections; 
    using System.Configuration;
    using System.Configuration.Internal; 
    using System.Globalization;
    using System.Security.Principal;
    using System.Threading;
    using System.Web.Security; 
    using System.Web.SessionState;
    using System.Web.Configuration; 
    using System.Web.Caching; 
    using System.Web.Hosting;
    using System.Web.Util; 
    using System.Web.UI;
    using System.Security.Permissions;

    // Data about a path that is cached across requests 
    class CachedPathData {
        internal const int FInited                  = 0x0001; 
        internal const int FCompletedFirstRequest   = 0x0002; 
        internal const int FExists                  = 0x0004;
        internal const int FOwnsConfigRecord        = 0x0010;   // is this the highest ancestor pointing to the config record? 
        internal const int FClosed                  = 0x0020;   // Has item been closed already?
        internal const int FCloseNeeded             = 0x0040;   // Should we close?
        internal const int FAnonymousAccessChecked  = 0x0100;
        internal const int FAnonymousAccessAllowed  = 0x0200; 

        static CacheItemRemovedCallback s_callback = new CacheItemRemovedCallback(CachedPathData.OnCacheItemRemoved); 
        // initialize the URL metadata cache expiration here, just in case there's an issue with HttpRuntime.HostingInit 
        private static TimeSpan s_urlMetadataSlidingExpiration = HostingEnvironmentSection.DefaultUrlMetadataSlidingExpiration;
        private static bool s_doNotCacheUrlMetadata = false; 
        private static int s_appConfigPathLength = 0;

        #pragma warning disable 0649
        SafeBitVector32         _flags; 
        #pragma warning restore 0649
        string                  _configPath; 
        VirtualPath             _virtualPath; 
        string                  _physicalPath;
        RuntimeConfig           _runtimeConfig; 
        HandlerMappingMemo      _handlerMemo;


        // 
        // Constructor
        // 
        internal CachedPathData(string configPath, VirtualPath virtualPath, string physicalPath, bool exists) { 
            // Guarantee that we return a non-null config record
            // if an error occurs during initialization. 
            _runtimeConfig = RuntimeConfig.GetErrorRuntimeConfig();
            _configPath = configPath;
            _virtualPath = virtualPath;
            _physicalPath = physicalPath; 
            _flags[FExists] = exists;
 
            // VSWhidbey 607683: Config loading for web app has a dependency on CachedPathData. 
            // On the other hand, Config also has a dependency on Uri class which has
            // a new static constructor that calls config, and eventually to CachedPathData again. 
            // We need a dummy reference to Uri class so the static constructor would be involved
            // first to initialize config.
            string dummy = System.Uri.SchemeDelimiter;
 
        }
 
        // 
        // Called by HttpRuntime.HostingInit to initialize UrlMetadataSlidingExpiration
        // 
        static internal void InitializeUrlMetadataSlidingExpiration(HostingEnvironmentSection section) {
            TimeSpan slidingExp = section.UrlMetadataSlidingExpiration;
            if (slidingExp == TimeSpan.Zero) {
                // a value of TimeSpan.Zero means don't cache 
                // this "feature" was added for Bing, because they
                // have scenarios where the same URL is never seen twice 
                s_doNotCacheUrlMetadata = true; 
            }
            else if (slidingExp == TimeSpan.MaxValue) { 
                // a value of TimeSpan.MaxValue means use Cache.NoSlidingExpiration,
                // which is how CachedPathData used to be cached, so this effectively
                // reverts to v2.0 behavior for caching CachedPathData
                s_urlMetadataSlidingExpiration = Cache.NoSlidingExpiration; 
                s_doNotCacheUrlMetadata = false;
            } 
            else { 
                // anything in between means cache with that sliding expiration
                s_urlMetadataSlidingExpiration = slidingExp; 
                s_doNotCacheUrlMetadata = false;
            }
        }
 
        //
        // Get CachedPathData for the machine.config level 
        // 
        static internal CachedPathData GetMachinePathData() {
            return GetConfigPathData(WebConfigurationHost.MachineConfigPath); 
        }

        //
        // Get CachedPathData for the root web.config path 
        //
        static internal CachedPathData GetRootWebPathData() { 
            return GetConfigPathData(WebConfigurationHost.RootWebConfigPath); 
        }
 
        //
        // Get CachedPathData for the application.
        //
        static internal CachedPathData GetApplicationPathData() { 
            if (!HostingEnvironment.IsHosted) {
                return GetRootWebPathData(); 
            } 

            return GetConfigPathData(HostingEnvironment.AppConfigPath); 
        }

        //
        // Get CachedPathData for a virtual path. 
        // The path may be supplied by user code, so check that it is valid.
        // 
        static internal CachedPathData GetVirtualPathData(VirtualPath virtualPath, bool permitPathsOutsideApp) { 
            if (!HostingEnvironment.IsHosted) {
                return GetRootWebPathData(); 
            }

            // Make sure it's not relative
            if (virtualPath != null) { 
                virtualPath.FailIfRelativePath();
            } 
 
            // Check if the path is within the application.
            if (virtualPath == null || !virtualPath.IsWithinAppRoot) { 
                if (permitPathsOutsideApp) {
                    return GetApplicationPathData();
                }
                else { 
                    throw new ArgumentException(SR.GetString(
                        SR.Cross_app_not_allowed, 
                        (virtualPath != null) ? virtualPath.VirtualPathString : "null")); 
                }
            } 

            // Construct a configPath based on the unvalidated virtualPath.
            string configPath = WebConfigurationHost.GetConfigPathFromSiteIDAndVPath(HostingEnvironment.SiteID, virtualPath);
 
            // Pass the virtualPath to GetConfigPathData to validate in the case where the
            // CachedPathData for the unsafeConfigPath is not found. 
            return GetConfigPathData(configPath); 
        }
 
        // Dev10 862204: AppDomain does not restart when the application's web.config is touched 2 minutes after the last request
        static private bool IsCachedPathDataRemovable(string configPath) {
            // have we initialized yet?
            if (s_appConfigPathLength == 0) { 
                // when hosted use AppConfigPath, otherwise use RootWebConfigPath
                s_appConfigPathLength = (HostingEnvironment.IsHosted) ? HostingEnvironment.AppConfigPath.Length : WebConfigurationHost.RootWebConfigPath.Length; 
            } 
            // Only config paths beneath the application config path can be removed from the cache.
            return (configPath.Length > s_appConfigPathLength); 
        }

        // Example of configPath = "machine/webroot/1/fxtest/sub/foo.aspx"
        // The configPath parameter must be lower case. 
        static private CachedPathData GetConfigPathData(string configPath) {
            Debug.Assert(ConfigPathUtility.IsValid(configPath), "ConfigPathUtility.IsValid(configPath)"); 
            Debug.Assert(configPath == configPath.ToLower(CultureInfo.InvariantCulture), "configPath == configPath.ToLower(CultureInfo.InvariantCulture)"); 
            bool exists = false;
            bool isDirectory = false; 
            bool isRemovable = IsCachedPathDataRemovable(configPath);
            // if the sliding expiration is zero, we won't cache it unless it is a configPath for root web.config or above
            if (isRemovable && DoNotCacheUrlMetadata) {
                string pathSiteID = null; 
                VirtualPath virtualFilePath = null;
                string physicalFilePath = null; 
                WebConfigurationHost.GetSiteIDAndVPathFromConfigPath(configPath, out pathSiteID, out virtualFilePath); 
                physicalFilePath = GetPhysicalPath(virtualFilePath);
 
                string parentConfigPath = ConfigPathUtility.GetParent(configPath);
                CachedPathData pathParentData = GetConfigPathData(parentConfigPath);
                if (!String.IsNullOrEmpty(physicalFilePath)) {
                    FileUtil.PhysicalPathStatus(physicalFilePath, false, false, out exists, out isDirectory); 
                }
                CachedPathData pathData = new CachedPathData(configPath, virtualFilePath, physicalFilePath, exists); 
                pathData.Init(pathParentData); 

                return pathData; 
            }

            //
            // First, see if the CachedPathData is in the cache. 
            // we don't use Add for this lookup, as doing so requires
            // creating a CacheDependency, which can be slow as it may hit 
            // the filesystem. 
            //
            string key = CreateKey(configPath); 
            CacheInternal cacheInternal = HttpRuntime.CacheInternal;
            CachedPathData data = (CachedPathData) cacheInternal.Get(key);

            // if found, return the data 
            if (data != null) {
                data.WaitForInit(); 
                return data; 
            }
 
            // WOS


            bool cacheEntryIsNotRemovable = false; 

            // if not found, try to add it 
            string siteID = null; 
            VirtualPath virtualPath = null;
            CachedPathData parentData = null; 
            CacheDependency dependency = null;
            string physicalPath = null;
            string[] fileDependencies = null;
            string[] cacheItemDependencies = null; 

            if (WebConfigurationHost.IsMachineConfigPath(configPath)) { 
                cacheEntryIsNotRemovable = true; 
            }
            else { 
                // Make sure we have the parent data so we can create a dependency on the parent.
                // The parent dependency will ensure that configuration data in the parent
                // will be referenced by a cache hit on the child. (see UtcUpdateUsageRecursive in Cache.cs)
                string parentConfigPath = ConfigPathUtility.GetParent(configPath); 
                parentData = GetConfigPathData(parentConfigPath);
                string parentKey = CreateKey(parentConfigPath); 
                cacheItemDependencies = new string[1] {parentKey}; 

                if (!WebConfigurationHost.IsVirtualPathConfigPath(configPath)) { 
                    // assume hardcoded levels above the path, such as root web.config, exist
                    cacheEntryIsNotRemovable = true;
                }
                else { 
                    cacheEntryIsNotRemovable = !isRemovable;
                    WebConfigurationHost.GetSiteIDAndVPathFromConfigPath(configPath, out siteID, out virtualPath); 
                    physicalPath = GetPhysicalPath(virtualPath); 

                    // Add a dependency on the path itself, if it is a file, 
                    // to handle the case where a file is deleted and replaced
                    // with a directory of the same name.
                    if (!String.IsNullOrEmpty(physicalPath)) {
                        FileUtil.PhysicalPathStatus(physicalPath, false, false, out exists, out isDirectory); 
                        if (exists && !isDirectory) {
                            fileDependencies = new string[1] {physicalPath}; 
                        } 
                    }
                } 

                try {
                    dependency = new CacheDependency(0, fileDependencies, cacheItemDependencies);
                } 
                catch {
                    // CacheDependency ctor could fail because of bogus file path 
                    // and it is ok not to watch those 
                }
            } 

            // Try to add the CachedPathData to the cache.
            CachedPathData    dataAdd = null;
            bool              isDataCreator = false; 
            bool              initCompleted = false;
            CacheItemPriority priority = cacheEntryIsNotRemovable ? CacheItemPriority.NotRemovable : CacheItemPriority.Normal; 
            TimeSpan          slidingExpiration = cacheEntryIsNotRemovable ? Cache.NoSlidingExpiration : UrlMetadataSlidingExpiration; 
            try {
                using (dependency) { 
                    dataAdd = new CachedPathData(configPath, virtualPath, physicalPath, exists);
                    try {
                    }
                    finally { 
                        data = (CachedPathData) cacheInternal.UtcAdd(key, dataAdd, dependency,
                            Cache.NoAbsoluteExpiration, slidingExpiration, 
                            priority, s_callback); 

                        if (data == null) { 
                            isDataCreator = true;
                        }
                    }
                } 

                // If another thread added it first, return the data 
                if (!isDataCreator) { 
                    data.WaitForInit();
                    return data; 
                }

                // This thread is the creator of the CachedPathData, initialize it
                lock (dataAdd) { 
                    try {
                        dataAdd.Init(parentData); 
                        initCompleted = true; 
                    }
                    finally { 
                        // free waiters
                        dataAdd._flags[FInited] = true;

                        // Wake up waiters. 
                        Monitor.PulseAll(dataAdd);
 
                        if (dataAdd._flags[FCloseNeeded]) { 
                            // If we have received a call back to close, then lets
                            // make sure that our config object is cleaned up 
                            dataAdd.Close();
                        }
                    }
                } 
            }
            finally { 
                // All the work in this finally block is for the case where we're the 
                // creator of the CachedPathData.
                if (isDataCreator) { 

                    //

 

 
                    if (!dataAdd._flags[FInited]) { 
                        lock (dataAdd) {
                            // free waiters 
                            dataAdd._flags[FInited] = true;

                            // Wake up waiters.
                            Monitor.PulseAll(dataAdd); 

                            if (dataAdd._flags[FCloseNeeded]) { 
                                // If we have received a call back to close, then lets 
                                // make sure that our config object is cleaned up
                                dataAdd.Close(); 
                            }
                        }
                    }
 
                    //
                    // Even though there is a try/catch handler surrounding the call to Init, 
                    // a ThreadAbortException can still cause the handler to be bypassed. 
                    //
                    // If there is an error, either a thread abort or an error in the config 
                    // file itself, we do want to leave the item cached for a short period
                    // so that we do not revisit the error and potentially reparse the config file
                    // on every request.
                    // 
                    // The reason we simply do not leave the item in the cache forever is that the
                    // problem that caused the configuration exception may be fixed without touching 
                    // the config file in a way that causes a file change notification (for example, an 
                    // acl change in a parent directory, or a change of path mapping in the metabase).
                    // 
                    // NOTE: It is important to reinsert the item into the cache AFTER dropping
                    // the lock on dataAdd, in order to prevent the possibility of deadlock.
                    //
                    Debug.Assert(dataAdd._flags[FInited], "_flags[FInited]"); 
                    if (!initCompleted || (dataAdd.ConfigRecord != null && dataAdd.ConfigRecord.HasInitErrors)) {
                        // 
                        // Create a new dependency object as the old one cannot be reused. 
                        // Do not include a file dependency if initialization could not be completed,
                        // as invoking the file system could lead to further errors during a thread abort. 
                        //
                        if (dependency != null) {
                            if (!initCompleted) {
                                dependency = new CacheDependency(0, null, cacheItemDependencies); 
                            }
                            else { 
                                dependency = new CacheDependency(0, fileDependencies, cacheItemDependencies); 
                            }
                        } 

                        using (dependency) {
                            cacheInternal.UtcInsert(key, dataAdd, dependency,
                                DateTime.UtcNow.AddSeconds(5), Cache.NoSlidingExpiration, 
                                CacheItemPriority.Normal, s_callback);
                        } 
                    } 

                } 
            }

            return dataAdd;
        } 

        // Ensure that the physical path does not look suspicious (MSRC 5556). 
        static private string GetPhysicalPath(VirtualPath virtualPath) { 
            string physicalPath = null;
            try { 
                physicalPath = virtualPath.MapPathInternal(true);
            }
            catch (HttpException e) {
                // 
                // Treat exceptions that are thrown because the path is suspicious
                // as "404 Not Found" exceptions. Implementations of MapPath 
                // will throw HttpException with no error code if the path is 
                // suspicious.
                // 
                if (e.GetHttpCode() == 500) {
                    throw new HttpException(404, String.Empty);
                }
                else { 
                    throw;
                } 
            } 

            // 
            // Throw "404 Not Found" if the path is suspicious and
            // the implementation of MapPath has not already done so.
            //
            FileUtil.CheckSuspiciousPhysicalPath(physicalPath); 

            return physicalPath; 
        } 

        // Remove CachedPathData when the first request for the path results in a 
        // 400 range error. We need to remove all data up the path to account for
        // virtual files.
        // An example of a 400 range error is "path not found".
        static internal void RemoveBadPathData(CachedPathData pathData) { 
            CacheInternal cacheInternal = HttpRuntime.CacheInternal;
 
            string configPath = pathData._configPath; 
            string key = CreateKey(configPath);
            while (pathData != null && !pathData.CompletedFirstRequest && !pathData.Exists) { 

                cacheInternal.Remove(key);

                configPath = ConfigPathUtility.GetParent(configPath); 
                if (configPath == null)
                    break; 
 
                key = CreateKey(configPath);
                pathData = (CachedPathData) cacheInternal.Get(key); 
            }
        }

        // Mark CachedPathData as completed when the first request for the path results in a 
        // status outside the 400 range. We need to mark all data up the path to account for
        // virtual files. 
        static internal void MarkCompleted(CachedPathData pathData) { 
            CacheInternal cacheInternal = HttpRuntime.CacheInternal;
 
            string configPath = pathData._configPath;
            do {
                pathData.CompletedFirstRequest = true;
 
                configPath = ConfigPathUtility.GetParent(configPath);
                if (configPath == null) 
                    break; 

                string key = CreateKey(configPath); 
                pathData = (CachedPathData) cacheInternal.Get(key);
            } while (pathData != null && !pathData.CompletedFirstRequest);
        }
 
        // Close
        // 
        // Close the object.  This does not mean it can not be used anymore, 
        // it just means that the cleanup as been done, so we don't have
        // to worry about closing it anymore 
        //
        void Close() {
            // Only close if we are propertly initialized
            if (_flags[FInited]) { 

                // Only close if we haven't already closed 
                if (_flags.ChangeValue(FClosed, true)) { 

                    // Remove the config record if we own it 
                    // N.B. ConfigRecord.Remove is safe to call more than once.
                    if (_flags[FOwnsConfigRecord]) {
                        ConfigRecord.Remove();
                    } 
                }
            } 
        } 

        // OnCacheItemRemoved 
        //
        // Notification the items has been removed from the cache.  Flag
        // the item to be cleaned up, and then try cleanup
        // 
        static void OnCacheItemRemoved(string key, object value, CacheItemRemovedReason reason) {
            CachedPathData data = (CachedPathData) value; 
 
            data._flags[FCloseNeeded] = true;
            data.Close(); 
        }

        static string CreateKey(string configPath) {
            Debug.Assert(configPath == configPath.ToLower(CultureInfo.InvariantCulture), "configPath == configPath.ToLower(CultureInfo.InvariantCulture)"); 
            return CacheInternal.PrefixPathData + configPath;
        } 
 
        // Initialize the data
        void Init(CachedPathData parentData) { 
            // Note that _runtimeConfig will be set to the singleton instance of ErrorRuntimeConfig
            // if a ThreadAbortException is thrown during this method.
            Debug.Assert(_runtimeConfig == RuntimeConfig.GetErrorRuntimeConfig(), "_runtimeConfig == RuntimeConfig.GetErrorRuntimeConfig()");
 
            if (!HttpConfigurationSystem.UseHttpConfigurationSystem) {
                // 
                // configRecord may legitimately be null if we are not using the HttpConfigurationSystem. 
                //
                _runtimeConfig = null; 
            }
            else {
                IInternalConfigRecord configRecord = HttpConfigurationSystem.GetUniqueConfigRecord(_configPath);
                Debug.Assert(configRecord != null, "configRecord != null"); 

                if (configRecord.ConfigPath.Length == _configPath.Length) { 
                    // 
                    // The config is unique to this path, so this make this record the owner of the config.
                    // 
                    _flags[FOwnsConfigRecord] = true;
                    _runtimeConfig = new RuntimeConfig(configRecord);
                }
                else { 
                    //
                    // The config record is the same as an ancestor's, so use the parent's RuntimeConfig. 
                    // 
                    Debug.Assert(parentData != null, "parentData != null");
                    _runtimeConfig = parentData._runtimeConfig; 
                }
            }
        }
 
        void WaitForInit() {
            // Wait for the data to be initialized. 
            if (!_flags[FInited]) { 
                lock (this) {
                    if (!_flags[FInited]) { 
                        Monitor.Wait(this);
                    }
                }
            } 
        }
 
        // Ensure that Request.PhysicalPath is valid (canonical, not too long, and contains valid characters). 
        // The work is done by CheckSuspiciousPhysicalPath, but as a perf optimization, we can compare
        // Request.PhysicalPath with the cached path result.  The cached path result is validated before 
        // it is cached.  As long as the cached path result is identical to Request.PhysicalPath, we don't
        // have to call CheckSuspiciousPhysicalPath again.
        internal void ValidatePath(String physicalPath) {
            if (String.IsNullOrEmpty(_physicalPath) && String.IsNullOrEmpty(physicalPath)) { 
                return;
            } 
            if (!String.IsNullOrEmpty(_physicalPath) && !String.IsNullOrEmpty(physicalPath)) { 
                if (_physicalPath.Length == physicalPath.Length) {
                    // if identical, we don't have to call CheckSuspiciousPhysicalPath 
                    if (0 == String.Compare(_physicalPath, 0, physicalPath, 0, physicalPath.Length, StringComparison.OrdinalIgnoreCase)) {
                        return;
                    }
                } 
                else if (_physicalPath.Length - physicalPath.Length == 1) {
                    // if they differ by a trailing slash, we shouldn't call CheckSuspiciousPhysicalPath again 
                    if (_physicalPath[_physicalPath.Length-1] == System.IO.Path.DirectorySeparatorChar 
                        && (0 == String.Compare(_physicalPath, 0, physicalPath, 0, physicalPath.Length, StringComparison.OrdinalIgnoreCase))) {
                        return; 
                    }
                }
                else if (physicalPath.Length - _physicalPath.Length == 1) {
                    // if they differ by a trailing slash, we shouldn't call CheckSuspiciousPhysicalPath again 
                    if (physicalPath[physicalPath.Length-1] == System.IO.Path.DirectorySeparatorChar
                        && (0 == String.Compare(_physicalPath, 0, physicalPath, 0, _physicalPath.Length, StringComparison.OrdinalIgnoreCase))) { 
                        return; 
                    }
                } 
            }

            // If we're here, the paths were different, which normally should not happen.
            Debug.Assert(false, "ValidatePath optimization failed: Request.PhysicalPath=" 
                         + physicalPath + "; _physicalPath=" + _physicalPath);
            FileUtil.CheckSuspiciousPhysicalPath(physicalPath); 
        } 

        internal bool CompletedFirstRequest { 
            get {return _flags[FCompletedFirstRequest];}
            set {
                _flags[FCompletedFirstRequest] = value;
            } 
        }
 
        internal VirtualPath Path { 
            get {return _virtualPath;}
        } 

        internal string PhysicalPath {
            get { return _physicalPath; }
        } 

        internal bool AnonymousAccessChecked { 
            get { return _flags[FAnonymousAccessChecked]; } 
            set { _flags[FAnonymousAccessChecked] = value; }
        } 

        internal bool AnonymousAccessAllowed {
            get { return _flags[FAnonymousAccessAllowed]; }
            set { _flags[FAnonymousAccessAllowed] = value; } 
        }
 
        internal bool Exists { 
            get {return _flags[FExists];}
        } 

        internal HandlerMappingMemo CachedHandler {
            get {return _handlerMemo;}
            set {_handlerMemo = value;} 
        }
 
 
        internal IInternalConfigRecord ConfigRecord {
            get { 
                // _runtimeConfig may be null if we are not using the HttpConfigurationSystem.
                return (_runtimeConfig != null) ? _runtimeConfig.ConfigRecord : null;
            }
        } 

        internal RuntimeConfig RuntimeConfig { 
            get { 
                return _runtimeConfig;
            } 
        }

        // Any time we cache metadata for the URL, we should use this
        // sliding expiration, unless DoNotCacheUrlMetadata is true. 
        // This is currently used by CachedPathData, MapPathBasedVirtualPathProvider,
        // and FileAuthorizationModule 
        internal static TimeSpan UrlMetadataSlidingExpiration { 
            get {
                return s_urlMetadataSlidingExpiration; 
            }
        }

        // if true, do not cache at all. 
        internal static bool DoNotCacheUrlMetadata {
            get { return s_doNotCacheUrlMetadata; } 
        } 
    }
} 


// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK