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());
            }
        }
Ejemplo n.º 2
0
        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();
            }
        }
Ejemplo n.º 4
0
        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();
            }
        }