private static string GetScriptResourceUrlImpl( List <Tuple <Assembly, List <Tuple <string, CultureInfo> > > > assemblyResourceLists, bool zip) { EnsureAbsoluteScriptResourceUrl(); // If there's only a single assembly resource, format is // [Z|U|z|u]<assembly>|<resource>|<culture> // If there are multiple resources, or a single resource that is path based, format is // [Q|R|q|r]<assembly1>|<resource1a>,<culture1a>,<resource1b>,<culture1b>...|<assembly2>|<resource2a>,<culture2a>,<resource2b>,<culture2b>... // A path based reference has no assembly (empty). // (the Q/R indicators used in place of Z/U give the handler indiciation that the url is a composite // reference, and allows for System.Web.Extensions SP1 to maintain compatibility with RTM, should a // single resource be encrypted with SP1 and decrypted with RTM). bool singleAssemblyResource = false; if (assemblyResourceLists.Count == 1) { // only one assembly to pull from... var reference = assemblyResourceLists[0]; if ((reference.Item1 != null) && (reference.Item2.Count == 1)) { // resource is assembly not path, and there's only one resource within it to load singleAssemblyResource = true; } } // Next character of the encoded string is: // Format: S = Single Assembly Reference, C = Composite Reference or Single Path Reference // Zip: compress or not (true or false) // First Format Zip? // ===================== // Z S T // U S F // Q C T // R C F string indicator; if (singleAssemblyResource) { indicator = (zip ? "Z" : "U"); } else { indicator = (zip ? "Q" : "R"); } StringBuilder url = new StringBuilder(indicator); HashCodeCombiner hashCombiner = new HashCodeCombiner(); bool firstAssembly = true; foreach (Tuple <Assembly, List <Tuple <string, CultureInfo> > > assemblyData in assemblyResourceLists) { if (!firstAssembly) { url.Append('|'); } else { firstAssembly = false; } if (assemblyData.Item1 != null) { Tuple <AssemblyName, String> assemblyInfo = GetAssemblyInfo(assemblyData.Item1); AssemblyName assemblyName = (AssemblyName)assemblyInfo.Item1; string assemblyHash = (String)assemblyInfo.Item2; hashCombiner.AddObject(assemblyHash); if (assemblyData.Item1.GlobalAssemblyCache) { // If the assembly is in the GAC, we need to store a full name to load the assembly later // Pack the necessary values into a more compact format than FullName url.Append(assemblyName.Name); url.Append(','); url.Append(assemblyName.Version); url.Append(','); if (assemblyName.CultureInfo != null) { url.Append(assemblyName.CultureInfo); } url.Append(','); url.Append(HexParser.ToString(assemblyName.GetPublicKeyToken())); } else { // Otherwise, we can just use a partial name url.Append(assemblyName.Name); } } url.Append('|'); bool firstResource = true; foreach (Tuple <string, CultureInfo> resourceAndCulture in assemblyData.Item2) { if (!firstResource) { url.Append(','); } if (assemblyData.Item1 != null) { url.Append(resourceAndCulture.Item1); Tuple <Assembly, string, CultureInfo> cacheKey = Tuple.Create( assemblyData.Item1, resourceAndCulture.Item1, resourceAndCulture.Item2 ); string cultureName = (string)_cultureCache[cacheKey]; if (cultureName == null) { // Check if the resources exist ScriptResourceInfo resourceInfo = ScriptResourceInfo.GetInstance(assemblyData.Item1, resourceAndCulture.Item1); if (resourceInfo == ScriptResourceInfo.Empty) { ThrowUnknownResource(resourceAndCulture.Item1); } Stream scriptStream = assemblyData.Item1.GetManifestResourceStream(resourceInfo.ScriptName); if (scriptStream == null) { ThrowUnknownResource(resourceAndCulture.Item1); } cultureName = DetermineNearestAvailableCulture( assemblyData.Item1, resourceAndCulture.Item1, resourceAndCulture.Item2).Name; _cultureCache[cacheKey] = cultureName; } url.Append(singleAssemblyResource ? "|" : ","); url.Append(cultureName); } else { Debug.Assert(!singleAssemblyResource, "This should never happen since this is a path reference."); if (!_bypassVirtualPathResolution) { VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider; if (!vpp.FileExists(resourceAndCulture.Item1)) { ThrowUnknownResource(resourceAndCulture.Item1); } string hash = vpp.GetFileHash(resourceAndCulture.Item1, new string[] { resourceAndCulture.Item1 }); hashCombiner.AddObject(hash); } url.Append(resourceAndCulture.Item1); } firstResource = false; } } // DevDiv Bugs 186624: The hash code needs to be part of the encrypted blob for composite scripts // because we cache the composite script on the server using a VaryByParam["d"]. Otherwise, if a // path based script in the composite changes, only the 't' parameter would change, which would // cause a new request to the server, but it would be served via cache since 'd' would be the same. // This isn't a problem for assembly based resources since changing them also restarts the app and // clears the cache. We do not vary by 't' because that makes it possible to flood the server cache // with cache entries, since anything could be used for 't'. Putting the hash in 'd' ensures a different // url and different cache entry when a script changes, but without the possibility of flooding // the server cache. // However, we continue to use the 't' parameter for single assembly references for compatibility. string resourceUrl; if (singleAssemblyResource) { resourceUrl = _absoluteScriptResourceUrl + Page.EncryptString(url.ToString(), Purpose.ScriptResourceHandler_ScriptResourceUrl) + "&t=" + hashCombiner.CombinedHashString; } else { // note that CombinedHashString is hex, it will never include a '|' that would confuse the handler. url.Append("|#|"); url.Append(hashCombiner.CombinedHashString); resourceUrl = _absoluteScriptResourceUrl + Page.EncryptString(url.ToString(), Purpose.ScriptResourceHandler_ScriptResourceUrl); } if (resourceUrl.Length > _maximumResourceUrlLength) { throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, AtlasWeb.ScriptResourceHandler_ResourceUrlTooLong, _maximumResourceUrlLength)); } return(resourceUrl); }
internal static CultureInfo DetermineNearestAvailableCulture( Assembly assembly, string scriptResourceName, CultureInfo culture) { if (String.IsNullOrEmpty(scriptResourceName)) { return(CultureInfo.InvariantCulture); } Tuple <Assembly, string, CultureInfo> cacheKey = Tuple.Create(assembly, scriptResourceName, culture); CultureInfo cachedCulture = (CultureInfo)_cultureCache[cacheKey]; if (cachedCulture == null) { string releaseResourceName = scriptResourceName.EndsWith(".debug.js", StringComparison.OrdinalIgnoreCase) ? scriptResourceName.Substring(0, scriptResourceName.Length - 9) + ".js" : null; ScriptResourceInfo resourceInfo = ScriptResourceInfo.GetInstance(assembly, scriptResourceName); ScriptResourceInfo releaseResourceInfo = (releaseResourceName != null) ? ScriptResourceInfo.GetInstance(assembly, releaseResourceName) : null; if (!String.IsNullOrEmpty(resourceInfo.ScriptResourceName) || ((releaseResourceInfo != null) && !String.IsNullOrEmpty(releaseResourceInfo.ScriptResourceName))) { ResourceManager resourceManager = ScriptResourceAttribute.GetResourceManager(resourceInfo.ScriptResourceName, assembly); ResourceManager releaseResourceManager = (releaseResourceInfo != null) ? ScriptResourceAttribute.GetResourceManager(releaseResourceInfo.ScriptResourceName, assembly) : null; ResourceSet localizedSet = null; ResourceSet releaseSet = null; if (resourceManager != null) { resourceManager.GetResourceSet(CultureInfo.InvariantCulture, true, true); // Look for the explicitly localized version of the resources that is nearest the culture. localizedSet = resourceManager.GetResourceSet(culture, true, false); } if (releaseResourceManager != null) { releaseResourceManager.GetResourceSet(CultureInfo.InvariantCulture, true, true); // Look for the explicitly localized version of the resources that is nearest the culture. releaseSet = releaseResourceManager.GetResourceSet(culture, true, false); } if ((resourceManager != null) || (releaseResourceManager != null)) { while ((localizedSet == null) && (releaseSet == null)) { culture = culture.Parent; if (culture.Equals(CultureInfo.InvariantCulture)) { break; } localizedSet = resourceManager.GetResourceSet(culture, true, false); releaseSet = (releaseResourceManager != null) ? releaseResourceManager.GetResourceSet(culture, true, false) : null; } } else { culture = CultureInfo.InvariantCulture; } } else { culture = CultureInfo.InvariantCulture; } // Neutral assembly culture falls back on invariant CultureInfo neutralCulture = GetAssemblyNeutralCulture(assembly); if ((neutralCulture != null) && neutralCulture.Equals(culture)) { culture = CultureInfo.InvariantCulture; } cachedCulture = culture; _cultureCache[cacheKey] = cachedCulture; } return(cachedCulture); }