1 //------------------------------------------------------------------------------
2 // <copyright file="ProcessHostMapPath.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
6 namespace System
.Web
.Configuration
{
8 using System
.Collections
;
9 using System
.Configuration
;
10 using System
.Diagnostics
.CodeAnalysis
;
11 using System
.Globalization
;
13 using System
.Runtime
.InteropServices
;
15 using System
.Web
.Caching
;
16 using System
.Web
.Hosting
;
18 using System
.Web
.Util
;
22 // Uses IIS 7 native config
24 internal sealed class ProcessHostMapPath
: IConfigMapPath
, IConfigMapPath2
{
25 IProcessHostSupportFunctions _functions
;
26 internal static string _DefaultPhysicalPathOnMapPathFailure
= null;
28 static ProcessHostMapPath() {
29 HttpRuntime
.ForceStaticInit();
32 [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification
= "We carefully control this method's caller.")]
33 internal ProcessHostMapPath(IProcessHostSupportFunctions functions
) {
34 // if the functions are null and we're
35 // not in a worker process, init the mgdeng config system
36 if (null == functions
) {
37 ProcessHostConfigUtils
.InitStandaloneConfig();
40 // we need to explicit create a COM proxy in this app domain
41 // so we don't go back to the default domain or have lifetime issues
42 if (null != functions
) {
43 _functions
= Misc
.CreateLocalSupportFunctions(functions
);
46 // proactive set the config functions for
47 // webengine in case this is a TCP init path
48 if (null != _functions
) {
49 IntPtr configSystem
= _functions
.GetNativeConfigurationSystem();
50 Debug
.Assert(IntPtr
.Zero
!= configSystem
, "null != configSystem");
52 if (IntPtr
.Zero
!= configSystem
) {
53 // won't fail if valid pointer
54 // no cleanup needed, we don't own instance
55 UnsafeIISMethods
.MgdSetNativeConfiguration(configSystem
);
60 string IConfigMapPath
.GetMachineConfigFilename() {
61 return HttpConfigurationSystem
.MachineConfigurationFilePath
;
64 string IConfigMapPath
.GetRootWebConfigFilename() {
65 string rootWeb
= null;
67 if (null != _functions
) {
68 rootWeb
= _functions
.GetRootWebConfigFilename();
71 if (String
.IsNullOrEmpty(rootWeb
)) {
72 rootWeb
= HttpConfigurationSystem
.RootWebConfigurationFilePath
;
74 Debug
.Trace("MapPath", "ProcHostMP.GetRootWebConfigFilename = " +
77 Debug
.Assert(!String
.IsNullOrEmpty(rootWeb
), "rootWeb != null or empty");
82 private void GetPathConfigFilenameWorker(string siteID
, VirtualPath path
, out string directory
, out string baseName
) {
83 directory
= MapPathCaching(siteID
, path
);
84 if (directory
!= null) {
85 baseName
= HttpConfigurationSystem
.WebConfigFileName
;
91 Debug
.Trace("MapPath", "ProcHostMP.GetPathConfigFilename(" + siteID
+ ", " + path
+ ")\n" +
92 " result = " + directory
+ " and " + baseName
+ "\n");
95 void IConfigMapPath
.GetPathConfigFilename(
96 string siteID
, string path
, out string directory
, out string baseName
) {
97 GetPathConfigFilenameWorker(siteID
, VirtualPath
.Create(path
), out directory
, out baseName
);
100 // IConfigMapPath2 VirtualPath variant
101 void IConfigMapPath2
.GetPathConfigFilename(
104 out string directory
,
105 out string baseName
) {
106 GetPathConfigFilenameWorker(siteID
, path
, out directory
, out baseName
);
110 void IConfigMapPath
.GetDefaultSiteNameAndID(out string siteName
, out string siteID
) {
111 Debug
.Trace("MapPath", "ProcHostMP.GetDefaultSiteNameAndID\n");
112 siteID
= ProcessHostConfigUtils
.DEFAULT_SITE_ID_STRING
;
113 siteName
= ProcessHostConfigUtils
.GetSiteNameFromId(ProcessHostConfigUtils
.DEFAULT_SITE_ID_UINT
);
117 void IConfigMapPath
.ResolveSiteArgument(string siteArgument
, out string siteName
, out string siteID
) {
118 Debug
.Trace("MapPath", "ProcHostMP.ResolveSiteArgument(" + siteArgument
+ ")\n");
121 if ( String
.IsNullOrEmpty(siteArgument
) ||
122 StringUtil
.EqualsIgnoreCase(siteArgument
, ProcessHostConfigUtils
.DEFAULT_SITE_ID_STRING
) ||
123 StringUtil
.EqualsIgnoreCase(siteArgument
, ProcessHostConfigUtils
.GetSiteNameFromId(ProcessHostConfigUtils
.DEFAULT_SITE_ID_UINT
))) {
125 siteName
= ProcessHostConfigUtils
.GetSiteNameFromId(ProcessHostConfigUtils
.DEFAULT_SITE_ID_UINT
);
126 siteID
= ProcessHostConfigUtils
.DEFAULT_SITE_ID_STRING
;
129 siteName
= String
.Empty
;
130 siteID
= String
.Empty
;
132 string resolvedName
= null;
133 if (IISMapPath
.IsSiteId(siteArgument
)) {
136 if (UInt32
.TryParse(siteArgument
, out id
)) {
137 resolvedName
= ProcessHostConfigUtils
.GetSiteNameFromId(id
);
140 // try to resolve the string
142 uint id
= UnsafeIISMethods
.MgdResolveSiteName(IntPtr
.Zero
, siteArgument
);
144 siteID
= id
.ToString(CultureInfo
.InvariantCulture
);
145 siteName
= siteArgument
;
150 if (!String
.IsNullOrEmpty(resolvedName
)) {
151 siteName
= resolvedName
;
152 siteID
= siteArgument
;
155 siteName
= siteArgument
;
156 siteID
= String
.Empty
;
160 Debug
.Assert(!String
.IsNullOrEmpty(siteName
), "!String.IsNullOrEmpty(siteName), siteArg=" + siteArgument
);
163 private string MapPathWorker(string siteID
, VirtualPath path
) {
164 Debug
.Trace("MapPath", "ProcHostMP.MapPath(" + siteID
+ ", " + path
+ ")\n");
165 return MapPathCaching(siteID
, path
);
168 // IConfigMapPath2 variant with VirtualPath
169 string IConfigMapPath2
.MapPath(string siteID
, VirtualPath path
) {
170 return MapPathWorker(siteID
, path
);
173 string IConfigMapPath
.MapPath(string siteID
, string path
) {
174 return MapPathWorker(siteID
, VirtualPath
.Create(path
));
177 string IConfigMapPath
.GetAppPathForPath(string siteID
, string path
) {
178 VirtualPath resolved
= GetAppPathForPathWorker(siteID
, VirtualPath
.Create(path
));
179 return resolved
.VirtualPathString
;
182 // IConfigMapPath2 variant with VirtualPath
183 VirtualPath IConfigMapPath2
.GetAppPathForPath(string siteID
, VirtualPath path
) {
184 return GetAppPathForPathWorker(siteID
, path
);
187 VirtualPath
GetAppPathForPathWorker(string siteID
, VirtualPath path
) {
188 Debug
.Trace("MapPath", "ProcHostMP.GetAppPathForPath(" + siteID
+ ", " + path
.VirtualPathString
+ ")\n");
191 if (!UInt32
.TryParse(siteID
, out siteValue
)) {
192 return VirtualPath
.RootVirtualPath
;
195 IntPtr pBstr
= IntPtr
.Zero
;
199 int result
= UnsafeIISMethods
.MgdGetAppPathForPath(IntPtr
.Zero
, siteValue
, path
.VirtualPathString
, out pBstr
, out cBstr
);
200 appPath
= (result
== 0 && cBstr
> 0) ? StringUtil
.StringFromWCharPtr(pBstr
, cBstr
) : null;
203 if (pBstr
!= IntPtr
.Zero
) {
204 Marshal
.FreeBSTR(pBstr
);
208 return (appPath
!= null) ? VirtualPath
.Create(appPath
) : VirtualPath
.RootVirtualPath
;
211 private string MapPathCaching(string siteID
, VirtualPath path
) {
212 // UrlMetaDataSlidingExpiration config setting controls
213 // the duration of all cached items related to url metadata.
214 bool doNotCache
= CachedPathData
.DoNotCacheUrlMetadata
;
215 TimeSpan slidingExpiration
= CachedPathData
.UrlMetadataSlidingExpiration
;
217 // store a single variation of the path
218 VirtualPath originalPath
= path
;
219 MapPathCacheInfo cacheInfo
;
222 cacheInfo
= new MapPathCacheInfo();
225 // Check if it's in the cache
226 String cacheKey
= CacheInternal
.PrefixMapPath
+ siteID
+ path
.VirtualPathString
;
227 cacheInfo
= (MapPathCacheInfo
)HttpRuntime
.Cache
.InternalCache
.Get(cacheKey
);
229 // If not in cache, add it to the cache
230 if (cacheInfo
== null) {
231 cacheInfo
= new MapPathCacheInfo();
233 // No need to have a lock here. UtcAdd will add the entry if it doesn't exist.
234 // If it does exist, the existing value will be returned (Dev10 Bug 755034).
235 object existingEntry
= HttpRuntime
.Cache
.InternalCache
.Add(cacheKey
, cacheInfo
, new CacheInsertOptions() { SlidingExpiration = slidingExpiration }
);
236 if (existingEntry
!= null) {
237 cacheInfo
= existingEntry
as MapPathCacheInfo
;
242 // If not been evaluated, then evaluate it
243 if (!cacheInfo
.Evaluated
) {
245 if (!cacheInfo
.Evaluated
&& HttpRuntime
.IsMapPathRelaxed
) {
246 //////////////////////////////////////////////////////////////////
247 // Verify that the parent path is valid. If parent is invalid, then set this to invalid
248 if (path
.VirtualPathString
.Length
> 1) {
249 VirtualPath vParent
= path
.Parent
;
250 if (vParent
!= null) {
251 string parentPath
= vParent
.VirtualPathString
;
252 if (parentPath
.Length
> 1 && StringUtil
.StringEndsWith(parentPath
, '/')) { // Trim the extra trailing / if there is one
253 vParent
= VirtualPath
.Create(parentPath
.Substring(0, parentPath
.Length
- 1));
256 string parentMapPathResult
= MapPathCaching(siteID
, vParent
);
257 if (parentMapPathResult
== HttpRuntime
.GetRelaxedMapPathResult(null)) {
258 // parent is invalid!
259 cacheInfo
.MapPathResult
= parentMapPathResult
;
260 cacheInfo
.Evaluated
= true;
263 cacheInfo
.MapPathResult
= HttpRuntime
.GetRelaxedMapPathResult(null);
264 cacheInfo
.Evaluated
= true;
270 if (!cacheInfo
.Evaluated
) {
272 string physicalPath
= null;
274 if (UInt32
.TryParse(siteID
, out siteIDValue
)) {
275 string siteName
= ProcessHostConfigUtils
.GetSiteNameFromId(siteIDValue
);
276 physicalPath
= ProcessHostConfigUtils
.MapPathActual(siteName
, path
);
278 if (physicalPath
!= null && physicalPath
.Length
== 2 && physicalPath
[1] == ':')
279 physicalPath
+= "\\";
280 // Throw if the resulting physical path is not canonical, to prevent potential
281 // security issues (VSWhidbey 418125)
283 if (HttpRuntime
.IsMapPathRelaxed
) {
284 physicalPath
= HttpRuntime
.GetRelaxedMapPathResult(physicalPath
);
287 if (FileUtil
.IsSuspiciousPhysicalPath(physicalPath
)) {
288 if (HttpRuntime
.IsMapPathRelaxed
) {
289 physicalPath
= HttpRuntime
.GetRelaxedMapPathResult(null);
291 throw new HttpException(SR
.GetString(SR
.Cannot_map_path
, path
));
295 cacheInfo
.MapPathResult
= physicalPath
;
296 } catch (Exception e
) {
297 if (HttpRuntime
.IsMapPathRelaxed
) {
298 cacheInfo
.MapPathResult
= HttpRuntime
.GetRelaxedMapPathResult(null);
300 cacheInfo
.CachedException
= e
;
301 cacheInfo
.Evaluated
=true;
306 cacheInfo
.Evaluated
= true;
311 // Throw an exception if required
312 if (cacheInfo
.CachedException
!= null) {
313 throw cacheInfo
.CachedException
;
316 return MatchResult(originalPath
, cacheInfo
.MapPathResult
);
319 private string MatchResult(VirtualPath path
, string result
) {
320 if (string.IsNullOrEmpty(result
)) {
324 result
= result
.Replace('/', '\\');
326 // ensure extra '\\' in the physical path if the virtual path had extra '/'
327 // and the other way -- no extra '\\' in physical if virtual didn't have it.
328 if (path
.HasTrailingSlash
) {
329 if (!UrlPath
.PathEndsWithExtraSlash(result
)) {
330 result
= result
+ "\\";
334 if (UrlPath
.PathEndsWithExtraSlash(result
)) {
335 result
= result
.Substring(0, result
.Length
- 1);