DefaultModuleLoader.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. #nullable enable
  2. using System;
  3. using System.IO;
  4. using Esprima;
  5. using Esprima.Ast;
  6. namespace Jint.Runtime.Modules;
  7. public sealed class DefaultModuleLoader : IModuleLoader
  8. {
  9. private readonly Uri _basePath;
  10. private readonly bool _restrictToBasePath;
  11. public DefaultModuleLoader(string basePath) : this(basePath, true)
  12. {
  13. }
  14. public DefaultModuleLoader(string basePath, bool restrictToBasePath)
  15. {
  16. if (string.IsNullOrWhiteSpace(basePath))
  17. {
  18. ExceptionHelper.ThrowArgumentException("Value cannot be null or whitespace.", nameof(basePath));
  19. }
  20. _restrictToBasePath = restrictToBasePath;
  21. if (!Uri.TryCreate(basePath, UriKind.Absolute, out _basePath))
  22. {
  23. if (!Path.IsPathRooted(basePath))
  24. {
  25. ExceptionHelper.ThrowArgumentException("Path must be rooted", nameof(basePath));
  26. }
  27. basePath = Path.GetFullPath(basePath);
  28. _basePath = new Uri(basePath, UriKind.Absolute);
  29. }
  30. if (_basePath.AbsolutePath[_basePath.AbsolutePath.Length - 1] != '/')
  31. {
  32. var uriBuilder = new UriBuilder(_basePath);
  33. uriBuilder.Path += '/';
  34. _basePath = uriBuilder.Uri;
  35. }
  36. }
  37. public ResolvedSpecifier Resolve(string? referencingModuleLocation, string specifier)
  38. {
  39. if (string.IsNullOrEmpty(specifier))
  40. {
  41. ExceptionHelper.ThrowModuleResolutionException("Invalid Module Specifier", specifier, referencingModuleLocation);
  42. return default;
  43. }
  44. // Specifications from ESM_RESOLVE Algorithm: https://nodejs.org/api/esm.html#resolution-algorithm
  45. Uri resolved;
  46. if (Uri.TryCreate(specifier, UriKind.Absolute, out var uri))
  47. {
  48. resolved = uri;
  49. }
  50. else if (IsRelative(specifier))
  51. {
  52. resolved = new Uri(referencingModuleLocation != null ? new Uri(referencingModuleLocation, UriKind.Absolute) : _basePath, specifier);
  53. }
  54. else if (specifier[0] == '#')
  55. {
  56. ExceptionHelper.ThrowNotSupportedException($"PACKAGE_IMPORTS_RESOLVE is not supported: '{specifier}'");
  57. return default;
  58. }
  59. else
  60. {
  61. return new ResolvedSpecifier(
  62. specifier,
  63. specifier,
  64. null,
  65. SpecifierType.Bare
  66. );
  67. }
  68. if (resolved.IsFile)
  69. {
  70. if (resolved.UserEscaped)
  71. {
  72. ExceptionHelper.ThrowModuleResolutionException("Invalid Module Specifier", specifier, referencingModuleLocation);
  73. return default;
  74. }
  75. if (!Path.HasExtension(resolved.LocalPath))
  76. {
  77. ExceptionHelper.ThrowModuleResolutionException("Unsupported Directory Import", specifier, referencingModuleLocation);
  78. return default;
  79. }
  80. }
  81. if (_restrictToBasePath && !_basePath.IsBaseOf(resolved))
  82. {
  83. ExceptionHelper.ThrowModuleResolutionException($"Unauthorized Module Path", specifier, referencingModuleLocation);
  84. return default;
  85. }
  86. return new ResolvedSpecifier(
  87. specifier,
  88. resolved.AbsoluteUri,
  89. resolved,
  90. SpecifierType.RelativeOrAbsolute
  91. );
  92. }
  93. public Module LoadModule(Engine engine, ResolvedSpecifier resolved)
  94. {
  95. if (resolved.Type != SpecifierType.RelativeOrAbsolute)
  96. {
  97. ExceptionHelper.ThrowNotSupportedException($"The default module loader can only resolve files. You can define modules directly to allow imports using {nameof(Engine)}.{nameof(Engine.AddModule)}(). Attempted to resolve: '{resolved.Specifier}'.");
  98. return default;
  99. }
  100. if (resolved.Uri == null)
  101. {
  102. ExceptionHelper.ThrowInvalidOperationException($"Module '{resolved.Specifier}' of type '{resolved.Type}' has no resolved URI.");
  103. }
  104. if (!File.Exists(resolved.Uri.AbsolutePath))
  105. {
  106. ExceptionHelper.ThrowArgumentException("Module Not Found: ", resolved.Specifier);
  107. return default;
  108. }
  109. var code = File.ReadAllText(resolved.Uri.LocalPath);
  110. Module module;
  111. try
  112. {
  113. var parserOptions = new ParserOptions(resolved.Uri.LocalPath);
  114. module = new JavaScriptParser(code, parserOptions).ParseModule();
  115. }
  116. catch (ParserException ex)
  117. {
  118. ExceptionHelper.ThrowSyntaxError(engine.Realm, $"Error while loading module: error in module '{resolved.Uri.LocalPath}': {ex.Error}");
  119. module = null;
  120. }
  121. catch (Exception)
  122. {
  123. ExceptionHelper.ThrowJavaScriptException(engine, $"Could not load module {resolved.Uri?.LocalPath}", Completion.Empty());
  124. module = null;
  125. }
  126. return module;
  127. }
  128. private static bool IsRelative(string specifier)
  129. {
  130. return specifier.StartsWith(".") || specifier.StartsWith("/");
  131. }
  132. }