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