[sgen] Fix logging of major heap size with concurrent sweep
[mono.git] / mcs / class / System.Web / System.Web.Handlers / AssemblyResourceLoader.cs
1 //
2 // System.Web.Handlers.AssemblyResourceLoader
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Marek Habersack <grendel@twistedcode.net>
7 //
8 // (C) 2003 Ben Maurer
9 // (C) 2010 Novell, Inc (http://novell.com/)
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.Web.UI;
33 using System.Globalization;
34 using System.Reflection;
35 using System.IO;
36 using System.Resources;
37 using System.Collections;
38 using System.Collections.Generic;
39 using System.Collections.Specialized;
40 using System.Security.Cryptography;
41 using System.Text;
42 using System.Text.RegularExpressions;
43 using System.Threading;
44 using System.Web.Configuration;
45 using System.Web.Util;
46
47 namespace System.Web.Handlers
48 {
49 #if SYSTEM_WEB_EXTENSIONS
50         partial class ScriptResourceHandler
51         {
52                 const string HandlerFileName = "ScriptResource.axd";
53                 static Assembly currAsm = typeof (ScriptResourceHandler).Assembly;
54 #else
55         public sealed class AssemblyResourceLoader : IHttpHandler
56         {
57                 const string HandlerFileName = "WebResource.axd";
58                 static Assembly currAsm = typeof (AssemblyResourceLoader).Assembly;
59 #endif
60                 const char QueryParamSeparator = '&';
61
62                 static readonly Dictionary <string, AssemblyEmbeddedResources> _embeddedResources = new Dictionary <string, AssemblyEmbeddedResources> (StringComparer.Ordinal);
63                 static readonly ReaderWriterLockSlim _embeddedResourcesLock = new ReaderWriterLockSlim ();
64                 static readonly ReaderWriterLockSlim _stringHashCacheLock = new ReaderWriterLockSlim ();
65                 static readonly Dictionary <string, string> stringHashCache = new Dictionary <string, string> (StringComparer.Ordinal);
66
67                 [ThreadStatic]
68                 static KeyedHashAlgorithm hashAlg;
69                 static bool canReuseHashAlg = true;
70
71                 static KeyedHashAlgorithm ReusableHashAlgorithm {
72                         get {
73                                 if (!canReuseHashAlg)
74                                         return null;
75
76                                 if (hashAlg == null) {                          
77                                         MachineKeySection mks = MachineKeySection.Config;
78                                         hashAlg = MachineKeySectionUtils.GetValidationAlgorithm (mks);
79                                         if (!hashAlg.CanReuseTransform) {
80                                                 canReuseHashAlg = false;
81                                                 hashAlg = null;
82                                         }
83                                 }
84
85                                 if (hashAlg != null)
86                                         hashAlg.Initialize ();
87
88                                 return hashAlg;
89                         }
90                 }
91                 
92                 static string GetStringHash (KeyedHashAlgorithm kha, string str)
93                 {
94                         if (String.IsNullOrEmpty (str))
95                                 return String.Empty;
96
97                         string result;
98                         try {
99                                 _stringHashCacheLock.EnterUpgradeableReadLock ();
100                                 if (stringHashCache.TryGetValue (str, out result))
101                                         return result;
102
103                                 try {
104                                         _stringHashCacheLock.EnterWriteLock ();
105                                         if (stringHashCache.TryGetValue (str, out result))
106                                                 return result;
107                                         
108                                         result = Convert.ToBase64String (kha.ComputeHash (Encoding.UTF8.GetBytes (str)));
109                                         stringHashCache.Add (str, result);
110                                 } finally {
111                                         _stringHashCacheLock.ExitWriteLock ();
112                                 }
113                         } finally {
114                                 _stringHashCacheLock.ExitUpgradeableReadLock ();
115                         }
116                         
117                         return result;
118                 }
119                 
120                 static void InitEmbeddedResourcesUrls (KeyedHashAlgorithm kha, Assembly assembly, string assemblyName, string assemblyHash, AssemblyEmbeddedResources entry)
121                 {
122                         WebResourceAttribute [] attrs = (WebResourceAttribute []) assembly.GetCustomAttributes (typeof (WebResourceAttribute), false);
123                         WebResourceAttribute attr;
124                         string apath = assembly.Location;
125                         for (int i = 0; i < attrs.Length; i++) {
126                                 attr = attrs [i];
127                                 string resourceName = attr.WebResource;
128                                 if (!String.IsNullOrEmpty (resourceName)) {
129                                         string resourceNameHash = GetStringHash (kha, resourceName);
130 #if SYSTEM_WEB_EXTENSIONS
131                                         bool debug = resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase);
132                                         string dbgTail = debug ? "d" : String.Empty;
133                                         string rkNoNotify = resourceNameHash + "f" + dbgTail;
134                                         string rkNotify = resourceNameHash + "t" + dbgTail;
135
136                                         if (!entry.Resources.ContainsKey (rkNoNotify)) {
137                                                 var er = new EmbeddedResource () {
138                                                         Name = resourceName,
139                                                         Attribute = attr, 
140                                                         Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNoNotify, debug, false, true)
141                                                 };
142                                                 
143                                                 entry.Resources.Add (rkNoNotify, er);
144                                         }
145                                         
146                                         if (!entry.Resources.ContainsKey (rkNotify)) {
147                                                 var er = new EmbeddedResource () {
148                                                         Name = resourceName,
149                                                         Attribute = attr, 
150                                                         Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNotify, debug, true, true)
151                                                 };
152                                                 
153                                                 entry.Resources.Add (rkNotify, er);
154                                         }
155 #else
156                                         if (!entry.Resources.ContainsKey (resourceNameHash)) {
157                                                 var er = new EmbeddedResource () {
158                                                         Name = resourceName,
159                                                         Attribute = attr, 
160                                                         Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, resourceNameHash, false, false, true)
161                                                 };
162                                                 entry.Resources.Add (resourceNameHash, er);
163                                         }
164 #endif
165                                 }
166                         }
167                 }
168
169 #if !SYSTEM_WEB_EXTENSIONS
170                 internal static string GetResourceUrl (Type type, string resourceName)
171                 {
172                         return GetResourceUrl (type.Assembly, resourceName, false);
173                 }
174 #endif
175                 static EmbeddedResource DecryptAssemblyResource (string val, out AssemblyEmbeddedResources entry)
176                 {
177                         entry = null;
178                         
179                         string[] parts = val.Split ('_');
180                         if (parts.Length != 3)
181                                 return null;
182
183                         string asmNameHash = parts [0];
184                         string resNameHash = parts [1];
185                         try {
186                                 _embeddedResourcesLock.EnterReadLock ();
187                                 if (!_embeddedResources.TryGetValue (asmNameHash, out entry) || entry == null)
188                                         return null;
189                                 
190                                 EmbeddedResource res;
191                                 if (!entry.Resources.TryGetValue (resNameHash, out res) || res == null) {
192 #if SYSTEM_WEB_EXTENSIONS
193                                         bool debug = parts [2] == "t";
194                                         if (!debug)
195                                                 return null;
196
197                                         if (!entry.Resources.TryGetValue (resNameHash.Substring (0, resNameHash.Length - 1), out res))
198                                                 return null;
199 #else
200                                         return null;
201 #endif
202                                 }
203                                 
204                                 return res;
205                         } finally {
206                                 _embeddedResourcesLock.ExitReadLock ();
207                         }
208                 }
209
210                 static void GetAssemblyNameAndHashes (KeyedHashAlgorithm kha, Assembly assembly, string resourceName, out string assemblyName, out string assemblyNameHash, out string resourceNameHash)
211                 {
212                         assemblyName = assembly == currAsm ? "s" : assembly.GetName ().FullName;
213                         assemblyNameHash = GetStringHash (kha, assemblyName);
214                         resourceNameHash = GetStringHash (kha, resourceName);
215                 }
216                 
217                 // MUST be called with the _embeddedResourcesLock taken in the upgradeable read lock mode
218                 static AssemblyEmbeddedResources GetAssemblyEmbeddedResource (KeyedHashAlgorithm kha, Assembly assembly, string assemblyNameHash, string assemblyName)
219                 {
220                         AssemblyEmbeddedResources entry;
221                         
222                         if (!_embeddedResources.TryGetValue (assemblyNameHash, out entry) || entry == null) {
223                                 try {
224                                         _embeddedResourcesLock.EnterWriteLock ();
225                                         entry = new AssemblyEmbeddedResources () {
226                                                         AssemblyName = assemblyName
227                                                                 };
228                                         InitEmbeddedResourcesUrls (kha, assembly, assemblyName, assemblyNameHash, entry);
229                                         _embeddedResources.Add (assemblyNameHash, entry);
230                                 } finally {
231                                         _embeddedResourcesLock.ExitWriteLock ();
232                                 }
233                         }
234
235                         return entry;
236                 }
237                 
238                 internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
239                 {
240                         if (assembly == null)
241                                 return String.Empty;
242
243                         KeyedHashAlgorithm kha = ReusableHashAlgorithm;
244                         if (kha != null) {
245                                 return GetResourceUrl (kha, assembly, resourceName, notifyScriptLoaded);
246                         } else {
247                                 MachineKeySection mks = MachineKeySection.Config;
248                                 using (kha = MachineKeySectionUtils.GetValidationAlgorithm (mks)) {
249                                         kha.Key = MachineKeySectionUtils.GetValidationKey (mks);
250                                         return GetResourceUrl (kha, assembly, resourceName, notifyScriptLoaded);
251                                 }
252                         }
253                 }
254
255                 static string GetResourceUrl (KeyedHashAlgorithm kha, Assembly assembly, string resourceName, bool notifyScriptLoaded)
256                 {
257                         string assemblyName;
258                         string assemblyNameHash;
259                         string resourceNameHash;
260
261                         GetAssemblyNameAndHashes (kha, assembly, resourceName, out assemblyName, out assemblyNameHash, out resourceNameHash);
262                         bool debug = false;
263                         string url;
264                         AssemblyEmbeddedResources entry;
265                         bool includeTimeStamp = true;
266
267                         try {
268                                 _embeddedResourcesLock.EnterUpgradeableReadLock ();
269                                 entry = GetAssemblyEmbeddedResource (kha, assembly, assemblyNameHash, assemblyName);
270                                 string lookupKey;
271 #if SYSTEM_WEB_EXTENSIONS
272                                 debug = resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase);
273                                 string dbgTail = debug ? "d" : String.Empty;
274                                 lookupKey = resourceNameHash + (notifyScriptLoaded ? "t" : "f") + dbgTail;
275                                 CheckIfResourceIsCompositeScript (resourceName, ref includeTimeStamp);
276 #else
277                                 lookupKey = resourceNameHash;
278 #endif
279                                 EmbeddedResource res;
280                                 if (entry.Resources.TryGetValue (lookupKey, out res) && res != null)
281                                         url = res.Url;
282                                 else {
283 #if SYSTEM_WEB_EXTENSIONS
284                                         if (debug) {
285                                                 resourceNameHash = GetStringHash (kha, resourceName.Substring (0, resourceName.Length - 9) + ".js");
286                                                 lookupKey = resourceNameHash + (notifyScriptLoaded ? "t" : "f");
287                                         
288                                                 if (entry.Resources.TryGetValue (lookupKey, out res) && res != null)
289                                                         url = res.Url;
290                                                 else
291                                                         url = null;
292                                         } else
293 #endif
294                                                 url = null;
295                                 }
296                         } finally {
297                                 _embeddedResourcesLock.ExitUpgradeableReadLock ();
298                         }
299
300                         if (url == null)
301                                 url = CreateResourceUrl (kha, assemblyName, assemblyNameHash, assembly.Location, resourceNameHash, debug, notifyScriptLoaded, includeTimeStamp);
302                         
303                         return url;
304                 }
305                 
306                 static string CreateResourceUrl (KeyedHashAlgorithm kha, string assemblyName, string assemblyNameHash, string assemblyPath, string resourceNameHash, bool debug,
307                                                  bool notifyScriptLoaded, bool includeTimeStamp)
308                 {
309                         string atime = String.Empty;
310                         string extra = String.Empty;
311 #if SYSTEM_WEB_EXTENSIONS
312                         extra = QueryParamSeparator + "n=" + (notifyScriptLoaded ? "t" : "f");
313 #endif
314
315                         if (includeTimeStamp) {
316                                 if (!String.IsNullOrEmpty (assemblyPath) && File.Exists (assemblyPath))
317                                         atime = QueryParamSeparator + "t=" + File.GetLastWriteTimeUtc (assemblyPath).Ticks;
318                                 else
319                                         atime = QueryParamSeparator + "t=" + DateTime.UtcNow.Ticks;
320                         }
321                         string d = HttpUtility.UrlEncode (assemblyNameHash + "_" + resourceNameHash +  (debug ? "_t" : "_f"));
322                         string href = HandlerFileName + "?d=" + d + atime + extra;
323                         HttpContext ctx = HttpContext.Current;
324                         HttpRequest req = ctx != null ? ctx.Request : null;
325                         if (req != null) {
326                                 string appPath = VirtualPathUtility.AppendTrailingSlash (req.ApplicationPath);
327                                 href = appPath + href;
328                         }
329                         
330                         return href;
331                 }
332
333                 bool HasIfModifiedSince (HttpRequest request, out DateTime modified)
334                 {
335                         string modif_since = request.Headers ["If-Modified-Since"];
336                         if (String.IsNullOrEmpty (modif_since)) {
337                                 modified = DateTime.MinValue;
338                                 return false;
339                         }
340
341                         try {
342                                 if (DateTime.TryParseExact (modif_since, "r", null, 0, out modified))
343                                         return true;
344                         } catch {
345                                 modified = DateTime.MinValue;
346                         }
347
348                         return false;
349                 }
350
351                 void RespondWithNotModified (HttpContext context)
352                 {
353                         HttpResponse response = context.Response;
354                         response.Clear ();
355                         response.StatusCode = 304;
356                         response.ContentType = null;
357                         context.ApplicationInstance.CompleteRequest ();
358                 }
359                 
360                 void SendEmbeddedResource (HttpContext context, out EmbeddedResource res, out Assembly assembly)
361                 {
362                         HttpRequest request = context.Request;
363                         NameValueCollection queryString = request.QueryString;
364                         
365                         // val is URL-encoded, which means every + has been replaced with ' ', we
366                         // need to revert that or the base64 conversion will fail.
367                         string d = queryString ["d"];
368                         if (!String.IsNullOrEmpty (d))
369                                 d = d.Replace (' ', '+');
370
371                         AssemblyEmbeddedResources entry;
372                         res = DecryptAssemblyResource (d, out entry);
373                         WebResourceAttribute wra = res != null ? res.Attribute : null;
374                         if (wra == null)
375                                 throw new HttpException (404, "Resource not found");
376
377                         if (entry.AssemblyName == "s")
378                                 assembly = currAsm;
379                         else
380                                 assembly = Assembly.Load (entry.AssemblyName);
381                         
382                         DateTime modified;
383                         if (HasIfModifiedSince (request, out modified)) {
384                                 if (File.GetLastWriteTimeUtc (assembly.Location) <= modified) {
385                                         RespondWithNotModified (context);
386                                         return;
387                                 }
388                         }
389
390                         HttpResponse response = context.Response;
391                         response.ContentType = wra.ContentType;
392
393                         DateTime utcnow = DateTime.UtcNow;
394                         response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));
395                         response.ExpiresAbsolute = utcnow.AddYears (1);
396                         response.CacheControl = "public";
397
398                         Stream s = assembly.GetManifestResourceStream (res.Name);
399                         if (s == null)
400                                 throw new HttpException (404, "Resource " + res.Name + " not found");
401
402                         if (wra.PerformSubstitution) {
403                                 using (StreamReader r = new StreamReader (s)) {
404                                         TextWriter w = response.Output;
405                                         new PerformSubstitutionHelper (assembly).PerformSubstitution (r, w);
406                                 }
407                         } else if (response.OutputStream is HttpResponseStream) {
408                                 UnmanagedMemoryStream st = (UnmanagedMemoryStream) s;
409                                 HttpResponseStream hstream = (HttpResponseStream) response.OutputStream;
410                                 unsafe {
411                                         hstream.WritePtr (new IntPtr (st.PositionPointer), (int) st.Length);
412                                 }
413                         } else {
414                                 byte [] buf = new byte [1024];
415                                 Stream output = response.OutputStream;
416                                 int c;
417                                 do {
418                                         c = s.Read (buf, 0, 1024);
419                                         output.Write (buf, 0, c);
420                                 } while (c > 0);
421                         }
422                 }
423                 
424 #if !SYSTEM_WEB_EXTENSIONS
425                 void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
426                 {
427                         EmbeddedResource res;
428                         Assembly assembly;
429                         
430                         SendEmbeddedResource (context, out res, out assembly);
431                 }
432 #endif
433                 sealed class PerformSubstitutionHelper
434                 {
435                         readonly Assembly _assembly;
436                         static readonly Regex _regex = new Regex (@"\<%=[ ]*WebResource[ ]*\([ ]*""([^""]+)""[ ]*\)[ ]*%\>");
437
438                         public PerformSubstitutionHelper (Assembly assembly) {
439                                 _assembly = assembly;
440                         }
441
442                         public void PerformSubstitution (TextReader reader, TextWriter writer) {
443                                 string line = reader.ReadLine ();
444                                 while (line != null) {
445                                         if (line.Length > 0 && _regex.IsMatch (line))
446                                                 line = _regex.Replace (line, new MatchEvaluator (PerformSubstitutionReplace));
447                                         writer.WriteLine (line);
448                                         line = reader.ReadLine ();
449                                 }
450                         }
451
452                         string PerformSubstitutionReplace (Match m) {
453                                 string resourceName = m.Groups [1].Value;
454 #if SYSTEM_WEB_EXTENSIONS
455                                 return ScriptResourceHandler.GetResourceUrl (_assembly, resourceName, false);
456 #else
457                                 return AssemblyResourceLoader.GetResourceUrl (_assembly, resourceName, false);
458 #endif
459                         }
460                 }
461                 
462 #if !SYSTEM_WEB_EXTENSIONS
463                 bool System.Web.IHttpHandler.IsReusable { get { return true; } }
464 #endif
465                 sealed class EmbeddedResource
466                 {
467                         public string Name;
468                         public string Url;
469                         public WebResourceAttribute Attribute;
470                 }
471                 
472                 sealed class AssemblyEmbeddedResources
473                 {
474                         public string AssemblyName = String.Empty;
475                         public Dictionary <string, EmbeddedResource> Resources = new Dictionary <string, EmbeddedResource> (StringComparer.Ordinal);
476                 }               
477         }
478 }
479