@secure-exec/nodejs 0.2.0-rc.1 → 0.2.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/child-process.js +47 -1
- package/dist/bridge/fs.d.ts +6 -1
- package/dist/bridge/fs.js +12 -3
- package/dist/bridge/index.d.ts +2 -2
- package/dist/bridge/index.js +2 -2
- package/dist/bridge/network.d.ts +35 -1
- package/dist/bridge/network.js +471 -153
- package/dist/bridge/os.js +9 -3
- package/dist/bridge/polyfills.d.ts +6 -9
- package/dist/bridge/polyfills.js +616 -14
- package/dist/bridge/process.d.ts +3 -2
- package/dist/bridge/process.js +288 -61
- package/dist/bridge-contract.d.ts +21 -3
- package/dist/bridge-contract.js +2 -0
- package/dist/bridge-handlers.d.ts +10 -0
- package/dist/bridge-handlers.js +515 -179
- package/dist/bridge.js +1531 -855
- package/dist/builtin-modules.js +6 -0
- package/dist/esm-compiler.d.ts +1 -0
- package/dist/esm-compiler.js +29 -11
- package/dist/execution-driver.js +362 -10
- package/dist/host-network-adapter.js +10 -6
- package/dist/isolate-bootstrap.d.ts +6 -0
- package/dist/kernel-runtime.d.ts +3 -1
- package/dist/kernel-runtime.js +109 -11
- package/dist/module-access.d.ts +3 -0
- package/dist/module-access.js +52 -2
- package/dist/module-resolver.js +3 -3
- package/dist/module-source.d.ts +5 -0
- package/dist/module-source.js +224 -0
- package/dist/polyfills.js +27 -1
- package/package.json +5 -3
package/dist/bridge-handlers.js
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
// Each handler is a plain function that performs the host-side operation.
|
|
4
4
|
// Handler names match HOST_BRIDGE_GLOBAL_KEYS from the bridge contract.
|
|
5
5
|
import * as http from "node:http";
|
|
6
|
+
import * as https from "node:https";
|
|
6
7
|
import * as http2 from "node:http2";
|
|
7
8
|
import * as tls from "node:tls";
|
|
9
|
+
import * as hostUtil from "node:util";
|
|
10
|
+
import * as zlib from "node:zlib";
|
|
8
11
|
import { Duplex, PassThrough } from "node:stream";
|
|
9
12
|
import { readFileSync, realpathSync, existsSync } from "node:fs";
|
|
10
13
|
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from "node:path";
|
|
@@ -15,9 +18,9 @@ import { HOST_BRIDGE_GLOBAL_KEYS, } from "./bridge-contract.js";
|
|
|
15
18
|
import { AF_INET, AF_INET6, AF_UNIX, SOCK_DGRAM, SOCK_STREAM, mkdir, FDTableManager, O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND, FILETYPE_REGULAR_FILE, } from "@secure-exec/core";
|
|
16
19
|
import { normalizeBuiltinSpecifier } from "./builtin-modules.js";
|
|
17
20
|
import { resolveModule, loadFile } from "./package-bundler.js";
|
|
18
|
-
import { transformDynamicImport, isESM } from "@secure-exec/core/internal/shared/esm-utils";
|
|
19
21
|
import { bundlePolyfill, hasPolyfill } from "./polyfills.js";
|
|
20
|
-
import { createBuiltinESMWrapper, getStaticBuiltinWrapperSource, } from "./esm-compiler.js";
|
|
22
|
+
import { createBuiltinESMWrapper, getBuiltinBindingExpression, getStaticBuiltinWrapperSource, } from "./esm-compiler.js";
|
|
23
|
+
import { transformSourceForImport, transformSourceForImportSync, transformSourceForRequire, transformSourceForRequireSync, } from "./module-source.js";
|
|
21
24
|
import { checkBridgeBudget, assertPayloadByteLength, assertTextPayloadSize, getBase64EncodedByteLength, getHostBuiltinNamedExports, parseJsonWithLimit, polyfillCodeCache, RESOURCE_BUDGET_ERROR_CODE, } from "./isolate-bootstrap.js";
|
|
22
25
|
const SOL_SOCKET = 1;
|
|
23
26
|
const IPPROTO_TCP = 6;
|
|
@@ -1965,89 +1968,41 @@ function buildKernelSocketBridgeHandlers(dispatch, socketTable, pid) {
|
|
|
1965
1968
|
};
|
|
1966
1969
|
return { handlers, dispose };
|
|
1967
1970
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1971
|
+
function normalizeModuleResolveContext(referrer) {
|
|
1972
|
+
if (!referrer || referrer.endsWith("/")) {
|
|
1973
|
+
return referrer || "/";
|
|
1974
|
+
}
|
|
1975
|
+
return pathDirname(referrer) !== referrer && /\.[^/]+$/.test(referrer)
|
|
1976
|
+
? pathDirname(referrer)
|
|
1977
|
+
: referrer;
|
|
1975
1978
|
}
|
|
1976
|
-
function
|
|
1977
|
-
if (
|
|
1978
|
-
return
|
|
1979
|
-
let code = source;
|
|
1980
|
-
// Remove const __filename/dirname declarations (already provided by CJS wrapper)
|
|
1981
|
-
code = code.replace(/^\s*(?:const|let|var)\s+__filename\s*=\s*[^;]+;?\s*$/gm, "// __filename provided by CJS wrapper");
|
|
1982
|
-
code = code.replace(/^\s*(?:const|let|var)\s+__dirname\s*=\s*[^;]+;?\s*$/gm, "// __dirname provided by CJS wrapper");
|
|
1983
|
-
// import X from 'Y' → const X = require('Y')
|
|
1984
|
-
code = code.replace(/^\s*import\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?/gm, "const $1 = (function(m) { return m && m.__esModule ? m.default : m; })(require('$2'));");
|
|
1985
|
-
// import { a, b as c } from 'Y' → const { a, b: c } = require('Y')
|
|
1986
|
-
code = code.replace(/^\s*import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?/gm, (_match, imports, mod) => {
|
|
1987
|
-
const mapped = stripComments(imports).split(",").map((s) => {
|
|
1988
|
-
const t = s.trim();
|
|
1989
|
-
if (!t)
|
|
1990
|
-
return null;
|
|
1991
|
-
const parts = t.split(/\s+as\s+/);
|
|
1992
|
-
return parts.length === 2 ? `${parts[0].trim()}: ${parts[1].trim()}` : t;
|
|
1993
|
-
}).filter(Boolean).join(", ");
|
|
1994
|
-
return `const { ${mapped} } = require('${mod}');`;
|
|
1995
|
-
});
|
|
1996
|
-
// import * as X from 'Y' → const X = require('Y')
|
|
1997
|
-
code = code.replace(/^\s*import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]\s*;?/gm, "const $1 = require('$2');");
|
|
1998
|
-
// Side-effect imports: import 'Y' → require('Y')
|
|
1999
|
-
code = code.replace(/^\s*import\s+['"]([^'"]+)['"]\s*;?/gm, "require('$1');");
|
|
2000
|
-
// export { a, b } from 'Y' → re-export
|
|
2001
|
-
code = code.replace(/^\s*export\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]\s*;?/gm, (_match, exports, mod) => {
|
|
2002
|
-
return stripComments(exports).split(",").map((s) => {
|
|
2003
|
-
const t = s.trim();
|
|
2004
|
-
if (!t)
|
|
2005
|
-
return "";
|
|
2006
|
-
const parts = t.split(/\s+as\s+/);
|
|
2007
|
-
const local = parts[0].trim();
|
|
2008
|
-
const exported = parts.length === 2 ? parts[1].trim() : local;
|
|
2009
|
-
return `Object.defineProperty(exports, '${exported}', { get: () => require('${mod}').${local}, enumerable: true });`;
|
|
2010
|
-
}).filter(Boolean).join("\n");
|
|
2011
|
-
});
|
|
2012
|
-
// export * from 'Y'
|
|
2013
|
-
code = code.replace(/^\s*export\s+\*\s+from\s+['"]([^'"]+)['"]\s*;?/gm, "Object.assign(exports, require('$1'));");
|
|
2014
|
-
// export default X → module.exports.default = X
|
|
2015
|
-
code = code.replace(/^\s*export\s+default\s+/gm, "module.exports.default = ");
|
|
2016
|
-
// export const/let/var X = ... → const/let/var X = ...; exports.X = X;
|
|
2017
|
-
code = code.replace(/^\s*export\s+(const|let|var)\s+(\w+)\s*=/gm, "$1 $2 =");
|
|
2018
|
-
// Capture the names separately to add exports at the end
|
|
2019
|
-
const exportedVars = [];
|
|
2020
|
-
for (const m of source.matchAll(/^\s*export\s+(?:const|let|var)\s+(\w+)\s*=/gm)) {
|
|
2021
|
-
exportedVars.push(m[1]);
|
|
1979
|
+
function selectPackageExportTarget(entry, mode) {
|
|
1980
|
+
if (typeof entry === "string") {
|
|
1981
|
+
return entry;
|
|
2022
1982
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
1983
|
+
if (Array.isArray(entry)) {
|
|
1984
|
+
for (const candidate of entry) {
|
|
1985
|
+
const resolved = selectPackageExportTarget(candidate, mode);
|
|
1986
|
+
if (resolved) {
|
|
1987
|
+
return resolved;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
return null;
|
|
2027
1991
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
for (const m of source.matchAll(/^\s*export\s+class\s+(\w+)/gm)) {
|
|
2031
|
-
exportedVars.push(m[1]);
|
|
1992
|
+
if (!entry || typeof entry !== "object") {
|
|
1993
|
+
return null;
|
|
2032
1994
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
return `Object.defineProperty(exports, '${exported}', { get: () => ${local}, enumerable: true });`;
|
|
2043
|
-
}).filter(Boolean).join("\n");
|
|
2044
|
-
});
|
|
2045
|
-
// Append named exports for exported vars/functions/classes
|
|
2046
|
-
if (exportedVars.length > 0) {
|
|
2047
|
-
const lines = exportedVars.map((name) => `Object.defineProperty(exports, '${name}', { get: () => ${name}, enumerable: true });`);
|
|
2048
|
-
code += "\n" + lines.join("\n");
|
|
1995
|
+
const conditionalEntry = entry;
|
|
1996
|
+
const candidates = mode === "import"
|
|
1997
|
+
? [conditionalEntry.import, conditionalEntry.default, conditionalEntry.require]
|
|
1998
|
+
: [conditionalEntry.require, conditionalEntry.default, conditionalEntry.import];
|
|
1999
|
+
for (const candidate of candidates) {
|
|
2000
|
+
const resolved = selectPackageExportTarget(candidate, mode);
|
|
2001
|
+
if (resolved) {
|
|
2002
|
+
return resolved;
|
|
2003
|
+
}
|
|
2049
2004
|
}
|
|
2050
|
-
return
|
|
2005
|
+
return null;
|
|
2051
2006
|
}
|
|
2052
2007
|
/**
|
|
2053
2008
|
* Resolve a package specifier by walking up directories and reading package.json exports.
|
|
@@ -2067,16 +2022,16 @@ function resolvePackageExport(req, startDir, mode = "require") {
|
|
|
2067
2022
|
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
2068
2023
|
let entry;
|
|
2069
2024
|
if (pkg.exports) {
|
|
2070
|
-
const exportEntry =
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2025
|
+
const exportEntry = subpath === "." &&
|
|
2026
|
+
typeof pkg.exports === "object" &&
|
|
2027
|
+
pkg.exports !== null &&
|
|
2028
|
+
!Array.isArray(pkg.exports) &&
|
|
2029
|
+
!("." in pkg.exports)
|
|
2030
|
+
? pkg.exports
|
|
2031
|
+
: pkg.exports[subpath];
|
|
2032
|
+
const resolvedEntry = selectPackageExportTarget(exportEntry, mode);
|
|
2033
|
+
if (resolvedEntry)
|
|
2034
|
+
entry = resolvedEntry;
|
|
2080
2035
|
}
|
|
2081
2036
|
if (!entry && subpath === ".")
|
|
2082
2037
|
entry = pkg.main;
|
|
@@ -2111,8 +2066,11 @@ export function buildModuleResolutionBridgeHandlers(deps) {
|
|
|
2111
2066
|
if (builtin)
|
|
2112
2067
|
return builtin;
|
|
2113
2068
|
// Translate sandbox fromDir to host path for resolution context
|
|
2114
|
-
const
|
|
2115
|
-
const
|
|
2069
|
+
const referrer = String(fromDir);
|
|
2070
|
+
const sandboxDir = normalizeModuleResolveContext(referrer);
|
|
2071
|
+
const hostDir = normalizeModuleResolveContext(deps.sandboxToHostPath(referrer) ??
|
|
2072
|
+
deps.sandboxToHostPath(sandboxDir) ??
|
|
2073
|
+
sandboxDir);
|
|
2116
2074
|
const resolveFromExports = (dir) => {
|
|
2117
2075
|
const resolved = resolvePackageExport(req, dir, resolveMode);
|
|
2118
2076
|
return resolved ? deps.hostToSandboxPath(resolved) : null;
|
|
@@ -2156,20 +2114,12 @@ export function buildModuleResolutionBridgeHandlers(deps) {
|
|
|
2156
2114
|
catch { /* fallback failed */ }
|
|
2157
2115
|
return null;
|
|
2158
2116
|
};
|
|
2159
|
-
// Sync file read — translates sandbox path and
|
|
2160
|
-
//
|
|
2161
|
-
// for npm packages so require() can load ESM-only dependencies.
|
|
2117
|
+
// Sync file read — translates sandbox path and applies parser-backed
|
|
2118
|
+
// CJS transforms when require() needs ESM or import() support.
|
|
2162
2119
|
handlers[K.loadFileSync] = (filePath) => {
|
|
2163
2120
|
const sandboxPath = String(filePath);
|
|
2164
2121
|
const hostPath = deps.sandboxToHostPath(sandboxPath) ?? sandboxPath;
|
|
2165
|
-
|
|
2166
|
-
let source = readFileSync(hostPath, "utf-8");
|
|
2167
|
-
source = convertEsmToCjs(source, hostPath);
|
|
2168
|
-
return transformDynamicImport(source);
|
|
2169
|
-
}
|
|
2170
|
-
catch {
|
|
2171
|
-
return null;
|
|
2172
|
-
}
|
|
2122
|
+
return loadHostModuleSourceSync(hostPath, sandboxPath, "require");
|
|
2173
2123
|
};
|
|
2174
2124
|
return handlers;
|
|
2175
2125
|
}
|
|
@@ -2228,6 +2178,53 @@ export function buildConsoleBridgeHandlers(deps) {
|
|
|
2228
2178
|
};
|
|
2229
2179
|
return handlers;
|
|
2230
2180
|
}
|
|
2181
|
+
function getStaticBuiltinRequireSource(moduleName) {
|
|
2182
|
+
switch (moduleName) {
|
|
2183
|
+
case "fs":
|
|
2184
|
+
return "module.exports = globalThis.bridge?.fs || globalThis.bridge?.default || {};";
|
|
2185
|
+
case "fs/promises":
|
|
2186
|
+
return "module.exports = (globalThis.bridge?.fs || globalThis.bridge?.default || {}).promises || {};";
|
|
2187
|
+
case "module":
|
|
2188
|
+
return `module.exports = ${"globalThis.bridge?.module || {" +
|
|
2189
|
+
"createRequire: globalThis._createRequire || function(f) {" +
|
|
2190
|
+
"const dir = f.replace(/\\\\[^\\\\]*$/, '') || '/';" +
|
|
2191
|
+
"return function(m) { return globalThis._requireFrom(m, dir); };" +
|
|
2192
|
+
"}," +
|
|
2193
|
+
"Module: { builtinModules: [] }," +
|
|
2194
|
+
"isBuiltin: () => false," +
|
|
2195
|
+
"builtinModules: []" +
|
|
2196
|
+
"}"};`;
|
|
2197
|
+
case "os":
|
|
2198
|
+
return "module.exports = globalThis._osModule || {};";
|
|
2199
|
+
case "http":
|
|
2200
|
+
return "module.exports = globalThis._httpModule || globalThis.bridge?.network?.http || {};";
|
|
2201
|
+
case "https":
|
|
2202
|
+
return "module.exports = globalThis._httpsModule || globalThis.bridge?.network?.https || {};";
|
|
2203
|
+
case "http2":
|
|
2204
|
+
return "module.exports = globalThis._http2Module || {};";
|
|
2205
|
+
case "dns":
|
|
2206
|
+
return "module.exports = globalThis._dnsModule || globalThis.bridge?.network?.dns || {};";
|
|
2207
|
+
case "child_process":
|
|
2208
|
+
return "module.exports = globalThis._childProcessModule || globalThis.bridge?.childProcess || {};";
|
|
2209
|
+
case "process":
|
|
2210
|
+
return "module.exports = globalThis.process || {};";
|
|
2211
|
+
case "v8":
|
|
2212
|
+
return "module.exports = globalThis._moduleCache?.v8 || {};";
|
|
2213
|
+
default:
|
|
2214
|
+
return null;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
function loadHostModuleSourceSync(readPath, logicalPath, loadMode) {
|
|
2218
|
+
try {
|
|
2219
|
+
const source = readFileSync(readPath, "utf-8");
|
|
2220
|
+
return loadMode === "require"
|
|
2221
|
+
? transformSourceForRequireSync(source, logicalPath)
|
|
2222
|
+
: transformSourceForImportSync(source, logicalPath, readPath);
|
|
2223
|
+
}
|
|
2224
|
+
catch {
|
|
2225
|
+
return null;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2231
2228
|
/** Build module loading bridge handlers (loadPolyfill, resolveModule, loadFile). */
|
|
2232
2229
|
export function buildModuleLoadingBridgeHandlers(deps,
|
|
2233
2230
|
/** Extra handlers to dispatch through _loadPolyfill for V8 runtime compatibility. */
|
|
@@ -2325,16 +2322,25 @@ dispatchHandlers) {
|
|
|
2325
2322
|
// V8 ESM module mode handles static imports natively via module_resolve_callback;
|
|
2326
2323
|
// this handler covers the __dynamicImport() path used in exec mode.
|
|
2327
2324
|
handlers[K.dynamicImport] = async () => null;
|
|
2328
|
-
// Async file read
|
|
2325
|
+
// Async file read for CommonJS and ESM loader paths.
|
|
2329
2326
|
// Also serves ESM wrappers for built-in modules (fs, path, etc.) when
|
|
2330
2327
|
// used from V8's ES module system which calls _loadFile after _resolveModule.
|
|
2331
|
-
handlers[K.loadFile] =
|
|
2328
|
+
handlers[K.loadFile] = (path, requestedMode) => {
|
|
2332
2329
|
const p = String(path);
|
|
2333
2330
|
const loadMode = requestedMode === "require" || requestedMode === "import"
|
|
2334
2331
|
? requestedMode
|
|
2335
2332
|
: (deps.resolveMode ?? "require");
|
|
2336
2333
|
// Built-in module ESM wrappers (V8 module system resolves 'fs' then loads it)
|
|
2337
2334
|
const bare = p.replace(/^node:/, "");
|
|
2335
|
+
if (loadMode === "require") {
|
|
2336
|
+
const builtinRequireSource = getStaticBuiltinRequireSource(bare);
|
|
2337
|
+
if (builtinRequireSource)
|
|
2338
|
+
return builtinRequireSource;
|
|
2339
|
+
}
|
|
2340
|
+
const builtinBindingExpression = getBuiltinBindingExpression(bare);
|
|
2341
|
+
if (builtinBindingExpression) {
|
|
2342
|
+
return createBuiltinESMWrapper(builtinBindingExpression, getHostBuiltinNamedExports(bare));
|
|
2343
|
+
}
|
|
2338
2344
|
const builtin = getStaticBuiltinWrapperSource(bare);
|
|
2339
2345
|
if (builtin)
|
|
2340
2346
|
return builtin;
|
|
@@ -2342,14 +2348,21 @@ dispatchHandlers) {
|
|
|
2342
2348
|
if (hasPolyfill(bare)) {
|
|
2343
2349
|
return createBuiltinESMWrapper(`globalThis._requireFrom(${JSON.stringify(bare)}, "/")`, getHostBuiltinNamedExports(bare));
|
|
2344
2350
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
if (
|
|
2348
|
-
return
|
|
2349
|
-
if (loadMode === "require") {
|
|
2350
|
-
source = convertEsmToCjs(source, p);
|
|
2351
|
+
const hostPath = deps.sandboxToHostPath?.(p) ?? p;
|
|
2352
|
+
const syncSource = loadHostModuleSourceSync(hostPath, p, loadMode);
|
|
2353
|
+
if (syncSource !== null) {
|
|
2354
|
+
return syncSource;
|
|
2351
2355
|
}
|
|
2352
|
-
|
|
2356
|
+
// Regular files load differently for CommonJS require() vs V8's ESM loader.
|
|
2357
|
+
return (async () => {
|
|
2358
|
+
const source = await loadFile(p, deps.filesystem);
|
|
2359
|
+
if (source === null)
|
|
2360
|
+
return null;
|
|
2361
|
+
if (loadMode === "require") {
|
|
2362
|
+
return transformSourceForRequire(source, p);
|
|
2363
|
+
}
|
|
2364
|
+
return transformSourceForImport(source, p);
|
|
2365
|
+
})();
|
|
2353
2366
|
};
|
|
2354
2367
|
return handlers;
|
|
2355
2368
|
}
|
|
@@ -2369,6 +2382,40 @@ export function buildTimerBridgeHandlers(deps) {
|
|
|
2369
2382
|
};
|
|
2370
2383
|
return handlers;
|
|
2371
2384
|
}
|
|
2385
|
+
function serializeMimeTypeState(value) {
|
|
2386
|
+
return {
|
|
2387
|
+
value: String(value),
|
|
2388
|
+
essence: value.essence,
|
|
2389
|
+
type: value.type,
|
|
2390
|
+
subtype: value.subtype,
|
|
2391
|
+
params: Array.from(value.params.entries()),
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
export function buildMimeBridgeHandlers() {
|
|
2395
|
+
return {
|
|
2396
|
+
mimeBridge: (operation, input, ...args) => {
|
|
2397
|
+
const mime = new hostUtil.MIMEType(String(input));
|
|
2398
|
+
switch (String(operation)) {
|
|
2399
|
+
case "parse":
|
|
2400
|
+
return serializeMimeTypeState(mime);
|
|
2401
|
+
case "setType":
|
|
2402
|
+
mime.type = String(args[0]);
|
|
2403
|
+
return serializeMimeTypeState(mime);
|
|
2404
|
+
case "setSubtype":
|
|
2405
|
+
mime.subtype = String(args[0]);
|
|
2406
|
+
return serializeMimeTypeState(mime);
|
|
2407
|
+
case "setParam":
|
|
2408
|
+
mime.params.set(String(args[0]), String(args[1]));
|
|
2409
|
+
return serializeMimeTypeState(mime);
|
|
2410
|
+
case "deleteParam":
|
|
2411
|
+
mime.params.delete(String(args[0]));
|
|
2412
|
+
return serializeMimeTypeState(mime);
|
|
2413
|
+
default:
|
|
2414
|
+
throw new Error(`Unsupported MIME bridge operation: ${String(operation)}`);
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2372
2419
|
export function buildKernelTimerDispatchHandlers(deps) {
|
|
2373
2420
|
const handlers = {};
|
|
2374
2421
|
handlers.kernelTimerCreate = (delayMs, repeat) => {
|
|
@@ -2420,6 +2467,25 @@ export function buildKernelTimerDispatchHandlers(deps) {
|
|
|
2420
2467
|
};
|
|
2421
2468
|
return handlers;
|
|
2422
2469
|
}
|
|
2470
|
+
export function buildKernelStdinDispatchHandlers(deps) {
|
|
2471
|
+
const handlers = {};
|
|
2472
|
+
const K = HOST_BRIDGE_GLOBAL_KEYS;
|
|
2473
|
+
handlers[K.kernelStdinRead] = async () => {
|
|
2474
|
+
checkBridgeBudget(deps);
|
|
2475
|
+
if (!deps.liveStdinSource) {
|
|
2476
|
+
return { done: true };
|
|
2477
|
+
}
|
|
2478
|
+
const chunk = await deps.liveStdinSource.read();
|
|
2479
|
+
if (chunk === null || chunk.length === 0) {
|
|
2480
|
+
return { done: true };
|
|
2481
|
+
}
|
|
2482
|
+
return {
|
|
2483
|
+
done: false,
|
|
2484
|
+
dataBase64: Buffer.from(chunk).toString("base64"),
|
|
2485
|
+
};
|
|
2486
|
+
};
|
|
2487
|
+
return handlers;
|
|
2488
|
+
}
|
|
2423
2489
|
export function buildKernelHandleDispatchHandlers(deps) {
|
|
2424
2490
|
const handlers = {};
|
|
2425
2491
|
handlers.kernelHandleRegister = (id, description) => {
|
|
@@ -2598,8 +2664,23 @@ export function buildChildProcessBridgeHandlers(deps) {
|
|
|
2598
2664
|
// Serialize a child process event and push it into the V8 isolate
|
|
2599
2665
|
const dispatchEvent = (sessionId, type, data) => {
|
|
2600
2666
|
try {
|
|
2601
|
-
|
|
2602
|
-
|
|
2667
|
+
let eventType;
|
|
2668
|
+
let payload;
|
|
2669
|
+
if (type === "stdout" || type === "stderr") {
|
|
2670
|
+
eventType = type === "stdout" ? "child_stdout" : "child_stderr";
|
|
2671
|
+
payload = {
|
|
2672
|
+
sessionId,
|
|
2673
|
+
dataBase64: Buffer.from(data).toString("base64"),
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2676
|
+
else {
|
|
2677
|
+
eventType = "child_exit";
|
|
2678
|
+
payload = {
|
|
2679
|
+
sessionId,
|
|
2680
|
+
code: Number(data ?? 1),
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
deps.sendStreamEvent(eventType, Buffer.from(JSON.stringify(payload)));
|
|
2603
2684
|
}
|
|
2604
2685
|
catch {
|
|
2605
2686
|
// Context may be disposed
|
|
@@ -2741,10 +2822,15 @@ export function buildChildProcessBridgeHandlers(deps) {
|
|
|
2741
2822
|
function normalizeLoopbackHostname(hostname) {
|
|
2742
2823
|
if (!hostname || hostname === "localhost")
|
|
2743
2824
|
return "127.0.0.1";
|
|
2744
|
-
|
|
2825
|
+
// Preserve wildcard binds so kernel listener lookup and server.address()
|
|
2826
|
+
// reflect the caller's requested address while loopback connects still
|
|
2827
|
+
// resolve through SocketTable wildcard matching.
|
|
2828
|
+
if (hostname === "127.0.0.1" ||
|
|
2829
|
+
hostname === "::1" ||
|
|
2830
|
+
hostname === "0.0.0.0" ||
|
|
2831
|
+
hostname === "::") {
|
|
2745
2832
|
return hostname;
|
|
2746
|
-
|
|
2747
|
-
return "127.0.0.1";
|
|
2833
|
+
}
|
|
2748
2834
|
throw new Error(`Sandbox HTTP servers are restricted to loopback interfaces. Received hostname: ${hostname}`);
|
|
2749
2835
|
}
|
|
2750
2836
|
function debugHttpBridge(...args) {
|
|
@@ -2752,6 +2838,50 @@ function debugHttpBridge(...args) {
|
|
|
2752
2838
|
console.error("[secure-exec http bridge]", ...args);
|
|
2753
2839
|
}
|
|
2754
2840
|
}
|
|
2841
|
+
const MAX_REDIRECTS = 20;
|
|
2842
|
+
function shouldUseKernelHttpClientPath(adapter, urlString) {
|
|
2843
|
+
const loopbackAwareAdapter = adapter;
|
|
2844
|
+
if (typeof loopbackAwareAdapter.__setLoopbackPortChecker !== "function") {
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
try {
|
|
2848
|
+
const parsed = new URL(urlString);
|
|
2849
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
2850
|
+
}
|
|
2851
|
+
catch {
|
|
2852
|
+
return false;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
async function maybeDecompressHttpBody(buffer, contentEncoding) {
|
|
2856
|
+
const encoding = Array.isArray(contentEncoding)
|
|
2857
|
+
? contentEncoding[0]
|
|
2858
|
+
: contentEncoding;
|
|
2859
|
+
if (encoding !== "gzip" && encoding !== "deflate") {
|
|
2860
|
+
return buffer;
|
|
2861
|
+
}
|
|
2862
|
+
try {
|
|
2863
|
+
return await new Promise((resolve, reject) => {
|
|
2864
|
+
const decompress = encoding === "gzip" ? zlib.gunzip : zlib.inflate;
|
|
2865
|
+
decompress(buffer, (err, result) => {
|
|
2866
|
+
if (err)
|
|
2867
|
+
reject(err);
|
|
2868
|
+
else
|
|
2869
|
+
resolve(result);
|
|
2870
|
+
});
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
catch {
|
|
2874
|
+
// Preserve the original bytes when decompression fails.
|
|
2875
|
+
return buffer;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
function shouldEncodeHttpBodyAsBinary(urlString, headers) {
|
|
2879
|
+
const contentType = headers["content-type"] || "";
|
|
2880
|
+
const headerValue = Array.isArray(contentType) ? contentType.join(", ") : contentType;
|
|
2881
|
+
return (headerValue.includes("octet-stream") ||
|
|
2882
|
+
headerValue.includes("gzip") ||
|
|
2883
|
+
urlString.endsWith(".tgz"));
|
|
2884
|
+
}
|
|
2755
2885
|
/**
|
|
2756
2886
|
* Create a Duplex stream backed by a kernel socket.
|
|
2757
2887
|
* Readable side reads from kernel socket readBuffer; writable side writes via send().
|
|
@@ -2885,6 +3015,7 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
2885
3015
|
const kernelHttp2ClientSessions = new Map();
|
|
2886
3016
|
const http2Sessions = new Map();
|
|
2887
3017
|
const http2Streams = new Map();
|
|
3018
|
+
const pendingHttp2PushStreams = new Map();
|
|
2888
3019
|
const http2ServerSessionIds = new WeakMap();
|
|
2889
3020
|
let nextHttp2SessionId = 1;
|
|
2890
3021
|
let nextHttp2StreamId = 1;
|
|
@@ -3017,6 +3148,173 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
3017
3148
|
}
|
|
3018
3149
|
return false;
|
|
3019
3150
|
});
|
|
3151
|
+
const performKernelHttpRequest = async (urlString, requestOptions) => {
|
|
3152
|
+
const url = new URL(urlString);
|
|
3153
|
+
const isHttps = url.protocol === "https:";
|
|
3154
|
+
const host = url.hostname;
|
|
3155
|
+
const port = Number(url.port || (isHttps ? 443 : 80));
|
|
3156
|
+
const socketId = socketTable.create(host.includes(":") ? AF_INET6 : AF_INET, SOCK_STREAM, 0, pid);
|
|
3157
|
+
await socketTable.connect(socketId, { host, port });
|
|
3158
|
+
const baseTransport = createKernelSocketDuplex(socketId, socketTable, pid);
|
|
3159
|
+
const requestTransport = isHttps
|
|
3160
|
+
? tls.connect({
|
|
3161
|
+
socket: baseTransport,
|
|
3162
|
+
servername: host,
|
|
3163
|
+
...(requestOptions.rejectUnauthorized !== undefined
|
|
3164
|
+
? { rejectUnauthorized: requestOptions.rejectUnauthorized }
|
|
3165
|
+
: {}),
|
|
3166
|
+
})
|
|
3167
|
+
: baseTransport;
|
|
3168
|
+
const transport = isHttps ? https : http;
|
|
3169
|
+
return await new Promise((resolve, reject) => {
|
|
3170
|
+
let settled = false;
|
|
3171
|
+
const settleResolve = (value) => {
|
|
3172
|
+
if (settled)
|
|
3173
|
+
return;
|
|
3174
|
+
settled = true;
|
|
3175
|
+
resolve(value);
|
|
3176
|
+
};
|
|
3177
|
+
const settleReject = (error) => {
|
|
3178
|
+
if (settled)
|
|
3179
|
+
return;
|
|
3180
|
+
settled = true;
|
|
3181
|
+
reject(error);
|
|
3182
|
+
};
|
|
3183
|
+
const req = transport.request({
|
|
3184
|
+
hostname: host,
|
|
3185
|
+
port,
|
|
3186
|
+
path: `${url.pathname}${url.search}`,
|
|
3187
|
+
method: requestOptions.method || "GET",
|
|
3188
|
+
headers: requestOptions.headers || {},
|
|
3189
|
+
agent: false,
|
|
3190
|
+
createConnection: () => requestTransport,
|
|
3191
|
+
}, (res) => {
|
|
3192
|
+
const chunks = [];
|
|
3193
|
+
res.on("data", (chunk) => {
|
|
3194
|
+
chunks.push(chunk);
|
|
3195
|
+
});
|
|
3196
|
+
res.on("error", (error) => {
|
|
3197
|
+
requestTransport.destroy();
|
|
3198
|
+
settleReject(error);
|
|
3199
|
+
});
|
|
3200
|
+
res.on("end", async () => {
|
|
3201
|
+
const decodedBuffer = await maybeDecompressHttpBody(Buffer.concat(chunks), res.headers["content-encoding"]);
|
|
3202
|
+
const buffer = Buffer.from(decodedBuffer);
|
|
3203
|
+
const headers = {};
|
|
3204
|
+
const rawHeaders = [...res.rawHeaders];
|
|
3205
|
+
Object.entries(res.headers).forEach(([key, value]) => {
|
|
3206
|
+
if (typeof value === "string")
|
|
3207
|
+
headers[key] = value;
|
|
3208
|
+
else if (Array.isArray(value))
|
|
3209
|
+
headers[key] = value.join(", ");
|
|
3210
|
+
});
|
|
3211
|
+
delete headers["content-encoding"];
|
|
3212
|
+
const trailers = {};
|
|
3213
|
+
Object.entries(res.trailers || {}).forEach(([key, value]) => {
|
|
3214
|
+
if (typeof value === "string")
|
|
3215
|
+
trailers[key] = value;
|
|
3216
|
+
});
|
|
3217
|
+
const result = {
|
|
3218
|
+
status: res.statusCode || 200,
|
|
3219
|
+
statusText: res.statusMessage || "OK",
|
|
3220
|
+
headers,
|
|
3221
|
+
rawHeaders,
|
|
3222
|
+
url: urlString,
|
|
3223
|
+
body: shouldEncodeHttpBodyAsBinary(urlString, res.headers)
|
|
3224
|
+
? (() => {
|
|
3225
|
+
headers["x-body-encoding"] = "base64";
|
|
3226
|
+
return buffer.toString("base64");
|
|
3227
|
+
})()
|
|
3228
|
+
: buffer.toString("utf8"),
|
|
3229
|
+
};
|
|
3230
|
+
if (Object.keys(trailers).length > 0) {
|
|
3231
|
+
result.trailers = trailers;
|
|
3232
|
+
}
|
|
3233
|
+
requestTransport.destroy();
|
|
3234
|
+
settleResolve(result);
|
|
3235
|
+
});
|
|
3236
|
+
});
|
|
3237
|
+
req.on("upgrade", (res, upgradedSocket, head) => {
|
|
3238
|
+
const headers = {};
|
|
3239
|
+
const rawHeaders = [...res.rawHeaders];
|
|
3240
|
+
Object.entries(res.headers).forEach(([key, value]) => {
|
|
3241
|
+
if (typeof value === "string")
|
|
3242
|
+
headers[key] = value;
|
|
3243
|
+
else if (Array.isArray(value))
|
|
3244
|
+
headers[key] = value.join(", ");
|
|
3245
|
+
});
|
|
3246
|
+
settleResolve({
|
|
3247
|
+
status: res.statusCode || 101,
|
|
3248
|
+
statusText: res.statusMessage || "Switching Protocols",
|
|
3249
|
+
headers,
|
|
3250
|
+
rawHeaders,
|
|
3251
|
+
body: head.toString("base64"),
|
|
3252
|
+
url: urlString,
|
|
3253
|
+
upgradeSocketId: registerKernelUpgradeSocket(upgradedSocket),
|
|
3254
|
+
});
|
|
3255
|
+
});
|
|
3256
|
+
req.on("connect", (res, connectSocket, head) => {
|
|
3257
|
+
const headers = {};
|
|
3258
|
+
const rawHeaders = [...res.rawHeaders];
|
|
3259
|
+
Object.entries(res.headers).forEach(([key, value]) => {
|
|
3260
|
+
if (typeof value === "string")
|
|
3261
|
+
headers[key] = value;
|
|
3262
|
+
else if (Array.isArray(value))
|
|
3263
|
+
headers[key] = value.join(", ");
|
|
3264
|
+
});
|
|
3265
|
+
settleResolve({
|
|
3266
|
+
status: res.statusCode || 200,
|
|
3267
|
+
statusText: res.statusMessage || "Connection established",
|
|
3268
|
+
headers,
|
|
3269
|
+
rawHeaders,
|
|
3270
|
+
body: head.toString("base64"),
|
|
3271
|
+
url: urlString,
|
|
3272
|
+
upgradeSocketId: registerKernelUpgradeSocket(connectSocket),
|
|
3273
|
+
});
|
|
3274
|
+
});
|
|
3275
|
+
req.on("error", (error) => {
|
|
3276
|
+
requestTransport.destroy();
|
|
3277
|
+
settleReject(error);
|
|
3278
|
+
});
|
|
3279
|
+
if (requestOptions.body) {
|
|
3280
|
+
req.write(requestOptions.body);
|
|
3281
|
+
}
|
|
3282
|
+
req.end();
|
|
3283
|
+
});
|
|
3284
|
+
};
|
|
3285
|
+
const performKernelFetch = async (urlString, requestOptions) => {
|
|
3286
|
+
let currentUrl = urlString;
|
|
3287
|
+
let redirected = false;
|
|
3288
|
+
let currentOptions = { ...requestOptions };
|
|
3289
|
+
for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount += 1) {
|
|
3290
|
+
const response = await performKernelHttpRequest(currentUrl, currentOptions);
|
|
3291
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
3292
|
+
const location = response.headers.location;
|
|
3293
|
+
if (location) {
|
|
3294
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
3295
|
+
redirected = true;
|
|
3296
|
+
if (response.status === 301 || response.status === 302 || response.status === 303) {
|
|
3297
|
+
currentOptions = {
|
|
3298
|
+
...currentOptions,
|
|
3299
|
+
method: "GET",
|
|
3300
|
+
body: null,
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
continue;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
return {
|
|
3307
|
+
ok: response.status >= 200 && response.status < 300,
|
|
3308
|
+
status: response.status,
|
|
3309
|
+
statusText: response.statusText,
|
|
3310
|
+
headers: { ...response.headers },
|
|
3311
|
+
body: response.body,
|
|
3312
|
+
url: currentUrl,
|
|
3313
|
+
redirected,
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
throw new Error("Too many redirects");
|
|
3317
|
+
};
|
|
3020
3318
|
const registerKernelUpgradeSocket = (socket) => {
|
|
3021
3319
|
const socketId = nextKernelUpgradeSocketId++;
|
|
3022
3320
|
kernelUpgradeSockets.set(socketId, socket);
|
|
@@ -3069,10 +3367,20 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
3069
3367
|
handlers[K.networkFetchRaw] = async (url, optionsJson) => {
|
|
3070
3368
|
checkBridgeBudget(deps);
|
|
3071
3369
|
const options = parseJsonWithLimit("network.fetch options", String(optionsJson), jsonLimit);
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3370
|
+
deps.activeHttpClientRequests.count += 1;
|
|
3371
|
+
try {
|
|
3372
|
+
const urlString = String(url);
|
|
3373
|
+
const result = shouldUseKernelHttpClientPath(adapter, urlString)
|
|
3374
|
+
? await performKernelFetch(urlString, options)
|
|
3375
|
+
// Legacy fallback for custom adapters and explicit no-network stubs.
|
|
3376
|
+
: await adapter.fetch(urlString, options);
|
|
3377
|
+
const json = JSON.stringify(result);
|
|
3378
|
+
assertTextPayloadSize("network.fetch response", json, jsonLimit);
|
|
3379
|
+
return json;
|
|
3380
|
+
}
|
|
3381
|
+
finally {
|
|
3382
|
+
deps.activeHttpClientRequests.count = Math.max(0, deps.activeHttpClientRequests.count - 1);
|
|
3383
|
+
}
|
|
3076
3384
|
};
|
|
3077
3385
|
handlers[K.networkDnsLookupRaw] = async (hostname) => {
|
|
3078
3386
|
checkBridgeBudget(deps);
|
|
@@ -3082,10 +3390,20 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
3082
3390
|
handlers[K.networkHttpRequestRaw] = async (url, optionsJson) => {
|
|
3083
3391
|
checkBridgeBudget(deps);
|
|
3084
3392
|
const options = parseJsonWithLimit("network.httpRequest options", String(optionsJson), jsonLimit);
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3393
|
+
deps.activeHttpClientRequests.count += 1;
|
|
3394
|
+
try {
|
|
3395
|
+
const urlString = String(url);
|
|
3396
|
+
const result = shouldUseKernelHttpClientPath(adapter, urlString)
|
|
3397
|
+
? await performKernelHttpRequest(urlString, options)
|
|
3398
|
+
// Legacy fallback for custom adapters and explicit no-network stubs.
|
|
3399
|
+
: await adapter.httpRequest(urlString, options);
|
|
3400
|
+
const json = JSON.stringify(result);
|
|
3401
|
+
assertTextPayloadSize("network.httpRequest response", json, jsonLimit);
|
|
3402
|
+
return json;
|
|
3403
|
+
}
|
|
3404
|
+
finally {
|
|
3405
|
+
deps.activeHttpClientRequests.count = Math.max(0, deps.activeHttpClientRequests.count - 1);
|
|
3406
|
+
}
|
|
3089
3407
|
};
|
|
3090
3408
|
handlers[K.networkHttpServerRespondRaw] = (serverId, requestId, responseJson) => {
|
|
3091
3409
|
const numericServerId = Number(serverId);
|
|
@@ -3374,6 +3692,28 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
3374
3692
|
code: err.code,
|
|
3375
3693
|
}));
|
|
3376
3694
|
};
|
|
3695
|
+
const resolveHostHttp2FilePath = (filePath) => {
|
|
3696
|
+
// The sandbox defaults process.execPath to /usr/bin/node, but the host-side
|
|
3697
|
+
// http2 respondWithFile helper needs a real host path when serving the Node binary.
|
|
3698
|
+
if (filePath === "/usr/bin/node" && process.execPath) {
|
|
3699
|
+
return process.execPath;
|
|
3700
|
+
}
|
|
3701
|
+
return filePath;
|
|
3702
|
+
};
|
|
3703
|
+
const withHttp2ServerStream = (streamId, action, fallback) => {
|
|
3704
|
+
const stream = http2Streams.get(streamId);
|
|
3705
|
+
if (stream) {
|
|
3706
|
+
return action(stream);
|
|
3707
|
+
}
|
|
3708
|
+
const pending = pendingHttp2PushStreams.get(streamId);
|
|
3709
|
+
if (pending) {
|
|
3710
|
+
pending.operations.push((resolvedStream) => {
|
|
3711
|
+
action(resolvedStream);
|
|
3712
|
+
});
|
|
3713
|
+
return fallback();
|
|
3714
|
+
}
|
|
3715
|
+
throw new Error(`HTTP/2 stream ${String(streamId)} not found`);
|
|
3716
|
+
};
|
|
3377
3717
|
const attachHttp2ClientStreamListeners = (streamId, stream) => {
|
|
3378
3718
|
stream.on("response", (headers) => {
|
|
3379
3719
|
emitHttp2Event("clientResponseHeaders", streamId, JSON.stringify(normalizeHttp2EventHeaders(headers)));
|
|
@@ -3717,70 +4057,68 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
3717
4057
|
return state?.closedPromise ?? Promise.resolve();
|
|
3718
4058
|
};
|
|
3719
4059
|
handlers[K.networkHttp2StreamRespondRaw] = (streamId, headersJson) => {
|
|
3720
|
-
const stream = http2Streams.get(Number(streamId));
|
|
3721
|
-
if (!stream) {
|
|
3722
|
-
throw new Error(`HTTP/2 stream ${String(streamId)} not found`);
|
|
3723
|
-
}
|
|
3724
4060
|
const headers = parseJsonWithLimit("network.http2Stream.respond headers", String(headersJson), jsonLimit);
|
|
3725
|
-
|
|
4061
|
+
withHttp2ServerStream(Number(streamId), (stream) => {
|
|
4062
|
+
stream.respond(headers);
|
|
4063
|
+
}, () => undefined);
|
|
3726
4064
|
};
|
|
3727
|
-
handlers[K.networkHttp2StreamPushStreamRaw] =
|
|
4065
|
+
handlers[K.networkHttp2StreamPushStreamRaw] = (streamId, headersJson, optionsJson) => {
|
|
3728
4066
|
const stream = http2Streams.get(Number(streamId));
|
|
3729
4067
|
if (!stream) {
|
|
3730
4068
|
throw new Error(`HTTP/2 stream ${String(streamId)} not found`);
|
|
3731
4069
|
}
|
|
3732
4070
|
const headers = parseJsonWithLimit("network.http2Stream.pushStream headers", String(headersJson), jsonLimit);
|
|
3733
4071
|
const options = parseJsonWithLimit("network.http2Stream.pushStream options", String(optionsJson), jsonLimit);
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
}));
|
|
3745
|
-
return;
|
|
3746
|
-
}
|
|
3747
|
-
if (!pushStream) {
|
|
3748
|
-
reject(new Error("HTTP/2 push stream callback returned no stream"));
|
|
3749
|
-
return;
|
|
3750
|
-
}
|
|
3751
|
-
const pushStreamId = nextHttp2StreamId++;
|
|
3752
|
-
http2Streams.set(pushStreamId, pushStream);
|
|
3753
|
-
pushStream.on("close", () => {
|
|
3754
|
-
http2Streams.delete(pushStreamId);
|
|
3755
|
-
});
|
|
3756
|
-
resolve(JSON.stringify({
|
|
3757
|
-
streamId: pushStreamId,
|
|
3758
|
-
headers: JSON.stringify(normalizeHttp2EventHeaders(pushHeaders ?? {})),
|
|
3759
|
-
}));
|
|
3760
|
-
});
|
|
4072
|
+
const pushStreamId = nextHttp2StreamId++;
|
|
4073
|
+
pendingHttp2PushStreams.set(pushStreamId, {
|
|
4074
|
+
operations: [],
|
|
4075
|
+
});
|
|
4076
|
+
stream.pushStream(headers, options, (error, pushStream, pushHeaders) => {
|
|
4077
|
+
const pending = pendingHttp2PushStreams.get(pushStreamId);
|
|
4078
|
+
if (error) {
|
|
4079
|
+
pendingHttp2PushStreams.delete(pushStreamId);
|
|
4080
|
+
emitHttp2SerializedError("serverStreamError", Number(streamId), error);
|
|
4081
|
+
return;
|
|
3761
4082
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
4083
|
+
if (!pushStream) {
|
|
4084
|
+
pendingHttp2PushStreams.delete(pushStreamId);
|
|
4085
|
+
return;
|
|
3764
4086
|
}
|
|
4087
|
+
http2Streams.set(pushStreamId, pushStream);
|
|
4088
|
+
pushStream.on("close", () => {
|
|
4089
|
+
http2Streams.delete(pushStreamId);
|
|
4090
|
+
pendingHttp2PushStreams.delete(pushStreamId);
|
|
4091
|
+
});
|
|
4092
|
+
for (const operation of pending?.operations ?? []) {
|
|
4093
|
+
operation(pushStream);
|
|
4094
|
+
}
|
|
4095
|
+
pendingHttp2PushStreams.delete(pushStreamId);
|
|
4096
|
+
void pushHeaders;
|
|
4097
|
+
});
|
|
4098
|
+
return JSON.stringify({
|
|
4099
|
+
streamId: pushStreamId,
|
|
4100
|
+
headers: JSON.stringify(normalizeHttp2EventHeaders(headers)),
|
|
3765
4101
|
});
|
|
3766
4102
|
};
|
|
3767
4103
|
handlers[K.networkHttp2StreamWriteRaw] = (streamId, dataBase64) => {
|
|
3768
|
-
|
|
3769
|
-
if (!stream) {
|
|
3770
|
-
throw new Error(`HTTP/2 stream ${String(streamId)} not found`);
|
|
3771
|
-
}
|
|
3772
|
-
return stream.write(Buffer.from(String(dataBase64), "base64"));
|
|
4104
|
+
return withHttp2ServerStream(Number(streamId), (stream) => stream.write(Buffer.from(String(dataBase64), "base64")), () => true);
|
|
3773
4105
|
};
|
|
3774
4106
|
handlers[K.networkHttp2StreamEndRaw] = (streamId, dataBase64) => {
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
stream.end(
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
4107
|
+
withHttp2ServerStream(Number(streamId), (stream) => {
|
|
4108
|
+
if (typeof dataBase64 === "string" && dataBase64.length > 0) {
|
|
4109
|
+
stream.end(Buffer.from(dataBase64, "base64"));
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
stream.end();
|
|
4113
|
+
}, () => undefined);
|
|
4114
|
+
};
|
|
4115
|
+
handlers[K.networkHttp2StreamCloseRaw] = (streamId, rstCode) => {
|
|
4116
|
+
withHttp2ServerStream(Number(streamId), (stream) => {
|
|
4117
|
+
if (typeof stream.close !== "function") {
|
|
4118
|
+
throw new Error(`HTTP/2 stream ${String(streamId)} not found`);
|
|
4119
|
+
}
|
|
4120
|
+
stream.close(typeof rstCode === "number" ? Number(rstCode) : undefined);
|
|
4121
|
+
}, () => undefined);
|
|
3784
4122
|
};
|
|
3785
4123
|
handlers[K.networkHttp2StreamPauseRaw] = (streamId) => {
|
|
3786
4124
|
http2Streams.get(Number(streamId))?.pause();
|
|
@@ -3789,13 +4127,11 @@ export function buildNetworkBridgeHandlers(deps) {
|
|
|
3789
4127
|
http2Streams.get(Number(streamId))?.resume();
|
|
3790
4128
|
};
|
|
3791
4129
|
handlers[K.networkHttp2StreamRespondWithFileRaw] = (streamId, filePath, headersJson, optionsJson) => {
|
|
3792
|
-
const stream = http2Streams.get(Number(streamId));
|
|
3793
|
-
if (!stream) {
|
|
3794
|
-
throw new Error(`HTTP/2 stream ${String(streamId)} not found`);
|
|
3795
|
-
}
|
|
3796
4130
|
const headers = parseJsonWithLimit("network.http2Stream.respondWithFile headers", String(headersJson), jsonLimit);
|
|
3797
4131
|
const options = parseJsonWithLimit("network.http2Stream.respondWithFile options", String(optionsJson), jsonLimit);
|
|
3798
|
-
|
|
4132
|
+
withHttp2ServerStream(Number(streamId), (stream) => {
|
|
4133
|
+
stream.respondWithFile(resolveHostHttp2FilePath(String(filePath)), headers, options);
|
|
4134
|
+
}, () => undefined);
|
|
3799
4135
|
};
|
|
3800
4136
|
handlers[K.networkHttp2ServerRespondRaw] = (serverId, requestId, responseJson) => {
|
|
3801
4137
|
resolveHttp2CompatResponse({
|