Merge pull request #3373 from marek-safar/net-4.6.2
[mono.git] / mcs / class / referencesource / System.Web / Handlers / AssemblyResourceLoader.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="AssemblyResourceLoader.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 namespace System.Web.Handlers {
7     using System;
8     using System.Collections;
9     using System.Collections.Specialized;
10     using System.Globalization;
11     using System.IO;
12     using System.Reflection;
13     using System.Security.Permissions;
14     using System.Text;
15     using System.Text.RegularExpressions;
16     using System.Web;
17     using System.Web.Compilation;
18     using System.Web.Configuration;
19     using System.Web.Hosting;
20     using System.Web.Management;
21     using System.Web.RegularExpressions;
22     using System.Web.Security.Cryptography;
23     using System.Web.UI;
24     using System.Web.Util;
25
26
27     /// <devdoc>
28     /// Provides a way to load client-side resources from assemblies
29     /// </devdoc>
30     public sealed class AssemblyResourceLoader : IHttpHandler {
31         private const string _webResourceUrl = "WebResource.axd";
32
33         private readonly static Regex webResourceRegex = new WebResourceRegex();
34
35         private static IDictionary _urlCache = Hashtable.Synchronized(new Hashtable());
36         private static IDictionary _assemblyInfoCache = Hashtable.Synchronized(new Hashtable());
37         private static IDictionary _webResourceCache = Hashtable.Synchronized(new Hashtable());
38         private static IDictionary _typeAssemblyCache = Hashtable.Synchronized(new Hashtable());
39
40         // This group of fields is used for backwards compatibility. In v1.x you could
41         // technically customize the files in the /aspnet_client/ folder whereas in v2.x
42         // we serve those files using WebResource.axd. These fields are used to check
43         // if there is a customized version of the file and use that instead of the resource.
44         private static bool _webFormsScriptChecked;
45         private static VirtualPath _webFormsScriptLocation;
46         private static bool _webUIValidationScriptChecked;
47         private static VirtualPath _webUIValidationScriptLocation;
48         private static bool _smartNavScriptChecked;
49         private static VirtualPath _smartNavScriptLocation;
50         private static bool _smartNavPageChecked;
51         private static VirtualPath _smartNavPageLocation;
52
53         private static bool _handlerExistenceChecked;
54         private static bool _handlerExists;
55         // set by unit tests to avoid dependency on httpruntime.
56         internal static string _applicationRootPath;
57
58         private static bool DebugMode {
59             get {
60                 return HttpContext.Current.IsDebuggingEnabled;
61             }
62         }
63
64         /// <devdoc>
65         ///     Create a cache key for the UrlCache.  
66         ///
67         ///     requirement:  If assembly1 and assembly2 represent the same assembly, 
68         ///     then they must be the same object; otherwise this method will fail to generate 
69         ///     a unique cache key.
70         /// </devdoc>
71         private static int CreateWebResourceUrlCacheKey(Assembly assembly, string resourceName,
72             bool htmlEncoded, bool forSubstitution, bool enableCdn, bool debuggingEnabled, bool secureConnection) {
73             int hashCode = HashCodeCombiner.CombineHashCodes(
74                 assembly.GetHashCode(),
75                 resourceName.GetHashCode(),
76                 htmlEncoded.GetHashCode(),
77                 forSubstitution.GetHashCode(),
78                 enableCdn.GetHashCode());
79             return HashCodeCombiner.CombineHashCodes(hashCode,
80                 debuggingEnabled.GetHashCode(),
81                 secureConnection.GetHashCode());
82         }
83
84         /// <devdoc>
85         /// Validates that the WebResource.axd handler is registered in config and actually
86         /// points to the correct handler type.
87         /// </devdoc>
88         private static void EnsureHandlerExistenceChecked() {
89             // First we have to check that the handler is registered:
90             // <add path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader" validate="True" />
91             if (!_handlerExistenceChecked) { 
92                 HttpContext context = HttpContext.Current;
93                 IIS7WorkerRequest iis7WorkerRequest = (context != null) ? context.WorkerRequest as IIS7WorkerRequest : null;
94                 string webResourcePath = UrlPath.Combine(HttpRuntime.AppDomainAppVirtualPathString, _webResourceUrl);
95                 if (iis7WorkerRequest != null) {
96                     // check the IIS <handlers> section by mapping the handler
97                     string handlerTypeString = iis7WorkerRequest.MapHandlerAndGetHandlerTypeString(method: "GET",
98                                                path: UrlPath.Combine(HttpRuntime.AppDomainAppVirtualPathString, _webResourceUrl),
99                                                convertNativeStaticFileModule: false, ignoreWildcardMappings: true);
100                     if (!String.IsNullOrEmpty(handlerTypeString)) {
101                         _handlerExists = (typeof(AssemblyResourceLoader) == BuildManager.GetType(handlerTypeString, true /*throwOnFail*/, false /*ignoreCase*/));
102                     }
103                 }
104                 else {
105                     // check the <httpHandlers> section
106                     HttpHandlerAction httpHandler = RuntimeConfig.GetConfig(VirtualPath.Create(webResourcePath)).HttpHandlers.FindMapping("GET", VirtualPath.Create(_webResourceUrl));
107                     _handlerExists = (httpHandler != null) && (httpHandler.TypeInternal == typeof(AssemblyResourceLoader));
108                 }                
109                 _handlerExistenceChecked = true;
110             }
111         }
112
113         /// <devdoc>
114         ///     Performs the actual putting together of the resource reference URL.
115         /// </devdoc>
116         private static string FormatWebResourceUrl(string assemblyName, string resourceName, long assemblyDate, bool htmlEncoded) {
117             string encryptedData = Page.EncryptString(assemblyName + "|" + resourceName, Purpose.AssemblyResourceLoader_WebResourceUrl);
118             if (htmlEncoded) {
119                 return String.Format(CultureInfo.InvariantCulture, _webResourceUrl + "?d={0}&amp;t={1}",
120                                     encryptedData,
121                                     assemblyDate);
122             }
123             else {
124                 return String.Format(CultureInfo.InvariantCulture, _webResourceUrl + "?d={0}&t={1}",
125                                     encryptedData,
126                                     assemblyDate);
127             }
128         }
129
130         internal static Assembly GetAssemblyFromType(Type type) {
131             Assembly assembly = (Assembly)_typeAssemblyCache[type];
132             if (assembly == null) {
133                 assembly = type.Assembly;
134                 _typeAssemblyCache[type] = assembly;
135             }
136             return assembly;
137         }
138
139         private static Pair GetAssemblyInfo(Assembly assembly) {
140             Pair assemblyInfo = _assemblyInfoCache[assembly] as Pair;
141             if (assemblyInfo == null) {
142                 assemblyInfo = GetAssemblyInfoWithAssertInternal(assembly);
143                 _assemblyInfoCache[assembly] = assemblyInfo;
144             }
145             Debug.Assert(assemblyInfo != null, "Assembly info should not be null");
146             return assemblyInfo;
147         }
148
149         [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
150         private static Pair GetAssemblyInfoWithAssertInternal(Assembly assembly) {
151             AssemblyName assemblyName = assembly.GetName();
152             long assemblyDate = File.GetLastWriteTime(new Uri(assemblyName.CodeBase).LocalPath).Ticks;
153             Pair assemblyInfo = new Pair(assemblyName, assemblyDate);
154             return assemblyInfo;
155         }
156
157         /// <devdoc>
158         /// Gets the virtual path of a physical resource file. Null is
159         /// returned if the resource does not exist.
160         /// We assert full FileIOPermission so that we can map paths.
161         /// </devdoc>
162         [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]
163         private static VirtualPath GetDiskResourcePath(string resourceName) {
164             VirtualPath clientScriptsLocation = Util.GetScriptLocation();
165             VirtualPath resourceVirtualPath = clientScriptsLocation.SimpleCombine(resourceName);
166             string resourcePhysicalPath = resourceVirtualPath.MapPath();
167             if (File.Exists(resourcePhysicalPath)) {
168                 return resourceVirtualPath;
169             }
170             else {
171                 return null;
172             }
173         }
174
175         internal static string GetWebResourceUrl(Type type, string resourceName) {
176             return GetWebResourceUrl(type, resourceName, false, null);
177         }
178
179         internal static string GetWebResourceUrl(Type type, string resourceName, bool htmlEncoded) {
180             return GetWebResourceUrl(type, resourceName, htmlEncoded, null);
181         }
182
183         internal static string GetWebResourceUrl(Type type, string resourceName, bool htmlEncoded, IScriptManager scriptManager) {
184             bool enableCdn = (scriptManager != null && scriptManager.EnableCdn);
185             return GetWebResourceUrl(type, resourceName, htmlEncoded, scriptManager, enableCdn: enableCdn);
186         }
187
188         /// <devdoc>
189         ///     Gets a URL resource reference to a client-side resource
190         /// </devdoc>
191         internal static string GetWebResourceUrl(Type type, string resourceName, bool htmlEncoded, IScriptManager scriptManager, bool enableCdn) {
192             Assembly assembly = GetAssemblyFromType(type);
193             Debug.Assert(assembly != null, "Type.Assembly should never be null.");
194
195             // If the resource request is for System.Web.dll and more specifically
196             // it is for a file that we shipped in v1.x, we have to check if a
197             // customized copy of the file exists. See notes at the top of the file
198             // regarding this.
199             if (assembly == typeof(AssemblyResourceLoader).Assembly) {
200                 if (String.Equals(resourceName, "WebForms.js", StringComparison.Ordinal)) {
201                     if (!_webFormsScriptChecked) {
202                         _webFormsScriptLocation = GetDiskResourcePath(resourceName);
203                         _webFormsScriptChecked = true;
204                     }
205                     if (_webFormsScriptLocation != null) {
206                         return _webFormsScriptLocation.VirtualPathString;
207                     }
208                 }
209                 else if (String.Equals(resourceName, "WebUIValidation.js", StringComparison.Ordinal)) {
210                     if (!_webUIValidationScriptChecked) {
211                         _webUIValidationScriptLocation = GetDiskResourcePath(resourceName);
212                         _webUIValidationScriptChecked = true;
213                     }
214                     if (_webUIValidationScriptLocation != null) {
215                         return _webUIValidationScriptLocation.VirtualPathString;
216                     }
217                 }
218                 else if (String.Equals(resourceName, "SmartNav.htm", StringComparison.Ordinal)) {
219                     if (!_smartNavPageChecked) {
220                         _smartNavPageLocation = GetDiskResourcePath(resourceName);
221                         _smartNavPageChecked = true;
222                     }
223                     if (_smartNavPageLocation != null) {
224                         return _smartNavPageLocation.VirtualPathString;
225                     }
226                 }
227                 else if (String.Equals(resourceName, "SmartNav.js", StringComparison.Ordinal)) {
228                     if (!_smartNavScriptChecked) {
229                         _smartNavScriptLocation = GetDiskResourcePath(resourceName);
230                         _smartNavScriptChecked = true;
231                     }
232                     if (_smartNavScriptLocation != null) {
233                         return _smartNavScriptLocation.VirtualPathString;
234                     }
235                 }
236             }
237
238             return GetWebResourceUrlInternal(assembly, resourceName, htmlEncoded, false, scriptManager, enableCdn);
239         }
240
241         private static WebResourceAttribute FindWebResourceAttribute(Assembly assembly, string resourceName) {
242             object[] attrs = assembly.GetCustomAttributes(false);
243             for (int i = 0; i < attrs.Length; i++) {
244                 WebResourceAttribute wra = attrs[i] as WebResourceAttribute;
245                 if ((wra != null) && String.Equals(wra.WebResource, resourceName, StringComparison.Ordinal)) {
246                     return wra;
247                 }
248             }
249             return null;
250         }
251
252         internal static string FormatCdnUrl(Assembly assembly, string cdnPath) {
253             // {0} = Short Assembly Name
254             // {1} = Assembly Version
255             // {2} = Assembly File Version
256             // use a new AssemblyName because assembly.GetName() doesn't work in medium trust.
257             AssemblyName assemblyName = new AssemblyName(assembly.FullName);
258             return String.Format(CultureInfo.InvariantCulture,
259                 cdnPath,
260                 HttpUtility.UrlEncode(assemblyName.Name),
261                 HttpUtility.UrlEncode(assemblyName.Version.ToString(4)),
262                 HttpUtility.UrlEncode(AssemblyUtil.GetAssemblyFileVersion(assembly)));
263         }
264
265         private static string GetCdnPath(string resourceName, Assembly assembly, bool secureConnection) {
266             string cdnPath = null;
267             WebResourceAttribute wra = FindWebResourceAttribute(assembly, resourceName);
268             if (wra != null) {
269                 cdnPath = secureConnection ? wra.CdnPathSecureConnection : wra.CdnPath;
270                 if (!String.IsNullOrEmpty(cdnPath)) {
271                     cdnPath = FormatCdnUrl(assembly, cdnPath);
272                 }
273             }
274             return cdnPath;
275         }
276
277         internal static string GetWebResourceUrlInternal(Assembly assembly, string resourceName,
278                 bool htmlEncoded, bool forSubstitution, IScriptManager scriptManager) {
279
280             bool enableCdn = (scriptManager != null && scriptManager.EnableCdn);
281             return GetWebResourceUrlInternal(assembly, resourceName, htmlEncoded, forSubstitution, scriptManager, enableCdn: enableCdn);
282         }
283
284         internal static string GetWebResourceUrlInternal(Assembly assembly, string resourceName,
285             bool htmlEncoded, bool forSubstitution, IScriptManager scriptManager, bool enableCdn) {
286             // When this url is being inserted as a substitution in another resource,
287             // it should just be "WebResource.axd?d=..." since the resource is already coming
288             // from the app root (i.e. no need for a full absolute /app/WebResource.axd).
289             // Otherwise we must return a path that is absolute (starts with '/') or
290             // a full absolute uri (http://..) as in the case of a CDN Path.
291             
292             EnsureHandlerExistenceChecked();
293             if (!_handlerExists) {
294                 throw new InvalidOperationException(SR.GetString(SR.AssemblyResourceLoader_HandlerNotRegistered));
295             }
296             Assembly effectiveAssembly = assembly;
297             string effectiveResourceName = resourceName;
298
299             bool debuggingEnabled = false;
300             bool secureConnection;
301             if (scriptManager != null) {
302                 debuggingEnabled = scriptManager.IsDebuggingEnabled;
303                 secureConnection = scriptManager.IsSecureConnection;
304             }
305             else {
306                 secureConnection = ((HttpContext.Current != null) && (HttpContext.Current.Request != null) &&
307                     HttpContext.Current.Request.IsSecureConnection);
308                 debuggingEnabled = (HttpContext.Current != null) && HttpContext.Current.IsDebuggingEnabled;
309             }
310             int urlCacheKey = CreateWebResourceUrlCacheKey(assembly, resourceName, htmlEncoded,
311                 forSubstitution, enableCdn, debuggingEnabled, secureConnection);
312
313             string url = (string)_urlCache[urlCacheKey];
314
315             if (url == null) {
316                 IScriptResourceDefinition definition = null;
317                 if (ClientScriptManager._scriptResourceMapping != null) {
318                     definition = ClientScriptManager._scriptResourceMapping.GetDefinition(resourceName, assembly);
319                     if (definition != null) {
320                         if (!String.IsNullOrEmpty(definition.ResourceName)) {
321                             effectiveResourceName = definition.ResourceName;
322                         }
323                         if (definition.ResourceAssembly != null) {
324                             effectiveAssembly = definition.ResourceAssembly;
325                         }
326                     }
327                 } 
328                 string path = null;
329                 // if a resource mapping exists, take it's settings into consideration
330                 // it might supply a path or a cdnpath.
331                 if (definition != null) {
332                     if (enableCdn) {
333                         // Winner is first path defined, falling back on the effectiveResourceName/Assembly
334                         // Debug Mode  : d.CdnDebugPath, d.DebugPath, *wra.CdnPath, d.Path
335                         // Release Mode: d.CdnPath                  , *wra.CdnPath, d.Path
336                         // * the WebResourceAttribute corresponding to the resource defined in the definition, not the
337                         //  the original resource.
338                         // Also, if the definition has a CdnPath but it cannot be converted to a secure one during https,
339                         // the WRA's CdnPath is not considered.
340                         if (debuggingEnabled) {
341                             path = secureConnection ? definition.CdnDebugPathSecureConnection : definition.CdnDebugPath;
342                             if (String.IsNullOrEmpty(path)) {
343                                 path = definition.DebugPath;
344                                 if (String.IsNullOrEmpty(path)) {
345                                     // Get CDN Path from the redirected resource name/assembly, not the original one,
346                                     // but not if this is a secure connection and the only reason we didn't use the definition
347                                     // cdn path is because it doesnt support secure connections.
348                                     if (!secureConnection || String.IsNullOrEmpty(definition.CdnDebugPath)) {
349                                         path = GetCdnPath(effectiveResourceName, effectiveAssembly, secureConnection);
350                                     }
351                                     if (String.IsNullOrEmpty(path)) {
352                                         path = definition.Path;
353                                     }
354                                 }
355                             }
356                         }
357                         else {
358                             path = secureConnection ? definition.CdnPathSecureConnection : definition.CdnPath;
359                             if (String.IsNullOrEmpty(path)) {
360                                 // Get CDN Path from the redirected resource name/assembly, not the original one
361                                 // but not if this is a secure connection and the only reason we didn't use the definition
362                                 // cdn path is because it doesnt support secure connections.
363                                 if (!secureConnection || String.IsNullOrEmpty(definition.CdnPath)) {
364                                     path = GetCdnPath(effectiveResourceName, effectiveAssembly, secureConnection);
365                                 }
366                                 if (String.IsNullOrEmpty(path)) {
367                                     path = definition.Path;
368                                 }
369                             }
370                         }
371                     } // cdn
372                     else {
373                         // Winner is first path defined, falling back on the effectiveResourceName/Assembly
374                         // Debug Mode  : d.DebugPath, d.Path
375                         // Release Mode: d.Path
376                         if (debuggingEnabled) {
377                             path = definition.DebugPath;
378                             if (String.IsNullOrEmpty(path)) {
379                                 path = definition.Path;
380                             }
381                         }
382                         else {
383                             path = definition.Path;
384                         }
385                     }
386                 } // does not have definition
387                 else if (enableCdn) {
388                     path = GetCdnPath(effectiveResourceName, effectiveAssembly, secureConnection);
389                 }
390
391                 if (!String.IsNullOrEmpty(path)) {
392                     // assembly based resource has been overridden by a path,
393                     // whether that be a CDN Path or a definition.Path or DebugPath.
394                     // We must return a path that is absolute (starts with '/') or
395                     // a full absolute uri (http://..) as in the case of a CDN Path.
396                     // An overridden Path that is not a CDN Path is required to be absolute
397                     // or app relative.
398                     if (UrlPath.IsAppRelativePath(path)) {
399                         // expand ~/. If it is rooted (/) or an absolute uri, no conversion needed
400                         if (_applicationRootPath == null) {
401                             url = VirtualPathUtility.ToAbsolute(path);
402                         }
403                         else {
404                             url = VirtualPathUtility.ToAbsolute(path, _applicationRootPath);
405                         }
406                     }
407                     else {
408                         // must be a full uri or already rooted.
409                         url = path;
410                     }
411                     if (htmlEncoded) {
412                         url = HttpUtility.HtmlEncode(url);
413                     }
414                 }
415                 else {
416                     string urlAssemblyName;
417                     Pair assemblyInfo = GetAssemblyInfo(effectiveAssembly);
418                     AssemblyName assemblyName = (AssemblyName)assemblyInfo.First;
419                     long assemblyDate = (long)assemblyInfo.Second;
420                     string assemblyVersion = assemblyName.Version.ToString();
421
422                     if (effectiveAssembly.GlobalAssemblyCache) {
423                         // If the assembly is in the GAC, we need to store a full name to load the assembly later
424                         if (effectiveAssembly == HttpContext.SystemWebAssembly) {
425                             urlAssemblyName = "s";
426                         }
427                         else {
428                             // Pack the necessary values into a more compact format than FullName
429                             StringBuilder builder = new StringBuilder();
430                             builder.Append('f');
431                             builder.Append(assemblyName.Name);
432                             builder.Append(',');
433                             builder.Append(assemblyVersion);
434                             builder.Append(',');
435                             if (assemblyName.CultureInfo != null) {
436                                 builder.Append(assemblyName.CultureInfo.ToString());
437                             }
438                             builder.Append(',');
439                             byte[] token = assemblyName.GetPublicKeyToken();
440                             for (int i = 0; i < token.Length; i++) {
441                                 builder.Append(token[i].ToString("x2", CultureInfo.InvariantCulture));
442                             }
443                             urlAssemblyName = builder.ToString();
444                         }
445                     }
446                     else {
447                         // Otherwise, we can just use a partial name
448                         urlAssemblyName = "p" + assemblyName.Name;
449                     }
450                     url = FormatWebResourceUrl(urlAssemblyName, effectiveResourceName, assemblyDate, htmlEncoded);
451                     if (!forSubstitution && (HttpRuntime.AppDomainAppVirtualPathString != null)) {
452                         // When this url is being inserted as a substitution in another resource,
453                         // it should just be "WebResource.axd?d=..." since the resource is already coming
454                         // from the app root (i.e. no need for a full absolute /app/WebResource.axd).
455                         url = UrlPath.Combine(HttpRuntime.AppDomainAppVirtualPathString, url);
456                     }
457                 }
458                 _urlCache[urlCacheKey] = url;
459             }
460             return url;
461         }
462
463         internal static bool IsValidWebResourceRequest(HttpContext context) {
464             EnsureHandlerExistenceChecked();
465             if (!_handlerExists) {
466                 // If the handler isn't properly registered, it can't
467                 // possibly be a valid web resource request.
468                 return false;
469             }
470
471             string webResourceHandlerUrl = UrlPath.Combine(HttpRuntime.AppDomainAppVirtualPathString, _webResourceUrl);
472             string requestPath = context.Request.Path;
473             if (String.Equals(requestPath, webResourceHandlerUrl, StringComparison.OrdinalIgnoreCase)) {
474                 return true;
475             }
476
477             return false;
478         }
479
480         internal static void LogWebResourceFailure(string decryptedData, Exception exception) {
481             string errorMessage = null;
482             if (decryptedData != null) {
483                 errorMessage = SR.GetString(SR.Webevent_msg_RuntimeErrorWebResourceFailure_ResourceMissing, decryptedData);
484             }
485             else {
486                 errorMessage = SR.GetString(SR.Webevent_msg_RuntimeErrorWebResourceFailure_DecryptionError);
487             }
488             WebBaseEvent.RaiseSystemEvent(message: errorMessage,
489                 source: null,
490                 eventCode: WebEventCodes.RuntimeErrorWebResourceFailure,
491                 eventDetailCode: WebEventCodes.UndefinedEventDetailCode,
492                 exception: exception);
493         }
494
495         /// <internalonly/>
496         bool IHttpHandler.IsReusable {
497             get {
498                 return true;
499             }
500         }
501
502
503         /// <internalonly/>
504         void IHttpHandler.ProcessRequest(HttpContext context) {
505             // Make sure we don't get any extra content in this handler (like Application.BeginRequest stuff);
506             context.Response.Clear();
507
508             Stream resourceStream = null;
509             string decryptedData = null;
510             bool resourceIdentifierPresent = false;
511
512             Exception exception = null;
513             try {
514                 NameValueCollection queryString = context.Request.QueryString;
515
516                 string encryptedData = queryString["d"];
517                 if (String.IsNullOrEmpty(encryptedData)) {
518                     throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_InvalidRequest));
519                 }
520                 resourceIdentifierPresent = true;
521
522                 decryptedData = Page.DecryptString(encryptedData, Purpose.AssemblyResourceLoader_WebResourceUrl);
523
524                 int separatorIndex = decryptedData.IndexOf('|');
525                 Debug.Assert(separatorIndex != -1, "The decrypted data must contain a separator.");
526
527                 string assemblyName = decryptedData.Substring(0, separatorIndex);
528                 if (String.IsNullOrEmpty(assemblyName)) {
529                     throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_AssemblyNotFound, assemblyName));
530                 }
531
532                 string resourceName = decryptedData.Substring(separatorIndex + 1);
533                 if (String.IsNullOrEmpty(resourceName)) {
534                     throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_ResourceNotFound, resourceName));
535                 }
536
537                 char nameType = assemblyName[0];
538                 assemblyName = assemblyName.Substring(1);
539
540                 Assembly assembly = null;
541
542                 // If it was a full name, create an AssemblyName and load from that
543                 if (nameType == 'f') {
544                     string[] parts = assemblyName.Split(',');
545
546                     if (parts.Length != 4) {
547                         throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_InvalidRequest));
548                     }
549
550                     AssemblyName realName = new AssemblyName();
551                     realName.Name = parts[0];
552                     realName.Version = new Version(parts[1]);
553                     string cultureString = parts[2];
554
555                     // Try to determine the culture, using the invariant culture if there wasn't one (doesn't work without it)
556                     if (cultureString.Length > 0) {
557                         realName.CultureInfo = new CultureInfo(cultureString);
558                     }
559                     else {
560                         realName.CultureInfo = CultureInfo.InvariantCulture;
561                     }
562
563                     // Parse up the public key token which is represented as hex bytes in a string
564                     string token = parts[3];
565                     byte[] tokenBytes = new byte[token.Length / 2];
566                     for (int i = 0; i < tokenBytes.Length; i++) {
567                         tokenBytes[i] = Byte.Parse(token.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
568                     }
569                     realName.SetPublicKeyToken(tokenBytes);
570
571                     assembly = Assembly.Load(realName);
572                 }
573                 // System.Web special case
574                 else if (nameType == 's') {
575                     assembly = typeof(AssemblyResourceLoader).Assembly;
576                 }
577                 // If was a partial name, just try to load it
578                 else if (nameType == 'p') {
579                     assembly = Assembly.Load(assemblyName);
580                 }
581                 else {
582                     throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_InvalidRequest));
583                 }
584
585                 // Dev10 Bugs 602949: Throw 404 if resource not found rather than do nothing.
586                 // This is done before creating the cache entry, since it could be that the assembly is loaded
587                 // later on without the app restarting.
588                 if (assembly == null) {
589                     throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_InvalidRequest));
590                 }
591
592                 bool performSubstitution = false;
593                 bool validResource = false;
594                 string contentType = String.Empty;
595
596                 // Check the validation cache to see if the resource has already been validated
597                 int cacheKey = HashCodeCombiner.CombineHashCodes(assembly.GetHashCode(), resourceName.GetHashCode());
598                 Triplet resourceTriplet = (Triplet)_webResourceCache[cacheKey];
599                 if (resourceTriplet != null) {
600                     validResource = (bool)resourceTriplet.First;
601                     contentType = (string)resourceTriplet.Second;
602                     performSubstitution = (bool)resourceTriplet.Third;
603                 }
604                 else {
605                     // Validation cache is empty, find out if it's valid and add it to the cache
606                     WebResourceAttribute wra = FindWebResourceAttribute(assembly, resourceName);
607                     if (wra != null) {
608                         resourceName = wra.WebResource;
609                         validResource = true;
610                         contentType = wra.ContentType;
611                         performSubstitution = wra.PerformSubstitution;
612                     }
613
614                     // Cache the result so we don't have to do this again
615                     try {
616                         if (validResource) {
617                             // a WebResourceAttribute was found, but does the resource really exist?
618                             validResource = false;
619                             resourceStream = assembly.GetManifestResourceStream(resourceName);
620                             validResource = (resourceStream != null);
621                         }
622                     }
623                     finally {
624                         // Cache the results, even if there was an exception getting the stream,
625                         // so we don't have to do this again
626                         Triplet triplet = new Triplet();
627                         triplet.First = validResource;
628                         triplet.Second = contentType;
629                         triplet.Third = performSubstitution;
630                         _webResourceCache[cacheKey] = triplet;
631                     }
632                 }
633
634                 if (validResource) {
635                     // Cache the resource so we don't keep processing the same requests
636                     HttpCachePolicy cachePolicy = context.Response.Cache;
637                     cachePolicy.SetCacheability(HttpCacheability.Public);
638                     cachePolicy.VaryByParams["d"] = true;
639                     cachePolicy.SetOmitVaryStar(true);
640                     cachePolicy.SetExpires(DateTime.Now + TimeSpan.FromDays(365));
641                     cachePolicy.SetValidUntilExpires(true);
642                     Pair assemblyInfo = GetAssemblyInfo(assembly);
643                     cachePolicy.SetLastModified(new DateTime((long)assemblyInfo.Second));
644
645                     StreamReader reader = null;
646                     try {
647                         if (resourceStream == null) {
648                             // null in the case that _webResourceCache had the item
649                             resourceStream = assembly.GetManifestResourceStream(resourceName);
650                         }
651                         if (resourceStream != null) {
652                             context.Response.ContentType = contentType;
653
654                             if (performSubstitution) {
655                                 // 
656                                 reader = new StreamReader(resourceStream, true);
657                             
658                                 string content = reader.ReadToEnd();
659                             
660                                 // Looking for something of the form: WebResource("resourcename")
661                                 MatchCollection matches = webResourceRegex.Matches(content);
662                                 int startIndex = 0;
663                                 StringBuilder newContent = new StringBuilder();
664                                 foreach (Match match in matches) {
665                                     newContent.Append(content.Substring(startIndex, match.Index - startIndex));
666                                 
667                                     Group group = match.Groups["resourceName"];
668                                     if (group != null) {
669                                         string embeddedResourceName = group.ToString();
670                                         if (embeddedResourceName.Length > 0) {
671                                             // 
672                                             if (String.Equals(embeddedResourceName, resourceName, StringComparison.Ordinal)) {
673                                                 throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_NoCircularReferences, resourceName));
674                                             }
675                                             newContent.Append(GetWebResourceUrlInternal(assembly, embeddedResourceName, htmlEncoded: false, forSubstitution: true, scriptManager: null));
676                                         }
677                                     }
678                                 
679                                     startIndex = match.Index + match.Length;
680                                 }
681
682                                 newContent.Append(content.Substring(startIndex, content.Length - startIndex));
683                             
684                                 StreamWriter writer = new StreamWriter(context.Response.OutputStream, reader.CurrentEncoding);
685                                 writer.Write(newContent.ToString());
686                                 writer.Flush();
687                             }
688                             else {
689                                 byte[] buffer = new byte[1024];
690                                 Stream outputStream = context.Response.OutputStream;
691                                 int count = 1;
692                                 while (count > 0) {
693                                     count = resourceStream.Read(buffer, 0, 1024);
694                                     outputStream.Write(buffer, 0, count);
695                                 }
696                                 outputStream.Flush();
697                             }
698                         }
699                     }
700                     finally {
701                         if (reader != null)
702                             reader.Close();
703                         if (resourceStream != null)
704                             resourceStream.Close();
705                     }
706                 }
707             }
708             catch(Exception e) {
709                 exception = e;
710                 // MSRC 10405: ---- all errors in the event of failure. In particular, we don't want to
711                 // bubble the inner exceptions up in the YSOD, as they might contain sensitive cryptographic
712                 // information. Setting 'resourceStream' to null will cause an appropriate exception to
713                 // be thrown.
714                 resourceStream = null;
715             }
716
717             // Dev10 Bugs 602949: 404 if the assembly is not found or if the resource does not exist
718             if (resourceStream == null) {
719                 if (resourceIdentifierPresent) {
720                     LogWebResourceFailure(decryptedData, exception);
721                 }
722                 throw new HttpException(404, SR.GetString(SR.AssemblyResourceLoader_InvalidRequest));
723             }
724
725             context.Response.IgnoreFurtherWrites();
726         }
727     }
728 }