1'use strict';
2
3const {
4  ArrayPrototypePush,
5  RegExpPrototypeExec,
6  decodeURIComponent,
7} = primordials;
8const { kEmptyObject } = require('internal/util');
9
10const { defaultGetFormat } = require('internal/modules/esm/get_format');
11const { validateAttributes, emitImportAssertionWarning } = require('internal/modules/esm/assert');
12const { getOptionValue } = require('internal/options');
13
14// Do not eagerly grab .manifest, it may be in TDZ
15const policy = getOptionValue('--experimental-policy') ?
16  require('internal/process/policy') :
17  null;
18const experimentalNetworkImports =
19  getOptionValue('--experimental-network-imports');
20
21const { Buffer: { from: BufferFrom } } = require('buffer');
22
23const { URL } = require('internal/url');
24const {
25  ERR_INVALID_URL,
26  ERR_UNKNOWN_MODULE_FORMAT,
27  ERR_UNSUPPORTED_ESM_URL_SCHEME,
28} = require('internal/errors').codes;
29
30const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
31
32/**
33 * @param {URL} url URL to the module
34 * @param {ESModuleContext} context used to decorate error messages
35 * @returns {{ responseURL: string, source: string | BufferView }}
36 */
37async function getSource(url, context) {
38  const { protocol, href } = url;
39  let responseURL = href;
40  let source;
41  if (protocol === 'file:') {
42    const { readFile: readFileAsync } = require('internal/fs/promises').exports;
43    source = await readFileAsync(url);
44  } else if (protocol === 'data:') {
45    const match = RegExpPrototypeExec(DATA_URL_PATTERN, url.pathname);
46    if (!match) {
47      throw new ERR_INVALID_URL(responseURL);
48    }
49    const { 1: base64, 2: body } = match;
50    source = BufferFrom(decodeURIComponent(body), base64 ? 'base64' : 'utf8');
51  } else if (experimentalNetworkImports && (
52    protocol === 'https:' ||
53    protocol === 'http:'
54  )) {
55    const { fetchModule } = require('internal/modules/esm/fetch_module');
56    const res = await fetchModule(url, context);
57    source = await res.body;
58    responseURL = res.resolvedHREF;
59  } else {
60    const supportedSchemes = ['file', 'data'];
61    if (experimentalNetworkImports) {
62      ArrayPrototypePush(supportedSchemes, 'http', 'https');
63    }
64    throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url, supportedSchemes);
65  }
66  if (policy?.manifest) {
67    policy.manifest.assertIntegrity(href, source);
68  }
69  return { __proto__: null, responseURL, source };
70}
71
72
73/**
74 * Node.js default load hook.
75 * @param {string} url
76 * @param {object} context
77 * @returns {object}
78 */
79async function defaultLoad(url, context = kEmptyObject) {
80  let responseURL = url;
81  let {
82    importAttributes,
83    format,
84    source,
85  } = context;
86
87  if (importAttributes == null && !('importAttributes' in context) && 'importAssertions' in context) {
88    emitImportAssertionWarning();
89    importAttributes = context.importAssertions;
90    // Alias `importAssertions` to `importAttributes`
91    context = {
92      ...context,
93      importAttributes,
94    };
95  }
96
97  const urlInstance = new URL(url);
98
99  throwIfUnsupportedURLScheme(urlInstance, experimentalNetworkImports);
100
101  format ??= await defaultGetFormat(urlInstance, context);
102
103  validateAttributes(url, format, importAttributes);
104
105  if (
106    format === 'builtin' ||
107    format === 'commonjs'
108  ) {
109    source = null;
110  } else if (source == null) {
111    ({ responseURL, source } = await getSource(urlInstance, context));
112  }
113
114  return {
115    __proto__: null,
116    format,
117    responseURL,
118    source,
119  };
120}
121
122
123/**
124 * throws an error if the protocol is not one of the protocols
125 * that can be loaded in the default loader
126 * @param {URL} parsed
127 * @param {boolean} experimentalNetworkImports
128 */
129function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) {
130  // Avoid accessing the `protocol` property due to the lazy getters.
131  const protocol = parsed?.protocol;
132  if (
133    protocol &&
134    protocol !== 'file:' &&
135    protocol !== 'data:' &&
136    protocol !== 'node:' &&
137    (
138      !experimentalNetworkImports ||
139      (
140        protocol !== 'https:' &&
141        protocol !== 'http:'
142      )
143    )
144  ) {
145    const schemes = ['file', 'data', 'node'];
146    if (experimentalNetworkImports) {
147      ArrayPrototypePush(schemes, 'https', 'http');
148    }
149    throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, schemes);
150  }
151}
152
153/**
154 * For a falsy `format` returned from `load`, throw an error.
155 * This could happen from either a custom user loader _or_ from the default loader, because the default loader tries to
156 * determine formats for data URLs.
157 * @param {string} url The resolved URL of the module
158 * @param {null | undefined | false | 0 | -0 | 0n | ''} format Falsy format returned from `load`
159 */
160function throwUnknownModuleFormat(url, format) {
161  const dataUrl = RegExpPrototypeExec(
162    /^data:([^/]+\/[^;,]+)(?:[^,]*?)(;base64)?,/,
163    url,
164  );
165
166  throw new ERR_UNKNOWN_MODULE_FORMAT(
167    dataUrl ? dataUrl[1] : format,
168    url);
169}
170
171
172module.exports = {
173  defaultLoad,
174  throwUnknownModuleFormat,
175};
176