Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web / Configuration / MetabaseServerConfig.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="MetabaseServerConfig.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.Configuration {
8
9     using System.Configuration;
10     using System.Collections;
11     using System.Globalization;
12     using System.Text;
13     using System.IO;
14     using System.Web.Util;
15     using System.Web.Hosting;
16     using System.Web.Caching;
17     using Microsoft.Win32;
18
19     class MetabaseServerConfig : IServerConfig, IConfigMapPath, IConfigMapPath2 {
20         private const string    DEFAULT_SITEID = "1";
21         private const string    DEFAULT_ROOTAPPID = "/LM/W3SVC/1/ROOT";
22         private const int       MAX_PATH=260;
23         private const int       BUFSIZE = MAX_PATH + 1;
24         private const string    LMW3SVC_PREFIX = "/LM/W3SVC/";
25         private const string    ROOT_SUFFIX="/ROOT";
26
27         static private MetabaseServerConfig s_instance;
28         static private object               s_initLock = new Object();
29
30         string _defaultSiteName;
31         string _siteIdForCurrentApplication;
32
33         static internal IServerConfig GetInstance() {
34             if (s_instance == null) {
35                 lock (s_initLock) {
36                     if (s_instance == null) {
37                         s_instance = new MetabaseServerConfig();
38                     }
39                 }
40             }
41
42             return s_instance;
43         }
44
45         private MetabaseServerConfig() {
46             HttpRuntime.ForceStaticInit(); // force webengine.dll to load
47
48             // Get the default site information
49             bool found = MBGetSiteNameFromSiteID(DEFAULT_SITEID, out _defaultSiteName);
50             _siteIdForCurrentApplication = HostingEnvironment.SiteID;
51             if (_siteIdForCurrentApplication == null) {
52                 _siteIdForCurrentApplication = DEFAULT_SITEID;
53             }
54         }
55
56         string IServerConfig.GetSiteNameFromSiteID(string siteID) {
57             if (StringUtil.EqualsIgnoreCase(siteID, DEFAULT_SITEID))
58                 return _defaultSiteName;
59
60             string siteName;
61             bool found = MBGetSiteNameFromSiteID(siteID, out siteName);
62             return siteName;
63         }
64
65         // if appHost is null, we use the site ID for the current application
66         string IServerConfig.MapPath(IApplicationHost appHost, VirtualPath path) {
67             string siteID = (appHost == null) ? _siteIdForCurrentApplication : appHost.GetSiteID();
68             return MapPathCaching(siteID, path);
69         }
70
71         string[] IServerConfig.GetVirtualSubdirs(VirtualPath path, bool inApp) {
72             string aboPath = GetAboPath(_siteIdForCurrentApplication, path.VirtualPathString);
73             return MBGetVirtualSubdirs(aboPath, inApp);
74         }
75
76         bool IServerConfig.GetUncUser(IApplicationHost appHost, VirtualPath path, out string username, out string password) {
77             string aboPath = GetAboPath(appHost.GetSiteID(), path.VirtualPathString);
78             return MBGetUncUser(aboPath, out username, out password);
79         }
80
81         long IServerConfig.GetW3WPMemoryLimitInKB() {
82             return (long) MBGetW3WPMemoryLimitInKB();
83         }
84
85         string IConfigMapPath.GetMachineConfigFilename() {
86             return HttpConfigurationSystem.MachineConfigurationFilePath;
87         }
88
89         string IConfigMapPath.GetRootWebConfigFilename() {
90             return HttpConfigurationSystem.RootWebConfigurationFilePath;
91         }
92
93         private void GetPathConfigFilenameWorker(string siteID, VirtualPath path, out string directory, out string baseName) {
94             directory = MapPathCaching(siteID, path);
95             if (directory != null) {
96                 baseName = HttpConfigurationSystem.WebConfigFileName;
97             }
98             else {
99                 baseName = null;
100             }
101         }
102
103         // Based on <siteID, path>, return:
104         // directory - the physical directory of the path (vpath)
105         // baseName - name of the configuration file to look for.
106         // E.g. if siteID="1" and path="/", directory="c:\inetpub\wwwroot" and baseName="web.config"
107         void IConfigMapPath.GetPathConfigFilename(
108                 string siteID, string path, out string directory, out string baseName) {
109             GetPathConfigFilenameWorker(siteID, VirtualPath.Create(path), out directory, out baseName);
110         }
111
112         void IConfigMapPath2.GetPathConfigFilename(
113                 string siteID, VirtualPath path, out string directory, out string baseName) {
114             GetPathConfigFilenameWorker(siteID, path, out directory, out baseName);
115         }
116
117         void IConfigMapPath.GetDefaultSiteNameAndID(out string siteName, out string siteID) {
118             siteName = _defaultSiteName;
119             siteID = DEFAULT_SITEID;
120         }
121
122         void IConfigMapPath.ResolveSiteArgument(string siteArgument, out string siteName, out string siteID) {
123             if (    String.IsNullOrEmpty(siteArgument) ||
124                     StringUtil.EqualsIgnoreCase(siteArgument, DEFAULT_SITEID) ||
125                     StringUtil.EqualsIgnoreCase(siteArgument, _defaultSiteName)) {
126
127                 siteName = _defaultSiteName;
128                 siteID = DEFAULT_SITEID;
129             }
130             else {
131                 siteName = String.Empty;
132                 siteID = String.Empty;
133
134                 bool found = false;
135                 if (IISMapPath.IsSiteId(siteArgument)) {
136                     found = MBGetSiteNameFromSiteID(siteArgument, out siteName);
137                 }
138
139                 if (found) {
140                     siteID = siteArgument;
141                 }
142                 else {
143                     found = MBGetSiteIDFromSiteName(siteArgument, out siteID);
144                     if (found) {
145                         siteName = siteArgument;
146                     }
147                     else {
148                         siteName = siteArgument;
149                         siteID = String.Empty;
150                     }
151                 }
152             }
153         }
154
155         // Map from <siteID, path> to a physical file path
156         string IConfigMapPath.MapPath(string siteID, string vpath) {
157             return MapPathCaching(siteID, VirtualPath.Create(vpath));
158         }
159
160         // IConfigMapPath2 VirtualPath fast path
161         string IConfigMapPath2.MapPath(string siteID, VirtualPath vpath) {
162             return MapPathCaching(siteID, vpath);
163         }
164
165         VirtualPath GetAppPathForPathWorker(string siteID, VirtualPath vpath) {
166             string aboPath = GetAboPath(siteID, vpath.VirtualPathString);
167             string appAboPath = MBGetAppPath(aboPath);
168             if (appAboPath == null)
169                 return VirtualPath.RootVirtualPath;
170
171             string rootAboPath = GetRootAppIDFromSiteID(siteID);
172             if (StringUtil.EqualsIgnoreCase(rootAboPath, appAboPath)) {
173                 return VirtualPath.RootVirtualPath;
174             }
175
176             string appPath = appAboPath.Substring(rootAboPath.Length);
177             return VirtualPath.CreateAbsolute(appPath);
178         }
179
180         string IConfigMapPath.GetAppPathForPath(string siteID, string vpath) {
181             VirtualPath resolved = GetAppPathForPathWorker(siteID, VirtualPath.Create(vpath));
182             return resolved.VirtualPathString;
183         }
184
185         // IConfigMapPath2 VirtualPath fast path
186         VirtualPath IConfigMapPath2.GetAppPathForPath(string siteID, VirtualPath vpath) {
187             return GetAppPathForPathWorker(siteID, vpath);
188         }
189
190         private string MatchResult(VirtualPath path, string result) {
191             if (string.IsNullOrEmpty(result)) {
192                 return result;
193             }
194
195             result = result.Replace('/', '\\');
196
197             // ensure extra '\\' in the physical path if the virtual path had extra '/'
198             // and the other way -- no extra '\\' in physical if virtual didn't have it.
199             if (path.HasTrailingSlash) {
200                 if (!UrlPath.PathEndsWithExtraSlash(result) && !UrlPath.PathIsDriveRoot(result)) {
201                     result = result + "\\";
202                 }
203             }
204             else {
205                 if (UrlPath.PathEndsWithExtraSlash(result) && !UrlPath.PathIsDriveRoot(result)) {
206                     result = result.Substring(0, result.Length - 1);
207                 }
208             }
209
210             return result;
211         }
212
213         private string MapPathCaching(string siteID, VirtualPath path) {
214             // UrlMetaDataSlidingExpiration config setting controls 
215             // the duration of all cached items related to url metadata.
216             bool doNotCache = CachedPathData.DoNotCacheUrlMetadata;
217             TimeSpan slidingExpiration = CachedPathData.UrlMetadataSlidingExpiration; 
218
219             // store a single variation of the path
220             VirtualPath originalPath = path;
221             MapPathCacheInfo cacheInfo;
222
223             if (doNotCache) {
224                 cacheInfo = new MapPathCacheInfo();
225             }
226             else {
227                 // Check if it's in the cache
228                 String cacheKey = CacheInternal.PrefixMapPath + siteID + path.VirtualPathString;
229                 cacheInfo = (MapPathCacheInfo)HttpRuntime.Cache.InternalCache.Get(cacheKey);
230
231                 // If not in cache, add it to the cache
232                 if (cacheInfo == null) {
233                     cacheInfo = new MapPathCacheInfo();
234                     // Add to the cache.
235                     // No need to have a lock here. UtcAdd will add the entry if it doesn't exist. 
236                     // If it does exist, the existing value will be returned (Dev10 Bug 755034).
237                     object existingEntry = HttpRuntime.Cache.InternalCache.Add(cacheKey, cacheInfo, new CacheInsertOptions() { SlidingExpiration = slidingExpiration });
238                     if (existingEntry != null) {
239                         cacheInfo = existingEntry as MapPathCacheInfo;
240                     }
241                 }
242             }
243
244             // If not been evaluated, then evaluate it
245             if (!cacheInfo.Evaluated) {
246                 lock(cacheInfo) {
247
248                     if (!cacheInfo.Evaluated && HttpRuntime.IsMapPathRelaxed) {
249                         //////////////////////////////////////////////////////////////////
250                         // Verify that the parent path is valid. If parent is invalid, then set this to invalid
251                         if (path.VirtualPathString.Length > 1) {
252                             VirtualPath vParent = path.Parent;
253                             if (vParent != null) {
254                                 string parentPath = vParent.VirtualPathString;
255                                 if (parentPath.Length > 1 && StringUtil.StringEndsWith(parentPath, '/')) { // Trim the extra trailing / if there is one
256                                     vParent = VirtualPath.Create(parentPath.Substring(0, parentPath.Length - 1));
257                                 }
258                                 try {
259                                     string parentMapPathResult = MapPathCaching(siteID, vParent);
260                                     if (parentMapPathResult == HttpRuntime.GetRelaxedMapPathResult(null)) {
261                                         // parent is invalid!
262                                         cacheInfo.MapPathResult = parentMapPathResult;
263                                         cacheInfo.Evaluated = true;
264                                     }
265                                 } catch {
266                                     cacheInfo.MapPathResult = HttpRuntime.GetRelaxedMapPathResult(null);
267                                     cacheInfo.Evaluated = true;
268                                 }
269                             }
270                         }
271                     }
272
273                     if (!cacheInfo.Evaluated) {
274                         string physicalPath = null;
275
276                         try {
277                             physicalPath = MapPathActual(siteID, path);
278
279                             if (HttpRuntime.IsMapPathRelaxed) {
280                                 physicalPath = HttpRuntime.GetRelaxedMapPathResult(physicalPath);
281                             }
282
283                             // Throw if the resulting physical path is not canonical, to prevent potential
284                             // security issues (VSWhidbey 418125)
285                             if (FileUtil.IsSuspiciousPhysicalPath(physicalPath)) {
286                                 if (HttpRuntime.IsMapPathRelaxed) {
287                                     physicalPath = HttpRuntime.GetRelaxedMapPathResult(null);
288                                 } else {
289                                     throw new HttpException(SR.GetString(SR.Cannot_map_path, path));
290                                 }
291                             }
292
293                         } catch (Exception e) {
294                             if (HttpRuntime.IsMapPathRelaxed) {
295                                 physicalPath = HttpRuntime.GetRelaxedMapPathResult(null);
296                             } else {
297                                 cacheInfo.CachedException = e;
298                                 cacheInfo.Evaluated=true;
299                                 throw;
300                             }
301                         }
302
303                         if ( physicalPath != null ) {
304                             // Only cache if we got a good value
305                             cacheInfo.MapPathResult = physicalPath;
306                             cacheInfo.Evaluated = true;
307                         }
308                     }
309                 }
310             }
311
312             // Throw an exception if required
313             if (cacheInfo.CachedException != null) {
314                 throw cacheInfo.CachedException;
315             }
316
317             return MatchResult(originalPath, cacheInfo.MapPathResult);
318         }
319
320         private string MapPathActual(string siteID, VirtualPath path) {
321             string appID = GetRootAppIDFromSiteID(siteID);
322             string physicalPath = MBMapPath(appID, path.VirtualPathString);
323             return physicalPath;
324         }
325
326         private string GetRootAppIDFromSiteID(string siteId) {
327             return LMW3SVC_PREFIX + siteId + ROOT_SUFFIX;
328         }
329
330         private string GetAboPath(string siteID, string path) {
331             string rootAppID = GetRootAppIDFromSiteID(siteID);
332             string aboPath = rootAppID + FixupPathSlash(path);
333             return aboPath;
334         }
335
336         private string FixupPathSlash(string path) {
337             if (path == null) {
338                 return null;
339             }
340
341             int l = path.Length;
342             if (l == 0 || path[l-1] != '/') {
343                 return path;
344             }
345
346             return path.Substring(0, l-1);
347         }
348
349         //
350         // Metabase access functions
351         //
352         private bool MBGetSiteNameFromSiteID(string siteID, out string siteName) {
353             string appID = GetRootAppIDFromSiteID(siteID);
354             StringBuilder sb = new StringBuilder(BUFSIZE);
355             int r = UnsafeNativeMethods.IsapiAppHostGetSiteName(appID, sb, sb.Capacity);
356             if (r == 1) {
357                 siteName = sb.ToString();
358                 return true;
359             }
360             else {
361                 siteName = String.Empty;
362                 return false;
363             }
364         }
365
366         private bool MBGetSiteIDFromSiteName(string siteName, out string siteID) {
367             StringBuilder sb = new StringBuilder(BUFSIZE);
368             int r = UnsafeNativeMethods.IsapiAppHostGetSiteId(siteName, sb, sb.Capacity);
369             if (r == 1) {
370                 siteID = sb.ToString();
371                 return true;
372             }
373             else {
374                 siteID = String.Empty;
375                 return false;
376             }
377         }
378
379
380         private string MBMapPath(string appID, string path) {
381             // keep growing the buffer to support paths longer than MAX_PATH
382             int bufSize = BUFSIZE;
383             StringBuilder sb;
384             int r;
385
386             for (;;) {
387                 sb = new StringBuilder(bufSize);
388                 r = UnsafeNativeMethods.IsapiAppHostMapPath(appID, path, sb, sb.Capacity);
389                 Debug.Trace("MapPath", "IsapiAppHostMapPath(" + path + ") returns " + r);
390
391                 if (r == -2) {
392                     // insufficient buffer
393                     bufSize *= 2;
394                 }
395                 else {
396                     break;
397                 }
398             }
399
400             if (r == -1) {
401                 // special case access denied error
402                 throw new HostingEnvironmentException(
403                     SR.GetString(SR.Cannot_access_mappath_title),
404                     SR.GetString(SR.Cannot_access_mappath_details));
405             }
406
407             string physicalPath;
408             if (r == 1) {
409                 physicalPath = sb.ToString();
410             }
411             else {
412                 physicalPath = null;
413             }
414
415             return physicalPath;
416         }
417
418         private string[] MBGetVirtualSubdirs(string aboPath, bool inApp) {
419             StringBuilder sb = new StringBuilder(BUFSIZE);
420             int index = 0;
421             ArrayList list = new ArrayList();
422             for (;;) {
423                 sb.Length = 0;
424                 int r = UnsafeNativeMethods.IsapiAppHostGetNextVirtualSubdir(aboPath, inApp, ref index, sb, sb.Capacity);
425                 if (r == 0)
426                     break;
427
428                 string subdir = sb.ToString();
429                 list.Add(subdir);
430             }
431
432             string[] subdirs = new string[list.Count];
433             list.CopyTo(subdirs);
434             return subdirs;
435         }
436
437         private bool MBGetUncUser(string aboPath, out string username, out string password) {
438             StringBuilder usr = new StringBuilder(BUFSIZE);
439             StringBuilder pwd = new StringBuilder(BUFSIZE);
440             int r = UnsafeNativeMethods.IsapiAppHostGetUncUser(aboPath, usr, usr.Capacity, pwd, pwd.Capacity);
441             if (r == 1) {
442                 username = usr.ToString();
443                 password = pwd.ToString();
444                 return true;
445             }
446             else {
447                 username = null;
448                 password = null;
449                 return false;
450             }
451         }
452
453         private int MBGetW3WPMemoryLimitInKB() {
454             return UnsafeNativeMethods.GetW3WPMemoryLimitInKB();
455         }
456
457         private string MBGetAppPath(string aboPath) {
458             StringBuilder buf = new StringBuilder(aboPath.Length + 1);
459             int r = UnsafeNativeMethods.IsapiAppHostGetAppPath(aboPath, buf, buf.Capacity);
460             string appAboPath;
461             if (r == 1) {
462                 appAboPath = buf.ToString();
463             }
464             else {
465                 appAboPath = null;
466             }
467
468             return appAboPath;
469         }
470     }
471 }
472