| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -124,24 +124,25 @@ replaced. | |||
| 124 | 124 | ||
| 125 | 125 | ```json | |
| 126 | 126 | { | |
| 127 | - "builtins": [], | ||
| 128 | 127 | "resources": { | |
| 129 | 128 | "./app/checked.js": { | |
| 130 | 129 | "dependencies": { | |
| 131 | 130 | "fs": true, | |
| 132 | - "os": "./app/node_modules/alt-os" | ||
| 131 | + "os": "./app/node_modules/alt-os", | ||
| 132 | + "http": { "import": true } | ||
| 133 | 133 | } | |
| 134 | 134 | } | |
| 135 | 135 | } | |
| 136 | 136 | } | |
| 137 | 137 | ``` | |
| 138 | 138 | ||
| 139 | - The dependencies are keyed by the requested string specifier and have values | ||
| 140 | - of either `true` or a string pointing to a module that will be resolved. | ||
| 139 | + The dependencies are keyed by the requested specifier string and have values | ||
| 140 | + of either `true`, `null`, a string pointing to a module that will be resolved, | ||
| 141 | + or a conditions object. | ||
| 141 | 142 | ||
| 142 | 143 | The specifier string does not perform any searching and must match exactly | |
| 143 | - what is provided to the `require()`. Therefore, multiple specifiers may be | ||
| 144 | - needed in the policy if `require()` uses multiple different strings to point | ||
| 144 | + what is provided to the `require()` or `import`. Therefore, multiple specifiers | ||
| 145 | + may be needed in the policy if it uses multiple different strings to point | ||
| 145 | 146 | to the same module (such as excluding the extension). | |
| 146 | 147 | ||
| 147 | 148 | If the value of the redirection is `true` the default searching algorithms will | |
@@ -150,20 +151,31 @@ be used to find the module. | |||
| 150 | 151 | If the value of the redirection is a string, it will be resolved relative to | |
| 151 | 152 | the manifest and then immediately be used without searching. | |
| 152 | 153 | ||
| 153 | - Any specifier string that is `require()`ed and not listed in the dependencies | ||
| 154 | - will result in an error according to the policy. | ||
| 154 | + Any specifier string that is attempted to resolved and not listed in the | ||
| 155 | + dependencies will result in an error according to the policy. | ||
| 155 | 156 | ||
| 156 | 157 | Redirection will not prevent access to APIs through means such as direct access | |
| 157 | 158 | to `require.cache` and/or through `module.constructor` which allow access to | |
| 158 | - loading modules. Policy redirection only affect specifiers to `require()`. | ||
| 159 | - Other means such as to prevent undesired access to APIs through variables are | ||
| 160 | - necessary to lock down that path of loading modules. | ||
| 159 | + loading modules. Policy redirection only affect specifiers to `require()` and | ||
| 160 | + `import`. Other means such as to prevent undesired access to APIs through | ||
| 161 | + variables are necessary to lock down that path of loading modules. | ||
| 161 | 162 | ||
| 162 | 163 | A boolean value of `true` for the dependencies map can be specified to allow a | |
| 163 | 164 | module to load any specifier without redirection. This can be useful for local | |
| 164 | 165 | development and may have some valid usage in production, but should be used | |
| 165 | 166 | only with care after auditing a module to ensure its behavior is valid. | |
| 166 | 167 | ||
| 168 | + Similar to `"exports"` in `package.json` dependencies can also be specified to | ||
| 169 | + be objects containing conditions which branch how dependencies are loaded. In | ||
| 170 | + the above example `"http"` will be allowed when the `"import"` condition is | ||
| 171 | + part of loading it. | ||
| 172 | + | ||
| 173 | + A value of `null` for the resolved value will cause the resolution to fail. | ||
| 174 | + This can be used to ensure some kinds dynamic access are explicitly prevented. | ||
| 175 | + | ||
| 176 | + Unknown values for the resolved module location will cause failure, but are | ||
| 177 | + not guaranteed to be forwards compatible. | ||
| 178 | + | ||
| 167 | 179 | #### Example: Patched dependency | |
| 168 | 180 | ||
| 169 | 181 | Redirected dependencies can provide attenuated or modified functionality as fits | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1205,7 +1205,8 @@ E('ERR_MANIFEST_ASSERT_INTEGRITY', | |||
| 1205 | 1205 | return msg; | |
| 1206 | 1206 | }, Error); | |
| 1207 | 1207 | E('ERR_MANIFEST_DEPENDENCY_MISSING', | |
| 1208 | - 'Manifest resource %s does not list %s as a dependency specifier', | ||
| 1208 | + 'Manifest resource %s does not list %s as a dependency specifier for ' + | ||
| 1209 | + 'conditions: %s', | ||
| 1209 | 1210 | Error); | |
| 1210 | 1211 | E('ERR_MANIFEST_INTEGRITY_MISMATCH', | |
| 1211 | 1212 | 'Manifest resource %s has multiple entries but integrity lists do not match', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,11 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | + ArrayPrototypeJoin, | ||
| 4 | 5 | ObjectDefineProperty, | |
| 5 | 6 | ObjectPrototypeHasOwnProperty, | |
| 6 | 7 | SafeMap, | |
| 8 | + SafeSet, | ||
| 7 | 9 | } = primordials; | |
| 8 | 10 | const { | |
| 9 | 11 | ERR_MANIFEST_DEPENDENCY_MISSING, | |
@@ -16,10 +18,16 @@ const path = require('path'); | |||
| 16 | 18 | const { pathToFileURL, fileURLToPath } = require('internal/url'); | |
| 17 | 19 | const { URL } = require('url'); | |
| 18 | 20 | ||
| 21 | + const { getOptionValue } = require('internal/options'); | ||
| 22 | + const userConditions = getOptionValue('--conditions'); | ||
| 23 | + | ||
| 19 | 24 | let debug = require('internal/util/debuglog').debuglog('module', (fn) => { | |
| 20 | 25 | debug = fn; | |
| 21 | 26 | }); | |
| 22 | 27 | ||
| 28 | + // TODO: Use this set when resolving pkg#exports conditions in loader.js. | ||
| 29 | + const cjsConditions = new SafeSet(['require', 'node', ...userConditions]); | ||
| 30 | + | ||
| 23 | 31 | function loadNativeModule(filename, request) { | |
| 24 | 32 | const mod = NativeModule.map.get(filename); | |
| 25 | 33 | if (mod) { | |
@@ -38,11 +46,12 @@ function makeRequireFunction(mod, redirects) { | |||
| 38 | 46 | ||
| 39 | 47 | let require; | |
| 40 | 48 | if (redirects) { | |
| 41 | - const { resolve, reaction } = redirects; | ||
| 42 | 49 | const id = mod.filename || mod.id; | |
| 43 | - require = function require(path) { | ||
| 50 | + const conditions = cjsConditions; | ||
| 51 | + const { resolve, reaction } = redirects; | ||
| 52 | + require = function require(specifier) { | ||
| 44 | 53 | let missing = true; | |
| 45 | - const destination = resolve(path); | ||
| 54 | + const destination = resolve(specifier, conditions); | ||
| 46 | 55 | if (destination === true) { | |
| 47 | 56 | missing = false; | |
| 48 | 57 | } else if (destination) { | |
@@ -66,9 +75,13 @@ function makeRequireFunction(mod, redirects) { | |||
| 66 | 75 | } | |
| 67 | 76 | } | |
| 68 | 77 | if (missing) { | |
| 69 | - reaction(new ERR_MANIFEST_DEPENDENCY_MISSING(id, path)); | ||
| 78 | + reaction(new ERR_MANIFEST_DEPENDENCY_MISSING( | ||
| 79 | + id, | ||
| 80 | + specifier, | ||
| 81 | + ArrayPrototypeJoin([...conditions], ', ') | ||
| 82 | + )); | ||
| 70 | 83 | } | |
| 71 | - return mod.require(path); | ||
| 84 | + return mod.require(specifier); | ||
| 72 | 85 | }; | |
| 73 | 86 | } else { | |
| 74 | 87 | require = function require(path) { | |
@@ -168,6 +181,7 @@ function normalizeReferrerURL(referrer) { | |||
| 168 | 181 | ||
| 169 | 182 | module.exports = { | |
| 170 | 183 | addBuiltinLibsToObject, | |
| 184 | + cjsConditions, | ||
| 171 | 185 | loadNativeModule, | |
| 172 | 186 | makeRequireFunction, | |
| 173 | 187 | normalizeReferrerURL, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,7 +44,6 @@ const { | |||
| 44 | 44 | ReflectSet, | |
| 45 | 45 | RegExpPrototypeTest, | |
| 46 | 46 | SafeMap, | |
| 47 | - SafeSet, | ||
| 48 | 47 | String, | |
| 49 | 48 | StringPrototypeMatch, | |
| 50 | 49 | StringPrototypeSlice, | |
@@ -71,6 +70,7 @@ const { | |||
| 71 | 70 | makeRequireFunction, | |
| 72 | 71 | normalizeReferrerURL, | |
| 73 | 72 | stripBOM, | |
| 73 | + cjsConditions, | ||
| 74 | 74 | loadNativeModule | |
| 75 | 75 | } = require('internal/modules/cjs/helpers'); | |
| 76 | 76 | const { getOptionValue } = require('internal/options'); | |
@@ -81,7 +81,6 @@ const manifest = getOptionValue('--experimental-policy') ? | |||
| 81 | 81 | require('internal/process/policy').manifest : | |
| 82 | 82 | null; | |
| 83 | 83 | const { compileFunction } = internalBinding('contextify'); | |
| 84 | - const userConditions = getOptionValue('--conditions'); | ||
| 85 | 84 | ||
| 86 | 85 | // Whether any user-provided CJS modules had been loaded (executed). | |
| 87 | 86 | // Used for internal assertions. | |
@@ -803,7 +802,6 @@ Module._load = function(request, parent, isMain) { | |||
| 803 | 802 | return module.exports; | |
| 804 | 803 | }; | |
| 805 | 804 | ||
| 806 | - const cjsConditions = new SafeSet(['require', 'node', ...userConditions]); | ||
| 807 | 805 | Module._resolveFilename = function(request, parent, isMain, options) { | |
| 808 | 806 | if (NativeModule.canBeRequiredByUsers(request)) { | |
| 809 | 807 | return request; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -30,6 +30,9 @@ const { | |||
| 30 | 30 | Stats, | |
| 31 | 31 | } = require('fs'); | |
| 32 | 32 | const { getOptionValue } = require('internal/options'); | |
| 33 | + const manifest = getOptionValue('--experimental-policy') ? | ||
| 34 | + require('internal/process/policy').manifest : | ||
| 35 | + null; | ||
| 33 | 36 | const { sep, relative } = require('path'); | |
| 34 | 37 | const preserveSymlinks = getOptionValue('--preserve-symlinks'); | |
| 35 | 38 | const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); | |
@@ -41,6 +44,7 @@ const { | |||
| 41 | 44 | ERR_INVALID_MODULE_SPECIFIER, | |
| 42 | 45 | ERR_INVALID_PACKAGE_CONFIG, | |
| 43 | 46 | ERR_INVALID_PACKAGE_TARGET, | |
| 47 | + ERR_MANIFEST_DEPENDENCY_MISSING, | ||
| 44 | 48 | ERR_MODULE_NOT_FOUND, | |
| 45 | 49 | ERR_PACKAGE_IMPORT_NOT_DEFINED, | |
| 46 | 50 | ERR_PACKAGE_PATH_NOT_EXPORTED, | |
@@ -710,6 +714,27 @@ function resolveAsCommonJS(specifier, parentURL) { | |||
| 710 | 714 | ||
| 711 | 715 | function defaultResolve(specifier, context = {}, defaultResolveUnused) { | |
| 712 | 716 | let { parentURL, conditions } = context; | |
| 717 | + if (manifest) { | ||
| 718 | + const redirects = manifest.getRedirector(parentURL); | ||
| 719 | + if (redirects) { | ||
| 720 | + const { resolve, reaction } = redirects; | ||
| 721 | + const destination = resolve(specifier, new SafeSet(conditions)); | ||
| 722 | + let missing = true; | ||
| 723 | + if (destination === true) { | ||
| 724 | + missing = false; | ||
| 725 | + } else if (destination) { | ||
| 726 | + const href = destination.href; | ||
| 727 | + return { url: href }; | ||
| 728 | + } | ||
| 729 | + if (missing) { | ||
| 730 | + reaction(new ERR_MANIFEST_DEPENDENCY_MISSING( | ||
| 731 | + parentURL, | ||
| 732 | + specifier, | ||
| 733 | + ArrayPrototypeJoin([...conditions], ', ')) | ||
| 734 | + ); | ||
| 735 | + } | ||
| 736 | + } | ||
| 737 | + } | ||
| 713 | 738 | let parsed; | |
| 714 | 739 | try { | |
| 715 | 740 | parsed = new URL(specifier); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,11 +7,15 @@ const { | |||
| 7 | 7 | ObjectCreate, | |
| 8 | 8 | ObjectEntries, | |
| 9 | 9 | ObjectFreeze, | |
| 10 | + ObjectKeys, | ||
| 10 | 11 | ObjectSetPrototypeOf, | |
| 11 | 12 | RegExpPrototypeTest, | |
| 12 | 13 | SafeMap, | |
| 13 | 14 | uncurryThis, | |
| 14 | 15 | } = primordials; | |
| 16 | + const { | ||
| 17 | + compositeKey | ||
| 18 | + } = require('internal/util/compositekey'); | ||
| 15 | 19 | const { | |
| 16 | 20 | canBeRequiredByUsers | |
| 17 | 21 | } = require('internal/bootstrap/loaders').NativeModule; | |
@@ -70,13 +74,21 @@ class Manifest { | |||
| 70 | 74 | */ | |
| 71 | 75 | #integrities = new SafeMap(); | |
| 72 | 76 | /** | |
| 73 | - * @type {Map<string, (specifier: string) => true | URL>} | ||
| 77 | + * @type { | ||
| 78 | + Map< | ||
| 79 | + string, | ||
| 80 | + (specifier: string, conditions: Set<string>) => true | null | URL | ||
| 81 | + > | ||
| 82 | + } | ||
| 74 | 83 | * | |
| 75 | 84 | * Used to find where a dependency is located. | |
| 76 | 85 | * | |
| 77 | 86 | * This stores functions to lazily calculate locations as needed. | |
| 78 | 87 | * `true` is used to signify that the location is not specified | |
| 79 | 88 | * by the manifest and default resolution should be allowed. | |
| 89 | + * | ||
| 90 | + * The functions return `null` to signify that a dependency is | ||
| 91 | + * not found | ||
| 80 | 92 | */ | |
| 81 | 93 | #dependencies = new SafeMap(); | |
| 82 | 94 | /** | |
@@ -158,36 +170,83 @@ class Manifest { | |||
| 158 | 170 | dependencyMap = ObjectCreate(null); | |
| 159 | 171 | } | |
| 160 | 172 | if (typeof dependencyMap === 'object' && !ArrayIsArray(dependencyMap)) { | |
| 173 | + function searchDependencies(target, conditions) { | ||
| 174 | + if ( | ||
| 175 | + target && | ||
| 176 | + typeof target === 'object' && | ||
| 177 | + !ArrayIsArray(target) | ||
| 178 | + ) { | ||
| 179 | + const keys = ObjectKeys(target); | ||
| 180 | + for (let i = 0; i < keys.length; i++) { | ||
| 181 | + const key = keys[i]; | ||
| 182 | + if (conditions.has(key)) { | ||
| 183 | + const ret = searchDependencies(target[key], conditions); | ||
| 184 | + if (ret != null) { | ||
| 185 | + return ret; | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + } else if (typeof target === 'string') { | ||
| 190 | + return target; | ||
| 191 | + } else if (target === true) { | ||
| 192 | + return target; | ||
| 193 | + } else { | ||
| 194 | + throw new ERR_MANIFEST_INVALID_RESOURCE_FIELD( | ||
| 195 | + resourceHREF, | ||
| 196 | + 'dependencies'); | ||
| 197 | + } | ||
| 198 | + return null; | ||
| 199 | + } | ||
| 200 | + // This is used so we don't traverse this every time | ||
| 201 | + // in theory we can delete parts of the dep map once this is populated | ||
| 202 | + const localMappings = new SafeMap(); | ||
| 161 | 203 | /** | |
| 162 | - * @returns {true | URL} | ||
| 204 | + * @returns {true | null | URL} | ||
| 163 | 205 | */ | |
| 164 | - const dependencyRedirectList = (toSpecifier) => { | ||
| 165 | - if (toSpecifier in dependencyMap !== true) { | ||
| 206 | + const dependencyRedirectList = (specifier, conditions) => { | ||
| 207 | + const key = compositeKey([localMappings, specifier, ...conditions]); | ||
| 208 | + if (localMappings.has(key)) { | ||
| 209 | + return localMappings.get(key); | ||
| 210 | + } | ||
| 211 | + if (specifier in dependencyMap !== true) { | ||
| 212 | + localMappings.set(key, null); | ||
| 166 | 213 | return null; | |
| 167 | 214 | } | |
| 168 | - const to = dependencyMap[toSpecifier]; | ||
| 169 | - if (to === true) { | ||
| 215 | + const target = searchDependencies( | ||
| 216 | + dependencyMap[specifier], | ||
| 217 | + conditions); | ||
| 218 | + if (target === true) { | ||
| 219 | + localMappings.set(key, true); | ||
| 170 | 220 | return true; | |
| 171 | 221 | } | |
| 172 | - if (parsedURLs.has(to)) { | ||
| 173 | - return parsedURLs.get(to); | ||
| 174 | - } else if (canBeRequiredByUsers(to)) { | ||
| 175 | - const href = `node:${to}`; | ||
| 222 | + if (typeof target !== 'string') { | ||
| 223 | + localMappings.set(key, null); | ||
| 224 | + return null; | ||
| 225 | + } | ||
| 226 | + if (parsedURLs.has(target)) { | ||
| 227 | + const parsed = parsedURLs.get(target); | ||
| 228 | + localMappings.set(key, parsed); | ||
| 229 | + return parsed; | ||
| 230 | + } else if (canBeRequiredByUsers(target)) { | ||
| 231 | + const href = `node:${target}`; | ||
| 176 | 232 | const resolvedURL = new URL(href); | |
| 177 | - parsedURLs.set(to, resolvedURL); | ||
| 233 | + parsedURLs.set(target, resolvedURL); | ||
| 178 | 234 | parsedURLs.set(href, resolvedURL); | |
| 235 | + localMappings.set(key, resolvedURL); | ||
| 179 | 236 | return resolvedURL; | |
| 180 | - } else if (RegExpPrototypeTest(kRelativeURLStringPattern, to)) { | ||
| 181 | - const resolvedURL = new URL(to, manifestURL); | ||
| 237 | + } else if (RegExpPrototypeTest(kRelativeURLStringPattern, target)) { | ||
| 238 | + const resolvedURL = new URL(target, manifestURL); | ||
| 182 | 239 | const href = resourceURL.href; | |
| 183 | - parsedURLs.set(to, resolvedURL); | ||
| 240 | + parsedURLs.set(target, resolvedURL); | ||
| 184 | 241 | parsedURLs.set(href, resolvedURL); | |
| 242 | + localMappings.set(key, resolvedURL); | ||
| 185 | 243 | return resolvedURL; | |
| 186 | 244 | } | |
| 187 | - const resolvedURL = new URL(to); | ||
| 188 | - const href = resourceURL.href; | ||
| 189 | - parsedURLs.set(to, resolvedURL); | ||
| 245 | + const resolvedURL = new URL(target); | ||
| 246 | + const href = resolvedURL.href; | ||
| 247 | + parsedURLs.set(target, resolvedURL); | ||
| 190 | 248 | parsedURLs.set(href, resolvedURL); | |
| 249 | + localMappings.set(key, resolvedURL); | ||
| 191 | 250 | return resolvedURL; | |
| 192 | 251 | }; | |
| 193 | 252 | dependencies.set(resourceHREF, dependencyRedirectList); | |
@@ -208,7 +267,10 @@ class Manifest { | |||
| 208 | 267 | const dependencies = this.#dependencies; | |
| 209 | 268 | if (dependencies.has(requester)) { | |
| 210 | 269 | return { | |
| 211 | - resolve: (to) => dependencies.get(requester)(`${to}`), | ||
| 270 | + resolve: (specifier, conditions) => dependencies.get(requester)( | ||
| 271 | + `${specifier}`, | ||
| 272 | + conditions | ||
| 273 | + ), | ||
| 212 | 274 | reaction: this.#reaction | |
| 213 | 275 | }; | |
| 214 | 276 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments