1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 /*============================================================
11 ** Purpose: Searches for resources in Assembly manifest, used
12 ** for assembly-based resource lookup.
15 ===========================================================*/
17 namespace System.Resources
20 using System.Collections;
21 using System.Collections.Generic;
22 using System.Globalization;
24 using System.Reflection;
25 using System.Runtime.InteropServices;
26 using System.Runtime.CompilerServices;
27 using System.Runtime.Versioning;
29 using System.Threading;
30 using System.Diagnostics;
31 using Microsoft.Win32;
34 // Note: this type is integral to the construction of exception objects,
35 // and sometimes this has to be done in low memory situtations (OOM) or
36 // to create TypeInitializationExceptions due to failure of a static class
37 // constructor. This type needs to be extremely careful and assume that
38 // any type it references may have previously failed to construct, so statics
39 // belonging to that type may not be initialized. FrameworkEventSource.Log
40 // is one such example.
42 internal class ManifestBasedResourceGroveler : IResourceGroveler
44 private ResourceManager.ResourceManagerMediator _mediator;
46 public ManifestBasedResourceGroveler(ResourceManager.ResourceManagerMediator mediator)
48 // here and below: convert asserts to preconditions where appropriate when we get
49 // contracts story in place.
50 Debug.Assert(mediator != null, "mediator shouldn't be null; check caller");
54 public ResourceSet GrovelForResourceSet(CultureInfo culture, Dictionary<String, ResourceSet> localResourceSets, bool tryParents, bool createIfNotExists, ref StackCrawlMark stackMark)
56 Debug.Assert(culture != null, "culture shouldn't be null; check caller");
57 Debug.Assert(localResourceSets != null, "localResourceSets shouldn't be null; check caller");
59 ResourceSet rs = null;
61 RuntimeAssembly satellite = null;
63 // 1. Fixups for ultimate fallbacks
64 CultureInfo lookForCulture = UltimateFallbackFixup(culture);
66 // 2. Look for satellite assembly or main assembly, as appropriate
67 if (lookForCulture.HasInvariantCultureName && _mediator.FallbackLoc == UltimateResourceFallbackLocation.MainAssembly)
69 // don't bother looking in satellites in this case
70 satellite = _mediator.MainAssembly;
72 #if RESOURCE_SATELLITE_CONFIG
73 // If our config file says the satellite isn't here, don't ask for it.
74 else if (!lookForCulture.HasInvariantCultureName && !_mediator.TryLookingForSatellite(lookForCulture))
81 satellite = GetSatelliteAssembly(lookForCulture, ref stackMark);
83 if (satellite == null)
85 bool raiseException = (culture.HasInvariantCultureName && (_mediator.FallbackLoc == UltimateResourceFallbackLocation.Satellite));
86 // didn't find satellite, give error if necessary
89 HandleSatelliteMissing();
94 // get resource file name we'll search for. Note, be careful if you're moving this statement
95 // around because lookForCulture may be modified from originally requested culture above.
96 String fileName = _mediator.GetResourceFileName(lookForCulture);
98 // 3. If we identified an assembly to search; look in manifest resource stream for resource file
99 if (satellite != null)
101 // Handle case in here where someone added a callback for assembly load events.
102 // While no other threads have called into GetResourceSet, our own thread can!
103 // At that point, we could already have an RS in our hash table, and we don't
104 // want to add it twice.
105 lock (localResourceSets)
107 localResourceSets.TryGetValue(culture.Name, out rs);
110 stream = GetManifestResourceStream(satellite, fileName, ref stackMark);
113 // 4a. Found a stream; create a ResourceSet if possible
114 if (createIfNotExists && stream != null && rs == null)
116 rs = CreateResourceSet(stream, satellite);
118 else if (stream == null && tryParents)
120 // 4b. Didn't find stream; give error if necessary
121 bool raiseException = culture.HasInvariantCultureName;
124 HandleResourceStreamMissing(fileName);
131 private CultureInfo UltimateFallbackFixup(CultureInfo lookForCulture)
133 CultureInfo returnCulture = lookForCulture;
135 // If our neutral resources were written in this culture AND we know the main assembly
136 // does NOT contain neutral resources, don't probe for this satellite.
137 if (lookForCulture.Name == _mediator.NeutralResourcesCulture.Name &&
138 _mediator.FallbackLoc == UltimateResourceFallbackLocation.MainAssembly)
140 returnCulture = CultureInfo.InvariantCulture;
142 else if (lookForCulture.HasInvariantCultureName && _mediator.FallbackLoc == UltimateResourceFallbackLocation.Satellite)
144 returnCulture = _mediator.NeutralResourcesCulture;
147 return returnCulture;
150 internal static CultureInfo GetNeutralResourcesLanguage(Assembly a, ref UltimateResourceFallbackLocation fallbackLocation)
152 Debug.Assert(a != null, "assembly != null");
153 string cultureName = null;
155 if (GetNeutralResourcesLanguageAttribute(((RuntimeAssembly)a).GetNativeHandle(),
156 JitHelpers.GetStringHandleOnStack(ref cultureName),
159 if ((UltimateResourceFallbackLocation)fallback < UltimateResourceFallbackLocation.MainAssembly || (UltimateResourceFallbackLocation)fallback > UltimateResourceFallbackLocation.Satellite)
161 throw new ArgumentException(SR.Format(SR.Arg_InvalidNeutralResourcesLanguage_FallbackLoc, fallback));
163 fallbackLocation = (UltimateResourceFallbackLocation)fallback;
167 fallbackLocation = UltimateResourceFallbackLocation.MainAssembly;
168 return CultureInfo.InvariantCulture;
173 CultureInfo c = CultureInfo.GetCultureInfo(cultureName);
176 catch (ArgumentException e)
177 { // we should catch ArgumentException only.
178 // Note we could go into infinite loops if mscorlib's
179 // NeutralResourcesLanguageAttribute is mangled. If this assert
180 // fires, please fix the build process for the BCL directory.
181 if (a == typeof(Object).Assembly)
183 Debug.Fail(System.CoreLib.Name + "'s NeutralResourcesLanguageAttribute is a malformed culture name! name: \"" + cultureName + "\" Exception: " + e);
184 return CultureInfo.InvariantCulture;
187 throw new ArgumentException(SR.Format(SR.Arg_InvalidNeutralResourcesLanguage_Asm_Culture, a.ToString(), cultureName), e);
191 // Constructs a new ResourceSet for a given file name.
192 // Use the assembly to resolve assembly manifest resource references.
193 // Note that is can be null, but probably shouldn't be.
194 // This method could use some refactoring. One thing at a time.
195 internal ResourceSet CreateResourceSet(Stream store, Assembly assembly)
197 Debug.Assert(store != null, "I need a Stream!");
198 // Check to see if this is a Stream the ResourceManager understands,
199 // and check for the correct resource reader type.
200 if (store.CanSeek && store.Length > 4)
202 long startPos = store.Position;
204 // not disposing because we want to leave stream open
205 BinaryReader br = new BinaryReader(store);
207 // Look for our magic number as a little endian Int32.
208 int bytes = br.ReadInt32();
209 if (bytes == ResourceManager.MagicNumber)
211 int resMgrHeaderVersion = br.ReadInt32();
212 String readerTypeName = null, resSetTypeName = null;
213 if (resMgrHeaderVersion == ResourceManager.HeaderVersionNumber)
215 br.ReadInt32(); // We don't want the number of bytes to skip.
216 readerTypeName = System.CoreLib.FixupCoreLibName(br.ReadString());
217 resSetTypeName = System.CoreLib.FixupCoreLibName(br.ReadString());
219 else if (resMgrHeaderVersion > ResourceManager.HeaderVersionNumber)
221 // Assume that the future ResourceManager headers will
222 // have two strings for us - the reader type name and
223 // resource set type name. Read those, then use the num
224 // bytes to skip field to correct our position.
225 int numBytesToSkip = br.ReadInt32();
226 long endPosition = br.BaseStream.Position + numBytesToSkip;
228 readerTypeName = System.CoreLib.FixupCoreLibName(br.ReadString());
229 resSetTypeName = System.CoreLib.FixupCoreLibName(br.ReadString());
231 br.BaseStream.Seek(endPosition, SeekOrigin.Begin);
235 // resMgrHeaderVersion is older than this ResMgr version.
236 // We should add in backwards compatibility support here.
238 throw new NotSupportedException(SR.Format(SR.NotSupported_ObsoleteResourcesFile, _mediator.MainAssembly.GetSimpleName()));
241 store.Position = startPos;
242 // Perf optimization - Don't use Reflection for our defaults.
243 // Note there are two different sets of strings here - the
244 // assembly qualified strings emitted by ResourceWriter, and
245 // the abbreviated ones emitted by InternalResGen.
246 if (CanUseDefaultResourceClasses(readerTypeName, resSetTypeName))
248 return new RuntimeResourceSet(store);
252 // we do not want to use partial binding here.
253 Type readerType = Type.GetType(readerTypeName, true);
254 Object[] args = new Object[1];
256 IResourceReader reader = (IResourceReader)Activator.CreateInstance(readerType, args);
258 Object[] resourceSetArgs = new Object[1];
259 resourceSetArgs[0] = reader;
262 if (_mediator.UserResourceSet == null)
264 Debug.Assert(resSetTypeName != null, "We should have a ResourceSet type name from the custom resource file here.");
265 resSetType = Type.GetType(resSetTypeName, true, false);
268 resSetType = _mediator.UserResourceSet;
269 ResourceSet rs = (ResourceSet)Activator.CreateInstance(resSetType,
270 BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance,
280 store.Position = startPos;
284 if (_mediator.UserResourceSet == null)
286 return new RuntimeResourceSet(store);
290 Object[] args = new Object[2];
295 ResourceSet rs = null;
296 // Add in a check for a constructor taking in an assembly first.
299 rs = (ResourceSet)Activator.CreateInstance(_mediator.UserResourceSet, args);
302 catch (MissingMethodException) { }
304 args = new Object[1];
306 rs = (ResourceSet)Activator.CreateInstance(_mediator.UserResourceSet, args);
310 catch (MissingMethodException e)
312 throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResMgrBadResSet_Type, _mediator.UserResourceSet.AssemblyQualifiedName), e);
317 private Stream GetManifestResourceStream(RuntimeAssembly satellite, String fileName, ref StackCrawlMark stackMark)
319 Debug.Assert(satellite != null, "satellite shouldn't be null; check caller");
320 Debug.Assert(fileName != null, "fileName shouldn't be null; check caller");
322 // If we're looking in the main assembly AND if the main assembly was the person who
323 // created the ResourceManager, skip a security check for private manifest resources.
324 bool canSkipSecurityCheck = (_mediator.MainAssembly == satellite)
325 && (_mediator.CallingAssembly == _mediator.MainAssembly);
327 Stream stream = satellite.GetManifestResourceStream(_mediator.LocationInfo, fileName, canSkipSecurityCheck, ref stackMark);
330 stream = CaseInsensitiveManifestResourceStreamLookup(satellite, fileName);
336 // Looks up a .resources file in the assembly manifest using
337 // case-insensitive lookup rules. Yes, this is slow. The metadata
338 // dev lead refuses to make all assembly manifest resource lookups case-insensitive,
339 // even optionally case-insensitive.
340 [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
341 private Stream CaseInsensitiveManifestResourceStreamLookup(RuntimeAssembly satellite, String name)
343 Debug.Assert(satellite != null, "satellite shouldn't be null; check caller");
344 Debug.Assert(name != null, "name shouldn't be null; check caller");
346 StringBuilder sb = new StringBuilder();
347 if (_mediator.LocationInfo != null)
349 String nameSpace = _mediator.LocationInfo.Namespace;
350 if (nameSpace != null)
352 sb.Append(nameSpace);
354 sb.Append(Type.Delimiter);
359 String givenName = sb.ToString();
360 String canonicalName = null;
361 foreach (String existingName in satellite.GetManifestResourceNames())
363 if (String.Equals(existingName, givenName, StringComparison.InvariantCultureIgnoreCase))
365 if (canonicalName == null)
367 canonicalName = existingName;
371 throw new MissingManifestResourceException(SR.Format(SR.MissingManifestResource_MultipleBlobs, givenName, satellite.ToString()));
376 if (canonicalName == null)
381 // If we're looking in the main assembly AND if the main
382 // assembly was the person who created the ResourceManager,
383 // skip a security check for private manifest resources.
384 bool canSkipSecurityCheck = _mediator.MainAssembly == satellite && _mediator.CallingAssembly == _mediator.MainAssembly;
385 StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
386 return satellite.GetManifestResourceStream(canonicalName, ref stackMark, canSkipSecurityCheck);
389 private RuntimeAssembly GetSatelliteAssembly(CultureInfo lookForCulture, ref StackCrawlMark stackMark)
391 if (!_mediator.LookedForSatelliteContractVersion)
393 _mediator.SatelliteContractVersion = _mediator.ObtainSatelliteContractVersion(_mediator.MainAssembly);
394 _mediator.LookedForSatelliteContractVersion = true;
397 RuntimeAssembly satellite = null;
398 String satAssemblyName = GetSatelliteAssemblyName();
400 // Look up the satellite assembly, but don't let problems
401 // like a partially signed satellite assembly stop us from
402 // doing fallback and displaying something to the user.
403 // Yet also somehow log this error for a developer.
406 satellite = _mediator.MainAssembly.InternalGetSatelliteAssembly(satAssemblyName, lookForCulture, _mediator.SatelliteContractVersion, false, ref stackMark);
409 // Jun 08: for cases other than ACCESS_DENIED, we'll assert instead of throw to give release builds more opportunity to fallback.
411 catch (FileLoadException fle)
413 // Ignore cases where the loader gets an access
414 // denied back from the OS. This showed up for
415 // href-run exe's at one point.
416 int hr = fle._HResult;
417 if (hr != Win32Marshal.MakeHRFromErrorCode(Win32Native.ERROR_ACCESS_DENIED))
419 Debug.Fail("[This assert catches satellite assembly build/deployment problems - report this message to your build lab & loc engineer]" + Environment.NewLine + "GetSatelliteAssembly failed for culture " + lookForCulture.Name + " and version " + (_mediator.SatelliteContractVersion == null ? _mediator.MainAssembly.GetVersion().ToString() : _mediator.SatelliteContractVersion.ToString()) + " of assembly " + _mediator.MainAssembly.GetSimpleName() + " with error code 0x" + hr.ToString("X", CultureInfo.InvariantCulture) + Environment.NewLine + "Exception: " + fle);
423 // Don't throw for zero-length satellite assemblies, for compat with v1
424 catch (BadImageFormatException bife)
426 Debug.Fail("[This assert catches satellite assembly build/deployment problems - report this message to your build lab & loc engineer]" + Environment.NewLine + "GetSatelliteAssembly failed for culture " + lookForCulture.Name + " and version " + (_mediator.SatelliteContractVersion == null ? _mediator.MainAssembly.GetVersion().ToString() : _mediator.SatelliteContractVersion.ToString()) + " of assembly " + _mediator.MainAssembly.GetSimpleName() + Environment.NewLine + "Exception: " + bife);
432 // Perf optimization - Don't use Reflection for most cases with
433 // our .resources files. This makes our code run faster and we can
434 // creating a ResourceReader via Reflection. This would incur
435 // a security check (since the link-time check on the constructor that
436 // takes a String is turned into a full demand with a stack walk)
437 // and causes partially trusted localized apps to fail.
438 private bool CanUseDefaultResourceClasses(String readerTypeName, String resSetTypeName)
440 Debug.Assert(readerTypeName != null, "readerTypeName shouldn't be null; check caller");
441 Debug.Assert(resSetTypeName != null, "resSetTypeName shouldn't be null; check caller");
443 if (_mediator.UserResourceSet != null)
446 // Ignore the actual version of the ResourceReader and
447 // RuntimeResourceSet classes. Let those classes deal with
448 // versioning themselves.
449 AssemblyName mscorlib = new AssemblyName(ResourceManager.MscorlibName);
451 if (readerTypeName != null)
453 if (!ResourceManager.CompareNames(readerTypeName, ResourceManager.ResReaderTypeName, mscorlib))
457 if (resSetTypeName != null)
459 if (!ResourceManager.CompareNames(resSetTypeName, ResourceManager.ResSetTypeName, mscorlib))
466 private String GetSatelliteAssemblyName()
468 String satAssemblyName = _mediator.MainAssembly.GetSimpleName();
469 satAssemblyName += ".resources";
470 return satAssemblyName;
473 private void HandleSatelliteMissing()
475 String satAssemName = _mediator.MainAssembly.GetSimpleName() + ".resources.dll";
476 if (_mediator.SatelliteContractVersion != null)
478 satAssemName += ", Version=" + _mediator.SatelliteContractVersion.ToString();
481 AssemblyName an = new AssemblyName();
482 an.SetPublicKey(_mediator.MainAssembly.GetPublicKey());
483 byte[] token = an.GetPublicKeyToken();
485 int iLen = token.Length;
486 StringBuilder publicKeyTok = new StringBuilder(iLen * 2);
487 for (int i = 0; i < iLen; i++)
489 publicKeyTok.Append(token[i].ToString("x", CultureInfo.InvariantCulture));
491 satAssemName += ", PublicKeyToken=" + publicKeyTok;
493 String missingCultureName = _mediator.NeutralResourcesCulture.Name;
494 if (missingCultureName.Length == 0)
496 missingCultureName = "<invariant>";
498 throw new MissingSatelliteAssemblyException(SR.Format(SR.MissingSatelliteAssembly_Culture_Name, _mediator.NeutralResourcesCulture, satAssemName), missingCultureName);
501 private void HandleResourceStreamMissing(String fileName)
503 // Keep people from bothering me about resources problems
504 if (_mediator.MainAssembly == typeof(Object).Assembly && _mediator.BaseName.Equals(System.CoreLib.Name))
506 // This would break CultureInfo & all our exceptions.
507 Debug.Fail("Couldn't get " + System.CoreLib.Name + ResourceManager.ResFileExtension + " from " + System.CoreLib.Name + "'s assembly" + Environment.NewLine + Environment.NewLine + "Are you building the runtime on your machine? Chances are the BCL directory didn't build correctly. Type 'build -c' in the BCL directory. If you get build errors, look at buildd.log. If you then can't figure out what's wrong (and you aren't changing the assembly-related metadata code), ask a BCL dev.\n\nIf you did NOT build the runtime, you shouldn't be seeing this and you've found a bug.");
509 // We cannot continue further - simply FailFast.
510 string mesgFailFast = System.CoreLib.Name + ResourceManager.ResFileExtension + " couldn't be found! Large parts of the BCL won't work!";
511 System.Environment.FailFast(mesgFailFast);
513 // We really don't think this should happen - we always
514 // expect the neutral locale's resources to be present.
515 String resName = String.Empty;
516 if (_mediator.LocationInfo != null && _mediator.LocationInfo.Namespace != null)
517 resName = _mediator.LocationInfo.Namespace + Type.Delimiter;
519 throw new MissingManifestResourceException(SR.Format(SR.MissingManifestResource_NoNeutralAsm, resName, _mediator.MainAssembly.GetSimpleName()));
522 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
523 [return: MarshalAs(UnmanagedType.Bool)]
524 internal static extern bool GetNeutralResourcesLanguageAttribute(RuntimeAssembly assemblyHandle, StringHandleOnStack cultureName, out short fallbackLocation);