[asp.net] Don't throw NREX when a cache item to be removed is null
[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.Security.Cryptography;
40 using System.Text;
41 using System.Text.RegularExpressions;
42 using System.Threading;
43 using System.Web.Configuration;
44 using System.Web.Util;
45
46 namespace System.Web.Handlers
47 {
48 #if SYSTEM_WEB_EXTENSIONS
49         partial class ScriptResourceHandler
50         {
51                 const string HandlerFileName = "ScriptResource.axd";
52                 static Assembly currAsm = typeof (ScriptResourceHandler).Assembly;
53 #else
54         #if NET_2_0
55         public sealed
56         #else
57         internal // since this is in the .config file, we need to support it, since we dont have versoned support.
58         #endif
59         class AssemblyResourceLoader : IHttpHandler
60         {
61                 const string HandlerFileName = "WebResource.axd";
62                 static Assembly currAsm = typeof (AssemblyResourceLoader).Assembly;
63 #endif
64                 const char QueryParamSeparator = '&';
65
66                 static readonly Dictionary <string, AssemblyEmbeddedResources> _embeddedResources = new Dictionary <string, AssemblyEmbeddedResources> (StringComparer.Ordinal);
67                 static readonly ReaderWriterLockSlim _embeddedResourcesLock = new ReaderWriterLockSlim ();
68                 static readonly ReaderWriterLockSlim _stringHashCacheLock = new ReaderWriterLockSlim ();
69                 static readonly Dictionary <string, string> stringHashCache = new Dictionary <string, string> (StringComparer.Ordinal);
70
71                 [ThreadStatic]
72                 static KeyedHashAlgorithm hashAlg;
73                 static bool canReuseHashAlg = true;
74
75                 static KeyedHashAlgorithm ReusableHashAlgorithm {
76                         get {
77                                 if (!canReuseHashAlg)
78                                         return null;
79
80                                 if (hashAlg == null) {                          
81                                         MachineKeySection mks = MachineKeySection.Config;
82                                         hashAlg = MachineKeySectionUtils.GetValidationAlgorithm (mks);
83                                         if (!hashAlg.CanReuseTransform) {
84                                                 canReuseHashAlg = false;
85                                                 hashAlg = null;
86                                         }
87                                 }
88
89                                 if (hashAlg != null)
90                                         hashAlg.Initialize ();
91
92                                 return hashAlg;
93                         }
94                 }
95                 
96                 static string GetStringHash (KeyedHashAlgorithm kha, string str)
97                 {
98                         if (String.IsNullOrEmpty (str))
99                                 return String.Empty;
100
101                         string result;
102                         try {
103                                 _stringHashCacheLock.EnterUpgradeableReadLock ();
104                                 if (stringHashCache.TryGetValue (str, out result))
105                                         return result;
106
107                                 try {
108                                         _stringHashCacheLock.EnterWriteLock ();
109                                         if (stringHashCache.TryGetValue (str, out result))
110                                                 return result;
111                                         
112                                         result = Convert.ToBase64String (kha.ComputeHash (Encoding.UTF8.GetBytes (str)));
113                                         stringHashCache.Add (str, result);
114                                 } finally {
115                                         _stringHashCacheLock.ExitWriteLock ();
116                                 }
117                         } finally {
118                                 _stringHashCacheLock.ExitUpgradeableReadLock ();
119                         }
120                         
121                         return result;
122                 }
123                 
124                 static void InitEmbeddedResourcesUrls (KeyedHashAlgorithm kha, Assembly assembly, string assemblyName, string assemblyHash, AssemblyEmbeddedResources entry)
125                 {
126                         WebResourceAttribute [] attrs = (WebResourceAttribute []) assembly.GetCustomAttributes (typeof (WebResourceAttribute), false);
127                         WebResourceAttribute attr;
128                         string apath = assembly.Location;
129                         for (int i = 0; i < attrs.Length; i++) {
130                                 attr = attrs [i];
131                                 string resourceName = attr.WebResource;
132                                 if (!String.IsNullOrEmpty (resourceName)) {
133                                         string resourceNameHash = GetStringHash (kha, resourceName);
134 #if SYSTEM_WEB_EXTENSIONS
135                                         bool debug = resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase);
136                                         string dbgTail = debug ? "d" : String.Empty;
137                                         string rkNoNotify = resourceNameHash + "f" + dbgTail;
138                                         string rkNotify = resourceNameHash + "t" + dbgTail;
139
140                                         if (!entry.Resources.ContainsKey (rkNoNotify)) {
141                                                 var er = new EmbeddedResource () {
142                                                         Name = resourceName,
143                                                         Attribute = attr, 
144                                                         Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNoNotify, debug, false)
145                                                 };
146                                                 
147                                                 entry.Resources.Add (rkNoNotify, er);
148                                         }
149                                         
150                                         if (!entry.Resources.ContainsKey (rkNotify)) {
151                                                 var er = new EmbeddedResource () {
152                                                         Name = resourceName,
153                                                         Attribute = attr, 
154                                                         Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, rkNotify, debug, true)
155                                                 };
156                                                 
157                                                 entry.Resources.Add (rkNotify, er);
158                                         }
159 #else
160                                         if (!entry.Resources.ContainsKey (resourceNameHash)) {
161                                                 var er = new EmbeddedResource () {
162                                                         Name = resourceName,
163                                                         Attribute = attr, 
164                                                         Url = CreateResourceUrl (kha, assemblyName, assemblyHash, apath, resourceNameHash, false, false)
165                                                 };
166                                                 entry.Resources.Add (resourceNameHash, er);
167                                         }
168 #endif
169                                 }
170                         }
171                 }
172
173 #if !SYSTEM_WEB_EXTENSIONS
174                 internal static string GetResourceUrl (Type type, string resourceName)
175                 {
176                         return GetResourceUrl (type.Assembly, resourceName, false);
177                 }
178 #endif
179                 static EmbeddedResource DecryptAssemblyResource (string val, out AssemblyEmbeddedResources entry)
180                 {
181                         entry = null;
182                         
183                         string[] parts = val.Split ('_');
184                         if (parts.Length != 3)
185                                 return null;
186
187                         Encoding enc = Encoding.UTF8;
188                         string asmNameHash = parts [0];
189                         string resNameHash = parts [1];
190                         bool debug = parts [2] == "t";
191                         
192                         try {
193                                 _embeddedResourcesLock.EnterReadLock ();
194                                 if (!_embeddedResources.TryGetValue (asmNameHash, out entry) || entry == null)
195                                         return null;
196                                 
197                                 EmbeddedResource res;
198                                 if (!entry.Resources.TryGetValue (resNameHash, out res) || res == null) {
199 #if SYSTEM_WEB_EXTENSIONS
200                                         if (!debug)
201                                                 return null;
202
203                                         if (!entry.Resources.TryGetValue (resNameHash.Substring (0, resNameHash.Length - 1), out res))
204                                                 return null;
205 #else
206                                         return null;
207 #endif
208                                 }
209                                 
210                                 return res;
211                         } finally {
212                                 _embeddedResourcesLock.ExitReadLock ();
213                         }
214                 }
215
216                 internal static string GetResourceUrl (Assembly assembly, string resourceName, bool notifyScriptLoaded)
217                 {
218                         if (assembly == null)
219                                 return String.Empty;
220
221                         KeyedHashAlgorithm kha = ReusableHashAlgorithm;
222                         if (kha != null) {
223                                 return GetResourceUrl (kha, assembly, resourceName, notifyScriptLoaded);
224                         } else {
225                                 MachineKeySection mks = MachineKeySection.Config;
226                                 using (kha = MachineKeySectionUtils.GetValidationAlgorithm (mks)) {
227                                         kha.Key = MachineKeySectionUtils.GetValidationKey (mks);
228                                         return GetResourceUrl (kha, assembly, resourceName, notifyScriptLoaded);
229                                 }
230                         }
231                 }
232
233                 static string GetResourceUrl (KeyedHashAlgorithm kha, Assembly assembly, string resourceName, bool notifyScriptLoaded)
234                 {
235                         string assemblyName = assembly == currAsm ? "s" : assembly.GetName ().FullName;
236                         string assemblyNameHash = GetStringHash (kha, assemblyName);
237                         string resourceNameHash = GetStringHash (kha, resourceName);
238                         bool debug = false;
239                         string url;
240                         AssemblyEmbeddedResources entry;
241
242                         try {
243                                 _embeddedResourcesLock.EnterUpgradeableReadLock ();
244                                 if (!_embeddedResources.TryGetValue (assemblyNameHash, out entry) || entry == null) {
245                                         try {
246                                                 _embeddedResourcesLock.EnterWriteLock ();
247                                                 entry = new AssemblyEmbeddedResources () {
248                                                         AssemblyName = assemblyName
249                                                 };
250                                                 InitEmbeddedResourcesUrls (kha, assembly, assemblyName, assemblyNameHash, entry);
251                                                 _embeddedResources.Add (assemblyNameHash, entry);
252                                         } finally {
253                                                 _embeddedResourcesLock.ExitWriteLock ();
254                                         }
255                                 }
256                                 string lookupKey;
257 #if SYSTEM_WEB_EXTENSIONS
258                                 debug = resourceName.EndsWith (".debug.js", StringComparison.OrdinalIgnoreCase);
259                                 string dbgTail = debug ? "d" : String.Empty;
260                                 lookupKey = resourceNameHash + (notifyScriptLoaded ? "t" : "f") + dbgTail;
261 #else
262                                 lookupKey = resourceNameHash;
263 #endif
264                                 EmbeddedResource res;
265                                 if (entry.Resources.TryGetValue (lookupKey, out res) && res != null)
266                                         url = res.Url;
267                                 else {
268 #if SYSTEM_WEB_EXTENSIONS
269                                         if (debug) {
270                                                 resourceNameHash = GetStringHash (kha, resourceName.Substring (0, resourceName.Length - 9) + ".js");
271                                                 lookupKey = resourceNameHash + (notifyScriptLoaded ? "t" : "f");
272                                         
273                                                 if (entry.Resources.TryGetValue (lookupKey, out res) && res != null)
274                                                         url = res.Url;
275                                                 else
276                                                         url = null;
277                                         } else
278 #endif
279                                                 url = null;
280                                 }
281                         } finally {
282                                 _embeddedResourcesLock.ExitUpgradeableReadLock ();
283                         }
284
285                         if (url == null)
286                                 url = CreateResourceUrl (kha, assemblyName, assemblyNameHash, assembly.Location, resourceNameHash, debug, notifyScriptLoaded);
287                         
288                         return url;
289                 }
290                 
291                 static string CreateResourceUrl (KeyedHashAlgorithm kha, string assemblyName, string assemblyNameHash, string assemblyPath, string resourceNameHash, bool debug, bool notifyScriptLoaded)
292                 {
293                         string atime = String.Empty;
294                         string extra = String.Empty;
295 #if SYSTEM_WEB_EXTENSIONS
296                         extra = QueryParamSeparator + "n=" + (notifyScriptLoaded ? "t" : "f");
297 #endif
298
299 #if TARGET_JVM
300                         atime = QueryParamSeparator + "t=" + assemblyName.GetHashCode ();
301 #else
302                         if (!String.IsNullOrEmpty (assemblyPath) && File.Exists (assemblyPath))
303                                 atime = QueryParamSeparator + "t=" + File.GetLastWriteTimeUtc (assemblyPath).Ticks;
304                         else
305                                 atime = QueryParamSeparator + "t=" + DateTime.UtcNow.Ticks;
306 #endif
307                         string d = assemblyNameHash + "_" + resourceNameHash +  (debug ? "_t" : "_f");
308                         string href = HandlerFileName + "?d=" + d + atime + extra;
309                         HttpContext ctx = HttpContext.Current;
310                         HttpRequest req = ctx != null ? ctx.Request : null;
311                         if (req != null) {
312                                 string appPath = VirtualPathUtility.AppendTrailingSlash (req.ApplicationPath);
313                                 href = appPath + href;
314                         }
315                         
316                         return href;
317                 }
318
319 #if SYSTEM_WEB_EXTENSIONS
320                 protected virtual void ProcessRequest (HttpContext context)
321 #else
322                 void System.Web.IHttpHandler.ProcessRequest (HttpContext context)
323 #endif
324                 {
325                         HttpRequest request = context.Request;
326                         // val is URL-encoded, which means every + has been replaced with ' ', we
327                         // need to revert that or the base64 conversion will fail.
328                         string d = request.QueryString ["d"];
329                         if (!String.IsNullOrEmpty (d))
330                                 d = d.Replace (' ', '+');
331
332                         AssemblyEmbeddedResources entry;
333                         EmbeddedResource res = DecryptAssemblyResource (d, out entry);
334                         WebResourceAttribute wra = res != null ? res.Attribute : null;
335                         if (wra == null)
336                                 throw new HttpException (404, "Resource not found");
337                         
338                         Assembly assembly;
339                         if (entry.AssemblyName == "s")
340                                 assembly = currAsm;
341                         else
342                                 assembly = Assembly.Load (entry.AssemblyName);
343                         
344                         HttpResponse response = context.Response;
345                         string req_cache = request.Headers ["Cache-Control"];
346                         if (String.Compare (req_cache, "max-age=0", StringComparison.Ordinal) == 0) {
347                                 long atime;
348                                 if (Int64.TryParse (request.QueryString ["t"], out atime)) {
349                                         if (atime == File.GetLastWriteTimeUtc (assembly.Location).Ticks) {
350                                                 response.Clear ();
351                                                 response.StatusCode = 304;
352                                                 response.ContentType = null;
353                                                 response.CacheControl = "public"; // easier to set it to public as MS than remove it
354                                                 context.ApplicationInstance.CompleteRequest ();
355                                                 return;
356                                         }
357                                 }
358                         }
359                         string modif_since = request.Headers ["If-Modified-Since"];
360                         if (!String.IsNullOrEmpty (modif_since)) {
361                                 try {
362                                         DateTime modif;
363                                         if (DateTime.TryParseExact (modif_since, "r", null, 0, out modif)) {
364                                                 if (File.GetLastWriteTimeUtc (assembly.Location) <= modif) {
365                                                         response.Clear ();
366                                                         response.StatusCode = 304;
367                                                         response.ContentType = null;
368                                                         response.CacheControl = "public"; // easier to set it to public as MS than remove it
369                                                         context.ApplicationInstance.CompleteRequest ();
370                                                         return;
371                                                 }
372                                         }
373                                 } catch {}
374                         }
375
376                         response.ContentType = wra.ContentType;
377
378                         DateTime utcnow = DateTime.UtcNow;
379                         response.Headers.Add ("Last-Modified", utcnow.ToString ("r"));
380                         response.ExpiresAbsolute = utcnow.AddYears (1);
381                         response.CacheControl = "public";
382
383                         Stream s = assembly.GetManifestResourceStream (res.Name);
384                         if (s == null)
385                                 throw new HttpException (404, "Resource " + res.Name + " not found");
386
387                         if (wra.PerformSubstitution) {
388                                 using (StreamReader r = new StreamReader (s)) {
389                                         TextWriter w = response.Output;
390                                         new PerformSubstitutionHelper (assembly).PerformSubstitution (r, w);
391                                 }
392                         } else if (response.OutputStream is HttpResponseStream) {
393                                 UnmanagedMemoryStream st = (UnmanagedMemoryStream) s;
394                                 HttpResponseStream hstream = (HttpResponseStream) response.OutputStream;
395                                 unsafe {
396                                         hstream.WritePtr (new IntPtr (st.PositionPointer), (int) st.Length);
397                                 }
398                         } else {
399                                 byte [] buf = new byte [1024];
400                                 Stream output = response.OutputStream;
401                                 int c;
402                                 do {
403                                         c = s.Read (buf, 0, 1024);
404                                         output.Write (buf, 0, c);
405                                 } while (c > 0);
406                         }
407 #if SYSTEM_WEB_EXTENSIONS
408                         TextWriter writer = response.Output;
409                         foreach (ScriptResourceAttribute sra in assembly.GetCustomAttributes (typeof (ScriptResourceAttribute), false)) {
410                                 if (String.Compare (sra.ScriptName, res.Name, StringComparison.Ordinal) == 0) {
411                                         string scriptResourceName = sra.ScriptResourceName;
412                                         ResourceSet rset = null;
413                                         try {
414                                                 rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
415                                         }
416                                         catch (MissingManifestResourceException) {
417 #if TARGET_JVM // GetResourceSet does not throw  MissingManifestResourceException if ressource is not exists
418                                         }
419                                         if (rset == null) {
420 #endif
421                                                 if (scriptResourceName.EndsWith (".resources")) {
422                                                         scriptResourceName = scriptResourceName.Substring (0, scriptResourceName.Length - 10);
423                                                         rset = new ResourceManager (scriptResourceName, assembly).GetResourceSet (Threading.Thread.CurrentThread.CurrentUICulture, true, true);
424                                                 }
425 #if !TARGET_JVM
426                                                 else
427                                                         throw;
428 #endif
429                                         }
430                                         if (rset == null)
431                                                 break;
432                                         writer.WriteLine ();
433                                         string ns = sra.TypeName;
434                                         int indx = ns.LastIndexOf ('.');
435                                         if (indx > 0)
436                                                 writer.WriteLine ("Type.registerNamespace('" + ns.Substring (0, indx) + "')");
437                                         writer.Write ("{0}={{", sra.TypeName);
438                                         bool first = true;
439                                         foreach (DictionaryEntry de in rset) {
440                                                 string value = de.Value as string;
441                                                 if (value != null) {
442                                                         if (first)
443                                                                 first = false;
444                                                         else
445                                                                 writer.Write (',');
446                                                         writer.WriteLine ();
447                                                         writer.Write ("{0}:{1}", GetScriptStringLiteral ((string) de.Key), GetScriptStringLiteral (value));
448                                                 }
449                                         }
450                                         writer.WriteLine ();
451                                         writer.WriteLine ("};");
452                                         break;
453                                 }
454                         }
455
456                         bool notifyScriptLoaded = request.QueryString ["n"] == "t";
457                         if (notifyScriptLoaded) {
458                                 writer.WriteLine ();
459                                 writer.WriteLine ("if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();");
460                         }
461 #endif
462                 }
463
464                 sealed class PerformSubstitutionHelper
465                 {
466                         readonly Assembly _assembly;
467                         static readonly Regex _regex = new Regex (@"\<%=[ ]*WebResource[ ]*\([ ]*""([^""]+)""[ ]*\)[ ]*%\>");
468
469                         public PerformSubstitutionHelper (Assembly assembly) {
470                                 _assembly = assembly;
471                         }
472
473                         public void PerformSubstitution (TextReader reader, TextWriter writer) {
474                                 string line = reader.ReadLine ();
475                                 while (line != null) {
476                                         if (line.Length > 0 && _regex.IsMatch (line))
477                                                 line = _regex.Replace (line, new MatchEvaluator (PerformSubstitutionReplace));
478                                         writer.WriteLine (line);
479                                         line = reader.ReadLine ();
480                                 }
481                         }
482
483                         string PerformSubstitutionReplace (Match m) {
484                                 string resourceName = m.Groups [1].Value;
485 #if SYSTEM_WEB_EXTENSIONS
486                                 return ScriptResourceHandler.GetResourceUrl (_assembly, resourceName, false);
487 #else
488                                 return AssemblyResourceLoader.GetResourceUrl (_assembly, resourceName, false);
489 #endif
490                         }
491                 }
492                 
493 #if !SYSTEM_WEB_EXTENSIONS
494                 bool System.Web.IHttpHandler.IsReusable { get { return true; } }
495 #endif
496                 sealed class EmbeddedResource
497                 {
498                         public string Name;
499                         public string Url;
500                         public WebResourceAttribute Attribute;
501                 }
502                 
503                 sealed class AssemblyEmbeddedResources
504                 {
505                         public string AssemblyName = String.Empty;
506                         public Dictionary <string, EmbeddedResource> Resources = new Dictionary <string, EmbeddedResource> (StringComparer.Ordinal);
507                 }
508         }
509 }
510