private static void ProcessRequestInternal( HttpResponseBase response, string decryptedString, VirtualFileReader fileReader) { if (String.IsNullOrEmpty(decryptedString)) { Throw404(); } bool zip; bool singleAssemblyReference; // See GetScriptResourceUrl comment below for first character meanings. switch (decryptedString[0]) { case 'Z': case 'z': singleAssemblyReference = true; zip = true; break; case 'U': case 'u': singleAssemblyReference = true; zip = false; break; case 'Q': case 'q': singleAssemblyReference = false; zip = true; break; case 'R': case 'r': singleAssemblyReference = false; zip = false; break; case 'T': OutputEmptyPage(response, decryptedString.Substring(1)); return; default: Throw404(); return; } decryptedString = decryptedString.Substring(1); if (String.IsNullOrEmpty(decryptedString)) { Throw404(); } string[] decryptedData = decryptedString.Split('|'); if (singleAssemblyReference) { // expected: <assembly>|<resource>|<culture>[|#|<hash>] if (decryptedData.Length != 3 && decryptedData.Length != 5) { // The decrypted data must have 3 parts plus an optional 2 part hash code separated by pipes. Throw404(); } } else { // expected: <assembly1>|<resource1a>,<culture1a>,<resource1b>,<culture1b>,...|<assembly2>|<resource2a>,<culture2a>,<resource2b>,<culture2b>,...|#|<hash> if (decryptedData.Length % 2 != 0) { // The decrypted data must have an even number of parts separated by pipes. Throw404(); } } StringBuilder script = new StringBuilder(); string firstContentType = null; if (singleAssemblyReference) { // single assembly reference, format is // <assembly>|<resource>|<culture> string assemblyName = decryptedData[0]; string resourceName = decryptedData[1]; string cultureName = decryptedData[2]; Assembly assembly = GetAssembly(assemblyName); if (assembly == null) { Throw404(); } script.Append(ScriptResourceAttribute.GetScriptFromWebResourceInternal( assembly, resourceName, String.IsNullOrEmpty(cultureName) ? CultureInfo.InvariantCulture : new CultureInfo(cultureName), zip, out firstContentType )); } else { // composite script reference, format is: // <assembly1>|<resource1a>,<culture1a>,<resource1b>,<culture1b>,...|<assembly2>|<resource2a>,<culture2a>,<resource2b>,<culture2b>,... // Assembly is empty for path based scripts, and their resource/culture list is <path1>,<path2>,... // If an assembly starts with "#", the segment is ignored (expected that this includes a hash to ensure // url uniqueness when resources are changed). Also, for forward compatibility '#' segments may contain // other data. bool needsNewline = false; for (int i = 0; i < decryptedData.Length; i += 2) { string assemblyName = decryptedData[i]; bool hasAssembly = !String.IsNullOrEmpty(assemblyName); if (hasAssembly && assemblyName[0] == '#') { // hash segments are ignored, it contains a hash code for url uniqueness continue; } Debug.Assert(!String.IsNullOrEmpty(decryptedData[i + 1])); string[] resourcesAndCultures = decryptedData[i + 1].Split(','); if (resourcesAndCultures.Length == 0) { Throw404(); } Assembly assembly = hasAssembly ? GetAssembly(assemblyName) : null; if (assembly == null) { // The scripts are path-based if (firstContentType == null) { firstContentType = "text/javascript"; } for (int j = 0; j < resourcesAndCultures.Length; j++) { Encoding encoding; // DevDiv Bugs 197242 // path will either be absolute, as in "/app/foo/bar.js" or app relative, as in "~/foo/bar.js" // ToAbsolute() ensures it is in the form /app/foo/bar.js // This conversion was not done when the url was created to conserve url length. string path = _bypassVirtualPathResolution ? resourcesAndCultures[j] : VirtualPathUtility.ToAbsolute(resourcesAndCultures[j]); string fileContents = fileReader(path, out encoding); if (needsNewline) { // Output an additional newline between resources but not for the last one script.Append('\n'); } needsNewline = true; script.Append(fileContents); } } else { Debug.Assert(resourcesAndCultures.Length % 2 == 0, "The list of resource names and cultures must have an even number of parts separated by commas."); for (int j = 0; j < resourcesAndCultures.Length; j += 2) { try { string contentType; string resourceName = resourcesAndCultures[j]; string cultureName = resourcesAndCultures[j + 1]; if (needsNewline) { // Output an additional newline between resources but not for the last one script.Append('\n'); } needsNewline = true; script.Append(ScriptResourceAttribute.GetScriptFromWebResourceInternal( assembly, resourceName, String.IsNullOrEmpty(cultureName) ? CultureInfo.InvariantCulture : new CultureInfo(cultureName), zip, out contentType )); if (firstContentType == null) { firstContentType = contentType; } } catch (MissingManifestResourceException ex) { throw Create404(ex); } catch (HttpException ex) { throw Create404(ex); } } } } } if (ScriptingScriptResourceHandlerSection.ApplicationSettings.EnableCaching) { PrepareResponseCache(response); } else { PrepareResponseNoCache(response); } response.ContentType = firstContentType; if (zip) { using (MemoryStream zipped = new MemoryStream()) { using (Stream outputStream = new GZipStream(zipped, CompressionMode.Compress)) { // The choice of an encoding matters little here. // Input streams being of potentially different encodings, UTF-8 is the better // choice as it's the natural encoding for JavaScript. using (StreamWriter writer = new StreamWriter(outputStream, Encoding.UTF8)) { writer.Write(script.ToString()); } } byte[] zippedBytes = zipped.ToArray(); response.AddHeader("Content-encoding", "gzip"); response.OutputStream.Write(zippedBytes, 0, zippedBytes.Length); } } else { // Bug DevDiv #175061, we don't want to force any encoding here and let the default // encoding apply no matter what the incoming scripts might have been encoded with. response.Write(script.ToString()); } }
internal static void ProcessRequest(HttpContextBase context, VirtualFileReader fileReader = null, Action<string, Exception> logAction = null, bool validatePath = true) { string decryptedString = null; bool resourceIdentifierPresent = false; try { HttpResponseBase response = context.Response; response.Clear(); if (validatePath) { // Checking that the handler is not being called from a different path. EnsureScriptResourceRequest(context.Request.Path); } string encryptedData = context.Request.QueryString["d"]; if (String.IsNullOrEmpty(encryptedData)) { Throw404(); } resourceIdentifierPresent = true; try { decryptedString = Page.DecryptString(encryptedData, Purpose.ScriptResourceHandler_ScriptResourceUrl); } catch (CryptographicException ex) { Throw404(ex); } fileReader = fileReader ?? new VirtualFileReader(delegate(string virtualPath, out Encoding encoding) { VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider; if (!vpp.FileExists(virtualPath)) { Throw404(); } VirtualFile file = vpp.GetFile(virtualPath); if (!AppSettings.ScriptResourceAllowNonJsFiles && !file.Name.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { // MSRC 10405: Disallow all extensions other than *.js Throw404(); } using (Stream stream = file.Open()) { using (StreamReader reader = new StreamReader(stream, true)) { encoding = reader.CurrentEncoding; return reader.ReadToEnd(); } } }); ProcessRequestInternal(response, decryptedString, fileReader); } catch(Exception e) { if (resourceIdentifierPresent) { logAction = logAction ?? AssemblyResourceLoader.LogWebResourceFailure; logAction(decryptedString, e); } // MSRC 10405: There's no reason for this to return anything other than a 404 if something // goes wrong. We shouldn't propagate the inner exception inside the YSOD, as it might // contain sensitive cryptographic information. Throw404(); } }
internal static void ProcessRequest(HttpContextBase context, VirtualFileReader fileReader = null, Action <string, Exception> logAction = null, bool validatePath = true) { string decryptedString = null; bool resourceIdentifierPresent = false; try { HttpResponseBase response = context.Response; response.Clear(); if (validatePath) { // Checking that the handler is not being called from a different path. EnsureScriptResourceRequest(context.Request.Path); } string encryptedData = context.Request.QueryString["d"]; if (String.IsNullOrEmpty(encryptedData)) { Throw404(); } resourceIdentifierPresent = true; try { decryptedString = Page.DecryptString(encryptedData, Purpose.ScriptResourceHandler_ScriptResourceUrl); } catch (CryptographicException ex) { Throw404(ex); } fileReader = fileReader ?? new VirtualFileReader(delegate(string virtualPath, out Encoding encoding) { VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider; if (!vpp.FileExists(virtualPath)) { Throw404(); } VirtualFile file = vpp.GetFile(virtualPath); if (!AppSettings.ScriptResourceAllowNonJsFiles && !file.Name.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { // MSRC 10405: Disallow all extensions other than *.js Throw404(); } using (Stream stream = file.Open()) { using (StreamReader reader = new StreamReader(stream, true)) { encoding = reader.CurrentEncoding; return(reader.ReadToEnd()); } } }); ProcessRequestInternal(response, decryptedString, fileReader); } catch (Exception e) { if (resourceIdentifierPresent) { logAction = logAction ?? AssemblyResourceLoader.LogWebResourceFailure; logAction(decryptedString, e); } // MSRC 10405: There's no reason for this to return anything other than a 404 if something // goes wrong. We shouldn't propagate the inner exception inside the YSOD, as it might // contain sensitive cryptographic information. Throw404(); } }