Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web.Services / System / Web / Services / Discovery / DynamicVirtualDiscoSearcher.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DynamicVirtualDiscoSearcher.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.Services.Discovery {
8     using System;
9     using System.IO;
10     using System.Collections;
11     using System.Diagnostics;
12     using System.Text;
13     using System.DirectoryServices;
14     using System.ComponentModel;
15     using System.Globalization;
16     using System.Threading;    
17     using System.Web.Services.Diagnostics;
18
19     /// <include file='doc\DynamicVirtualDiscoSearcher.uex' path='docs/doc[@for="DynamicVirtualDiscoSearcher"]/*' />
20     /// <devdoc>
21     /// Does a recursive search of virtual subdirectories to find stuff to
22     /// make a disco file from. *.disco files (or whatever the PrimarySearchPattern is) are
23     /// treated as end-points - recursion stops where they are found.
24     /// </devdoc>
25     internal class DynamicVirtualDiscoSearcher : DynamicDiscoSearcher {
26
27         private string rootPathAsdi; // ADSI search root path with prefix
28         private string entryPathPrefix;
29
30         private string startDir;
31
32 // If we could get an event back from IIS Admin Object that some directory is addred/removed/renamed
33 // then the following memeber should become static, so we get 5-10 _times_ performace gain on
34 // processing .vsdisco in the Web Root
35 // !!SEE ALSO!! CleanupCache method
36         private /*static*/ Hashtable webApps = new Hashtable();
37         private Hashtable Adsi = new Hashtable();
38
39
40         // -------------------------------------------------------------------------------
41         internal DynamicVirtualDiscoSearcher(string startDir, string[] excludedUrls, string rootUrl) :
42                 base(excludedUrls)
43         {
44             origUrl = rootUrl;
45             entryPathPrefix = GetWebServerForUrl( rootUrl ) + "/ROOT";
46
47             this.startDir = startDir;
48
49             string localPath = (new System.Uri(rootUrl)).LocalPath;
50             if ( localPath.Equals("/") ) localPath = "";     // empty local path should be ""
51             rootPathAsdi = entryPathPrefix + localPath;
52         }
53
54         // -------------------------------------------------------------------------------
55         /// <include file='doc\DynamicVirtualDiscoSearcher.uex' path='docs/doc[@for="DynamicVirtualDiscoSearcher.Search"]/*' />
56         /// <devdoc>
57         /// Main function. Searches dir recursively for primary (.vsdisco) and seconary (.asmx) files.
58         /// </devdoc>
59         internal override void Search(string fileToSkipAtBegin) {
60             SearchInit(fileToSkipAtBegin);
61             ScanDirectory( rootPathAsdi );
62             CleanupCache();
63         }
64
65         // -------------------------------------------------------------------------------
66         // Look in virtual subdirectories.
67         protected override void SearchSubDirectories(string nameAdsiDir) {
68
69             if ( CompModSwitches.DynamicDiscoverySearcher.TraceVerbose ) Debug.WriteLine( "DynamicVirtualDiscoSearcher.SearchSubDirectories(): nameAdsiDir=" + nameAdsiDir);
70
71             DirectoryEntry vdir = (DirectoryEntry)Adsi[nameAdsiDir];    //may be already bound
72             if (vdir == null) {
73                 if ( !DirectoryEntry.Exists(nameAdsiDir) )
74                     return;
75                 vdir = new DirectoryEntry(nameAdsiDir);
76                 Adsi[nameAdsiDir] = vdir;
77             }
78
79             foreach (DirectoryEntry obj in vdir.Children) {
80                 DirectoryEntry child = (DirectoryEntry)Adsi[obj.Path];
81                 if (child == null) {
82                     child = obj;
83                     Adsi[obj.Path] = obj;
84                 } else {
85                     obj.Dispose();
86                 }
87                 AppSettings settings = GetAppSettings(child);
88                 if (settings != null) {
89                     ScanDirectory(child.Path);                      //go down ADSI path
90                 }
91             }
92
93         }
94
95         // -------------------------------------------------------------------------------
96         protected override DirectoryInfo GetPhysicalDir(string dir ) {
97             DirectoryEntry vdir = (DirectoryEntry)Adsi[dir];
98             if (vdir == null) {
99                 if (!DirectoryEntry.Exists(dir) ) {
100                     return null;
101                 }
102                 vdir = new DirectoryEntry(dir);
103                 Adsi[dir] = vdir;
104             }
105             try {
106                 DirectoryInfo directory = null;
107                 AppSettings settings = GetAppSettings(vdir);
108                 if (settings == null) {
109                     return null;
110                 }
111                 if (settings.VPath == null) {                   //SchemaClassName == "IIsWebDirectory"
112                     //NOTE This assumes there was a known physical directory
113                     //corresponding to a parent WebDirectory.
114                     //And incoming 'dir' is a child of that parent.
115                     if ( !dir.StartsWith(rootPathAsdi, StringComparison.Ordinal) ) {
116                         throw new ArgumentException(Res.GetString(Res.WebVirtualDisoRoot, dir, rootPathAsdi), "dir");
117                     }
118                     string physicalDir = dir.Substring(rootPathAsdi.Length);
119                     physicalDir = physicalDir.Replace('/', '\\'); //it always begins with '/' or is empty
120                     directory = new DirectoryInfo(startDir + physicalDir);
121
122                 }
123                 else {
124                     directory = new DirectoryInfo(settings.VPath); //SchemaClassName == "IIsWebVirtualDir
125                 }
126
127                 if ( directory.Exists )
128                     return directory;
129             }
130             catch (Exception e) {
131                 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
132                     throw;
133                 }
134                 if ( CompModSwitches.DynamicDiscoverySearcher.TraceVerbose ) Debug.WriteLine( "+++ DynamicVirtualDiscoSearcher.GetPhysicalDir(): dir=" + dir + " Exception=" + e.ToString() );
135                 if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Warning, this, "GetPhysicalDir", e);
136                 return null;
137             }
138             return null;
139         }
140
141
142         // -------------------------------------------------------------------------------
143         // Calculate root ADSI virtual directory name (func by 'Microsoft').
144         private string GetWebServerForUrl(string url) {
145             Uri uri = new Uri(url);
146             DirectoryEntry w3Service = new DirectoryEntry("IIS://" + uri.Host + "/W3SVC");
147
148             foreach (DirectoryEntry obj in w3Service.Children) {
149                 DirectoryEntry site = (DirectoryEntry)Adsi[obj.Path];           //may be already bound
150                 if (site == null) {
151                     site = obj;
152                     Adsi[obj.Path] = obj;
153                 }
154                 else {
155                     obj.Dispose();
156                 }
157                 AppSettings settings = GetAppSettings(site);
158
159                 if (settings == null || settings.Bindings == null) {            //SchemaClassName != "IIsWebServer"
160                     continue;
161                 }
162
163                 foreach (string bindingsEntry in settings.Bindings) {
164                     if ( CompModSwitches.DynamicDiscoverySearcher.TraceVerbose ) Debug.WriteLine("GetWebServerForUrl() bindingsEntry=" + bindingsEntry);
165                     string[] bindings = bindingsEntry.Split(':');
166                     string ip = bindings[0];
167                     string port = bindings[1];
168                     string hostname = bindings[2];
169
170                     if (Convert.ToInt32(port, CultureInfo.InvariantCulture) != uri.Port)
171                         continue;
172
173                     if (uri.HostNameType == UriHostNameType.Dns) {
174                         if (hostname.Length == 0 || string.Compare(hostname, uri.Host, StringComparison.OrdinalIgnoreCase) == 0)
175                             return site.Path;
176                         }
177                     else {
178                         if (ip.Length == 0 || string.Compare(ip, uri.Host, StringComparison.OrdinalIgnoreCase) == 0)
179                             return site.Path;
180                     }
181                 }
182             }
183             return null;
184         }
185
186         // -------------------------------------------------------------------------------
187         // Makes result URL found file path from diectory name and short file name.
188         protected override string MakeResultPath(string dirName, string fileName) {
189             string res = origUrl
190                    + dirName.Substring(rootPathAsdi.Length, dirName.Length - rootPathAsdi.Length)
191                    + '/' + fileName;
192             return res;
193         }
194
195         // -------------------------------------------------------------------------------
196         // Makes exclusion path absolute for quick comparision on search.
197         protected override string MakeAbsExcludedPath(string pathRelativ) {
198             return rootPathAsdi + '/' + pathRelativ.Replace('\\', '/');
199         }
200
201         // -------------------------------------------------------------------------------
202         protected override bool IsVirtualSearch {
203             get { return true; }
204         }
205
206         private AppSettings GetAppSettings(DirectoryEntry entry) {
207             string key = entry.Path;                   //this is fast since does not cause bind()
208             AppSettings result = null;
209
210             object obj = webApps[key];
211
212             if (obj == null) {
213                 // We provie a write lock while Hashtable supports multiple readers under single writer
214                 lock (webApps) {
215                     obj = webApps[key];
216                     if (obj == null) {                          //make sure other thread not taken care of
217                         result = new AppSettings(entry);        //that consumes a 50-2000 ms
218                         webApps[key] = result;
219                     }
220                 }
221             }
222             else {
223                 result = (AppSettings)obj;
224             }
225             return result.AccessRead ? result : null;         //ignore denied object on upper level
226         }
227
228         private void CleanupCache() {
229             //Destroy system resources excplicitly since the destructor is called sometime late
230             foreach (DictionaryEntry obj in Adsi) {
231                 ((DirectoryEntry)(obj.Value)).Dispose();
232             }
233             rootPathAsdi = null;
234             entryPathPrefix = null;
235             startDir = null;
236
237             Adsi     = null;
238 //REMOVE NEXT LINE IF the member webApps has turned into static (see webApps declaration line)
239             webApps = null;
240         }
241
242         private class AppSettings {
243             internal readonly bool AccessRead = false; // if false the caller will ignore the object
244             internal readonly string[] Bindings = null;  // the field is only for WebServers
245             internal readonly string VPath = null;  // the filed is only for VirtualDirs
246
247             internal AppSettings(DirectoryEntry entry) {
248
249                 string schema = entry.SchemaClassName;
250                 AccessRead = true;
251
252                 if (schema == "IIsWebVirtualDir" || schema == "IIsWebDirectory") {
253                     if (!(bool)(entry.Properties["AccessRead"][0])) {
254                         AccessRead = false;
255                         return;
256                     }
257                     if (schema == "IIsWebVirtualDir") {
258                         VPath = (string) (entry.Properties["Path"][0]);
259                     }
260                 }
261                 else if (schema == "IIsWebServer") {
262                     Bindings = new string[entry.Properties["ServerBindings"].Count];
263                     for (int i = 0; i < Bindings.Length; ++i) {
264                         Bindings[i] = (string) (entry.Properties["ServerBindings"][i]);
265                     }
266                 }
267                 else {
268                     //schema is not recognized add to the cache but never look at the object
269                     AccessRead = false;
270                 }
271             }
272         }
273
274     }
275 }