From 2da62d41f418b8fab1d34c1f833f26f1a77cd2aa Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 00:01:58 +0900 Subject: [PATCH 01/30] feat: extend filesystem and codec compatibility profiles --- README.md | 4 +- .../node-compat/completion-report.mjs | 4 +- .../node-compat/expansion-contracts.probe.mjs | 18 +- .../node-compat/expansion-native.mjs | 35 +- .../node-compat/native-measurements.mjs | 44 + .../node-compat/prepare-events-consumer.mjs | 71 +- .../experiments/published-package/README.md | 47 ++ .../published-package/benchmark.mjs | 80 ++ .../experiments/published-package/native.mjs | 150 ++++ .../experiments/published-package/worker.mjs | 28 + demo/tests/native/acceptance.mjs | 28 +- demo/tests/native/backend.mjs | 5 +- docs/README.md | 2 +- docs/build-setup.md | 2 +- docs/compatibility.md | 2 +- docs/development.md | 25 +- docs/integration.md | 8 +- docs/native-embedding.md | 39 +- scripts/build-codecs.mjs | 20 +- scripts/build-native-codecs.mjs | 4 + scripts/build-native-zlib.mjs | 48 ++ scripts/test-package.mjs | 7 +- src/compat/crypto/cipher-info.ts | 333 ++++++++ src/compat/crypto/metadata.ts | 2 + src/compat/expansion/README.md | 11 +- src/compat/expansion/index.spec.mjs | 453 ++++++++++ src/compat/fs/embedded.ts | 8 +- src/compat/fs/index.ts | 181 +++- src/compat/fs/writable.spec.ts | 159 +++- src/compat/fs/writable.ts | 240 +++++- src/compat/zlib/LICENSE-3RD-PARTY.md | 11 + src/compat/zlib/LICENSE-APACHE | 177 ++++ src/compat/zlib/LICENSE.zlib | 22 + src/compat/zlib/classic-base.cjs | 778 ++++++++++++++++++ src/compat/zlib/classic-base.d.cts | 37 + src/compat/zlib/classic-binding.ts | 146 ++++ src/compat/zlib/classic.ts | 245 ++++++ src/compat/zlib/codec-binding.ts | 191 +++++ src/compat/zlib/compression.ts | 434 ++++------ src/compat/zlib/errors.ts | 33 + src/compat/zlib/index.ts | 60 +- src/compat/zlib/native-codecs.build.json | 3 +- src/compat/zlib/native-codecs.c | 1 + src/compat/zlib/native-codecs.ts | 20 +- src/compat/zlib/native-codecs.wasm | Bin 1181723 -> 1181778 bytes src/compat/zlib/native-zlib.build.json | 7 + src/compat/zlib/native-zlib.c | 43 + src/compat/zlib/native-zlib.ts | 31 + src/compat/zlib/native-zlib.wasm | Bin 0 -> 58126 bytes src/compat/zlib/validators.ts | 25 + 50 files changed, 3914 insertions(+), 408 deletions(-) create mode 100644 demo/tests/experiments/node-compat/native-measurements.mjs create mode 100644 demo/tests/experiments/published-package/README.md create mode 100644 demo/tests/experiments/published-package/benchmark.mjs create mode 100644 demo/tests/experiments/published-package/native.mjs create mode 100644 demo/tests/experiments/published-package/worker.mjs create mode 100644 scripts/build-native-zlib.mjs create mode 100644 src/compat/crypto/cipher-info.ts create mode 100644 src/compat/zlib/LICENSE-3RD-PARTY.md create mode 100644 src/compat/zlib/LICENSE-APACHE create mode 100644 src/compat/zlib/LICENSE.zlib create mode 100644 src/compat/zlib/classic-base.cjs create mode 100644 src/compat/zlib/classic-base.d.cts create mode 100644 src/compat/zlib/classic-binding.ts create mode 100644 src/compat/zlib/classic.ts create mode 100644 src/compat/zlib/codec-binding.ts create mode 100644 src/compat/zlib/errors.ts create mode 100644 src/compat/zlib/native-zlib.build.json create mode 100644 src/compat/zlib/native-zlib.c create mode 100644 src/compat/zlib/native-zlib.ts create mode 100755 src/compat/zlib/native-zlib.wasm create mode 100644 src/compat/zlib/validators.ts diff --git a/README.md b/README.md index 97ad7e34..9400e4a6 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Install the package in your web project, then follow the [complete mobile setup](docs/integration.md#workflow): ```sh -npm install --save-exact cf-workers-native@0.1.4 +npm install --save-exact cf-workers-native@0.1.5 ``` Installation alone does not generate a mobile project. The guide covers adding @@ -84,7 +84,7 @@ Open your application repository and give your assistant this prompt: ```text Read https://github.com/rdlabo-dev/cf-workers-native/blob/main/docs/integration.md. -Add an iOS and Android version of this Workers web app using cf-workers-native@0.1.4 +Add an iOS and Android version of this Workers web app using cf-workers-native@0.1.5 and Capacitor. Preserve the existing pages, backend and Cloudflare deployment configuration. Use an existing test Worker for cloud-backed requests; ask before any cloud deployment. Start by verifying one page, navigation and a form or data action on a simulator/emulator. diff --git a/demo/tests/experiments/node-compat/completion-report.mjs b/demo/tests/experiments/node-compat/completion-report.mjs index 56131e0c..4d8bfc3f 100644 --- a/demo/tests/experiments/node-compat/completion-report.mjs +++ b/demo/tests/experiments/node-compat/completion-report.mjs @@ -23,7 +23,7 @@ const assessments = [ modules: ["crypto", "zlib"], priority: 2, category: "implementation-gap", - next: "Inventory algorithms, key/codec classes, synchronous and streaming behavior. Evaluate JS/Wasm implementations against all pinned operations; random/hash/HKDF/scrypt, owned secret/asymmetric keys, RSA/EC/Ed25519 signatures, prime generation/checking X509Certificate parsing/fields/name checks/signature checks and Brotli/Zstd profiles now supplement dependency polyfills. Pinned Workers X509 key-check restrictions are documented. Additional key-generation options, remaining algorithms and complete codec contracts remain open. No technical-impossibility claim has been established.", + next: "Inventory algorithms, key/codec classes, synchronous and streaming behavior. Evaluate JS/Wasm implementations against all pinned operations; random/hash/HKDF/scrypt, owned secret/asymmetric keys, RSA/EC/Ed25519 signatures, prime generation/checking X509Certificate parsing/fields/name checks/signature checks and cipher metadata lookup now supplement dependency polyfills. Gzip/deflate now use zlib Wasm; all codec streams use the pinned Workers lifecycle, with option, reset, close, callback and output-limit contracts. Pinned Workers X509 key-check restrictions are documented. Additional key-generation options, remaining algorithms and complete codec contracts remain open. No technical-impossibility claim has been established.", evidence: [ "surface-gaps.json", "expansion-contracts.probe.mjs", @@ -35,7 +35,7 @@ const assessments = [ modules: ["fs", "fs/promises"], priority: 2, category: "implementation-gap", - next: "Compare the Workers virtual filesystem, descriptors, FileHandle, writes, permissions and watch behavior. Request-local memory-backed /tmp now supports writes, descriptors and FileHandle; permission no-ops, statfs and embedded-file copying are tested; compare remaining cross-volume operations, descriptors and stream semantics.", + next: "Compare the Workers virtual filesystem, descriptors, FileHandle, writes, permissions and watch behavior. Request-local memory-backed /tmp now supports writes, descriptors and FileHandle; permission no-ops, statfs and embedded-file copying are tested; embedded descriptors/FileHandles, links, immutable copies, cp options, millisecond timestamps and readonly metadata are also compared. Complete the remaining operation/option inventory, including devices, directory iteration, glob and stream fd options.", evidence: [ "../../../../src/compat/fs/index.ts", "../../../../src/compat/fs/promises/index.ts", diff --git a/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs b/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs index 66b0dbdf..971aeb6b 100644 --- a/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs +++ b/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs @@ -24,18 +24,26 @@ assert.equal( ); const runtime = new Miniflare( convertV4MiniflareOptions({ - modules: true, compatibilityDate: profile.compatibilityDate, - script: `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; + modulesRoot: "/fixture", + modules: [ + { + type: "ESModule", + path: "/fixture/main.mjs", + contents: `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; const cases = {${Object.entries(eventCases) - .map(([name, fn]) => `${JSON.stringify(name)}: (${fn.toString()})`) - .join(",")}}; + .map(([name, fn]) => `${JSON.stringify(name)}: (${fn.toString()})`) + .join(",")}}; export default {async fetch(request) { const name = new URL(request.url).pathname.slice(1); return Response.json(await cases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http})); }};`, + }, + { type: "Text", path: "/fixture/readonly.txt", contents: "abcdef" }, + { type: "Text", path: "/fixture/nested/child.txt", contents: "child" }, + ], }), ); const baseline = {}; @@ -68,6 +76,7 @@ const compiled = await build({ import { eventCases } from './src/compat/expansion/index.spec.mjs'; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; +fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); export const run = name => eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, }, bundle: true, @@ -89,6 +98,7 @@ await writeFile( import { eventCases } from ${JSON.stringify(fileURLToPath(new URL("../../../../src/compat/expansion/index.spec.mjs", import.meta.url)))}; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; +fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); export const run = name => eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, ); const viteResult = await viteBuild({ diff --git a/demo/tests/experiments/node-compat/expansion-native.mjs b/demo/tests/experiments/node-compat/expansion-native.mjs index 49921493..9568196f 100644 --- a/demo/tests/experiments/node-compat/expansion-native.mjs +++ b/demo/tests/experiments/node-compat/expansion-native.mjs @@ -68,7 +68,7 @@ await writeFile( compatibility_date: profile.compatibilityDate, }), ); -const { consumer, tarball, tarballSha256 } = JSON.parse( +const consumerEvidence = JSON.parse( await readFile( new URL( "../../../artifacts/node-inventory/events-consumer.json", @@ -77,6 +77,18 @@ const { consumer, tarball, tarballSha256 } = JSON.parse( "utf8", ), ); +const { consumer, tarball, tarballSha256 } = consumerEvidence; +assert.equal( + createHash("sha256") + .update(await readFile(tarball)) + .digest("hex"), + tarballSha256, +); +const { workersCapacitorPlugin: installedVitePlugin } = await import( + pathToFileURL( + `${consumer}/node_modules/cf-workers-native/dist/tooling/vite.js`, + ).href +); const { workersCapacitorPlugin } = await import( pathToFileURL( `${consumer}/node_modules/cf-workers-native/dist/tooling/esbuild.js`, @@ -90,7 +102,10 @@ const compiled = await build({ import { eventCases } from ${JSON.stringify(root + "/src/compat/expansion/index.spec.mjs")}; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; -export const run = name => eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http}); +fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); +import {measureOperation,measurements} from ${JSON.stringify(root + "/demo/tests/experiments/node-compat/native-measurements.mjs")}; +export {measurements}; +export const run = name => measureOperation(name,()=>eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http}),name==='classicCodecs'?()=>zlib.gzipSync('x'.repeat(1024)):undefined); export {createNetworkRelay, initializeWorkerNetwork} from ${JSON.stringify(consumer + "/node_modules/cf-workers-native/dist/browser/fetch/relay.js")}; export const runTls = async ({port,upgradePort}) => { const attempt = (servername,halfClose=false) => new Promise((resolve,reject)=> { @@ -153,6 +168,7 @@ await writeFile( device, tarball, tarballSha256, + consumerEvidence, cases: Object.keys(eventCases), }, null, @@ -362,21 +378,21 @@ async function main() { const source = ${JSON.stringify(source)}; const names = ${JSON.stringify(Object.keys(eventCases))}; const report = async value => {await fetch(${JSON.stringify(backend.url.origin + "/__acceptance/report")}, {method:'POST', mode:'no-cors', headers:{'content-type':'text/plain'}, body:JSON.stringify(value)});}; -const results = {}; +const results = {}, measurements = {}; try { for (const mode of ['page', 'worker']) { - results[mode] = {}; + results[mode] = {}; measurements[mode] = {}; for (const name of names) { const url = URL.createObjectURL(new Blob([source], {type:'text/javascript'})); try { - if (mode === 'page') { const api = await import(/* @vite-ignore */ url); results[mode][name] = JSON.parse(JSON.stringify(await api.run(name))); } + if (mode === 'page') { const api = await import(/* @vite-ignore */ url); results[mode][name] = JSON.parse(JSON.stringify(await api.run(name))); measurements[mode][name] = api.measurements[name]; } else { - const workerURL = URL.createObjectURL(new Blob(['import {run} from ' + JSON.stringify(url) + '; self.onmessage = async ({data}) => {try {self.postMessage({value:JSON.parse(JSON.stringify(await run(data)))});} catch(error) {self.postMessage({error:String(error)});}};'], {type:'text/javascript'})); + const workerURL = URL.createObjectURL(new Blob(['import {run,measurements} from ' + JSON.stringify(url) + '; self.onmessage = async ({data}) => {try {self.postMessage({value:JSON.parse(JSON.stringify(await run(data))),measurements:measurements[data]});} catch(error) {self.postMessage({error:String(error)});}};'], {type:'text/javascript'})); const worker = new Worker(workerURL, {type:'module'}); try { results[mode][name] = await new Promise((resolve,reject) => { const timer = setTimeout(() => reject(new Error('Worker timeout: '+name)), 8000); - worker.onmessage = ({data}) => {clearTimeout(timer); data.error ? reject(new Error(data.error)) : resolve(data.value);}; + worker.onmessage = ({data}) => {clearTimeout(timer); if(data.error)reject(new Error(data.error));else {measurements[mode][name]=data.measurements;resolve(data.value);}}; worker.onerror = event => {clearTimeout(timer);reject(new Error(event.message || 'Worker error'));}; worker.postMessage(name); }); @@ -397,7 +413,7 @@ try { tlsWorker.onmessage=({data})=>{if(data.tls || data.error){clearTimeout(timer);data.error?reject(new Error(data.error)):resolve(data.tls);}}; tlsWorker.postMessage(${JSON.stringify(tlsPorts)}); });} finally {tlsWorker.terminate();await relay.dispose();URL.revokeObjectURL(workerURL);URL.revokeObjectURL(tlsURL);} - await report({stage:'complete', nativeTls, hash:${JSON.stringify(hash)}, results}); + await report({stage:'complete', measurements, nativeTls, hash:${JSON.stringify(hash)}, results}); document.body.textContent = 'Compatibility contracts complete'; } catch(error) {await report({stage:'failure', message:String(error), stack:error.stack});document.body.textContent=String(error);} } @@ -409,6 +425,9 @@ await runNativeAcceptance({ platform, device, bootstrapCode, + buildPlugin: installedVitePlugin, + bootstrapDirectory: `${consumer}/expansion-bootstrap`, + forbiddenModuleRoots: [`${root}/src`, `${root}/dist`], shell: consumer, appId: "jp.rdlabo.workerscapacitor.events", worker: `${artifacts}/worker.ts`, diff --git a/demo/tests/experiments/node-compat/native-measurements.mjs b/demo/tests/experiments/node-compat/native-measurements.mjs new file mode 100644 index 00000000..c3d17fad --- /dev/null +++ b/demo/tests/experiments/node-compat/native-measurements.mjs @@ -0,0 +1,44 @@ +// Test-only instrumentation. Restore constructors even when a contract fails. +export const measurements = {}; +export async function measureOperation(name, operation, warmOperation) { + const constructors = {}, + wasm = [], + memories = []; + for (const kind of ["Module", "Instance"]) { + const original = WebAssembly[kind]; + constructors[kind] = original; + WebAssembly[kind] = new Proxy(original, { + construct(target, args) { + const started = performance.now(); + const value = Reflect.construct(target, args); + wasm.push({ + kind, + elapsedMs: performance.now() - started, + ...(kind === "Module" ? { bytes: args[0].byteLength } : {}), + }); + if (kind === "Instance" && value.exports.memory) + memories.push(value.exports.memory); + return value; + }, + }); + } + const started = performance.now(); + try { + const value = await operation(); + measurements[name] = { + operationMs: performance.now() - started, + wasm, + wasmMemoryBytes: memories.reduce((n, m) => n + m.buffer.byteLength, 0), + }; + if (warmOperation) + measurements[name].warmBatches = Array.from({ length: 10 }, () => { + const start = performance.now(); + for (let n = 0; n < 100; n++) warmOperation(); + return { iterations: 100, elapsedMs: performance.now() - start }; + }); + return value; + } finally { + for (const [kind, original] of Object.entries(constructors)) + WebAssembly[kind] = original; + } +} diff --git a/demo/tests/experiments/node-compat/prepare-events-consumer.mjs b/demo/tests/experiments/node-compat/prepare-events-consumer.mjs index 4b686b42..f08b709d 100644 --- a/demo/tests/experiments/node-compat/prepare-events-consumer.mjs +++ b/demo/tests/experiments/node-compat/prepare-events-consumer.mjs @@ -10,15 +10,60 @@ const output = new URL("../../../artifacts/node-inventory/", import.meta.url); await mkdir(output, { recursive: true }); await mkdir(join(root, "artifacts/npm"), { recursive: true }); const packed = await mkdtemp(join(root, "artifacts/npm/events-")); -execFileSync("npm", ["pack", "--pack-destination", packed], { - cwd: root, - stdio: "inherit", -}); +const version = process.env.CONSUMER_VERSION; +assert.ok( + !version || /^\d+\.\d+\.\d+$/.test(version), + "CONSUMER_VERSION must be an exact release version", +); +const registry = "https://registry.npmjs.org"; +const published = version + ? JSON.parse( + execFileSync( + "npm", + [ + "view", + `cf-workers-native@${version}`, + "--json", + `--registry=${registry}`, + "--prefer-online", + ], + { encoding: "utf8" }, + ), + ) + : undefined; +if (published) assert.equal(published.version, version); +execFileSync( + "npm", + [ + "pack", + ...(version + ? [ + `cf-workers-native@${version}`, + "--ignore-scripts", + `--registry=${registry}`, + ] + : []), + "--pack-destination", + packed, + ], + { + cwd: root, + stdio: "inherit", + }, +); const archives = (await readdir(packed)).filter((name) => name.endsWith(".tgz"), ); assert.equal(archives.length, 1); const tarball = join(packed, archives[0]); +if (published) + assert.equal( + published.dist.integrity, + "sha512-" + + createHash("sha512") + .update(await readFile(tarball)) + .digest("base64"), + ); const tarballSha256 = createHash("sha256") .update(await readFile(tarball)) .digest("hex"); @@ -63,6 +108,22 @@ execFileSync("npm", ["install", "--no-audit", "--no-fund"], { }); await writeFile( new URL("events-consumer.json", output), - JSON.stringify({ consumer, tarball, tarballSha256 }, null, 2) + "\n", + JSON.stringify( + { + consumer, + tarball, + tarballSha256, + ...(published + ? { + source: "npm", + version, + registry, + integrity: published.dist.integrity, + } + : { source: "checkout" }), + }, + null, + 2, + ) + "\n", ); console.log(`Prepared independent events consumer: ${consumer}`); diff --git a/demo/tests/experiments/published-package/README.md b/demo/tests/experiments/published-package/README.md new file mode 100644 index 00000000..4ef5ff0d --- /dev/null +++ b/demo/tests/experiments/published-package/README.md @@ -0,0 +1,47 @@ +# Published-package onboarding acceptance + +This fixture starts from an exact npm release in a temporary consumer. The Worker +contains ordinary HTML, a link, a form and a redirect, with no mobile SDK imports. +Only the native bootstrap imports `cf-workers-native/capacitor`. + +Run from the repository root, with the target devices already booted: + +```sh +CONSUMER_VERSION=0.1.5 npm run test:events:native:prepare --prefix demo +node demo/tests/experiments/published-package/native.mjs ios +node demo/tests/experiments/published-package/native.mjs android +``` + +Run serially. Preparation verifies the npm registry SHA-512 integrity and records +the tarball SHA-256. Each native run checks that hash, uses the installed Vite +plugin and runtime, and rejects modules from the checkout's `src` and `dist`. +The shared harness builds and installs the native application. A local HTTPS +Miniflare server executes the Worker; an injected UI test driver operates the link +and form. No production service is contacted. + +The assertions require the initial page, `/details`, exactly one POST to `/save`, +a 303 redirect and the saved page containing exactly one submission. Evidence is +written under `artifacts/validation/published-/onboarding/`. + +The generated evidence is specific to the tested version. This network-execution +fixture does not establish offline operation, framework-wide compatibility or +whole-module Node.js compatibility. A single acceptance run is not a startup benchmark. + +For repeated process-cold startup measurements, use: + +```sh +node demo/tests/experiments/published-package/benchmark.mjs ios 3 +node demo/tests/experiments/published-package/benchmark.mjs android 3 +``` + +Each sample clears application data and relaunches the process. Installation and +build time are excluded. OS/compiler caches are not reset. Results contain the +server-observed time to the first rendered page, full navigation/form duration, +and web asset bytes. The small sample reports min/median/max, not p95, and is not +a device-to-device performance comparison. Wasm module/instance initialization, +linear-memory bytes and batched warm compression timing are recorded separately +by `node-compat/expansion-native.mjs`. + +To measure an unreleased checkout, prepare without `CONSUMER_VERSION` and run with +`ACCEPTANCE_CHECKOUT=1`. Its evidence directory uses the tarball hash rather than a +published version label; it must not be presented as evidence for an npm release. diff --git a/demo/tests/experiments/published-package/benchmark.mjs b/demo/tests/experiments/published-package/benchmark.mjs new file mode 100644 index 00000000..8713458c --- /dev/null +++ b/demo/tests/experiments/published-package/benchmark.mjs @@ -0,0 +1,80 @@ +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { readFile, writeFile, mkdir } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import assert from "node:assert/strict"; +const exec = promisify(execFile); +const [platform, device, count = "3"] = process.argv.slice(2); +assert.ok( + ["ios", "android"].includes(platform) && + device && + /^\d+$/.test(count) && + Number(count) >= 3 && + Number(count) <= 20, + "Usage: benchmark.mjs ios|android [3..20 samples]", +); +const root = fileURLToPath(new URL("../../../..", import.meta.url)); +const evidence = JSON.parse( + await readFile( + `${root}/demo/artifacts/node-inventory/events-consumer.json`, + "utf8", + ), +); +const label = + evidence.source === "npm" + ? `published-${evidence.version}` + : `candidate-${evidence.tarballSha256.slice(0, 12)}`; +const artifacts = `${root}/artifacts/validation/${label}/onboarding/${platform}`; +await mkdir(artifacts, { recursive: true }); +const samples = []; +for (let n = 0; n < Number(count); n++) { + const run = `sample-${n + 1}`; + console.log(`${platform}: process-cold launch sample ${n + 1}/${count}`); + const { stdout, stderr } = await exec( + process.execPath, + [fileURLToPath(new URL("native.mjs", import.meta.url)), platform, device], + { + cwd: root, + env: { ...process.env, ACCEPTANCE_RUN: run }, + maxBuffer: 64 * 1024 * 1024, + }, + ); + await writeFile(`${artifacts}/${run}/build.log`, stdout + "\n" + stderr); + samples.push( + JSON.parse( + await readFile(`${artifacts}/${run}/${platform}-summary.json`, "utf8"), + ), + ); +} +const stats = (key) => { + const values = samples.map((s) => s[key]).sort((a, b) => a - b); + assert.ok( + values.every((value) => Number.isFinite(value) && value >= 0), + `Missing or invalid ${key} measurement`, + ); + const middle = Math.floor(values.length / 2); + return { + min: values[0], + median: + values.length % 2 + ? values[middle] + : (values[middle - 1] + values[middle]) / 2, + max: values.at(-1), + }; +}; +const report = { + platform, + device, + source: evidence.source, + tarballSha256: evidence.tarballSha256, + samples, + launchToFirstPageMs: stats("launchToFirstPageMs"), + elapsedFlowMs: stats("elapsedFlowMs"), + limits: + "Application data cleared and process relaunched for each sample. Native build, installation and server startup excluded. OS/compiler caches are not reset. Local HTTPS backend, no production network. Not a platform performance comparison or p95 estimate.", +}; +await writeFile( + `${artifacts}/benchmark.json`, + JSON.stringify(report, null, 2) + "\n", +); +console.log(JSON.stringify(report, null, 2)); diff --git a/demo/tests/experiments/published-package/native.mjs b/demo/tests/experiments/published-package/native.mjs new file mode 100644 index 00000000..8912a534 --- /dev/null +++ b/demo/tests/experiments/published-package/native.mjs @@ -0,0 +1,150 @@ +import assert from "node:assert/strict"; +import { readFile, mkdir, writeFile, copyFile, stat } from "node:fs/promises"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { createHash } from "node:crypto"; +import { Miniflare, convertV4MiniflareOptions } from "miniflare"; +import { nativeBackend } from "../../native/backend.mjs"; +import { runNativeAcceptance } from "../../native/acceptance.mjs"; + +const [platform, device] = process.argv.slice(2); +assert.ok( + ["ios", "android"].includes(platform) && device, + "Usage: native.mjs ios|android ", +); +const root = fileURLToPath(new URL("../../../..", import.meta.url)); +const evidence = JSON.parse( + await readFile( + new URL( + "../../../artifacts/node-inventory/events-consumer.json", + import.meta.url, + ), + "utf8", + ), +); +assert.ok( + evidence.source === "npm" || process.env.ACCEPTANCE_CHECKOUT === "1", + "Prepare with CONSUMER_VERSION=, or explicitly set ACCEPTANCE_CHECKOUT=1 for a local candidate", +); +assert.equal( + createHash("sha256") + .update(await readFile(evidence.tarball)) + .digest("hex"), + evidence.tarballSha256, +); +const { consumer } = evidence; +const version = + evidence.version ?? + JSON.parse( + await readFile( + `${consumer}/node_modules/cf-workers-native/package.json`, + "utf8", + ), + ).version; +const installed = `${consumer}/node_modules/cf-workers-native`; +assert.equal( + JSON.parse(await readFile(`${installed}/package.json`, "utf8")).version, + version, +); +const { workersCapacitorPlugin } = await import( + pathToFileURL(`${installed}/dist/tooling/vite.js`).href +); +const label = + evidence.source === "npm" + ? `published-${version}` + : `candidate-${evidence.tarballSha256.slice(0, 12)}`; +const run = process.env.ACCEPTANCE_RUN; +assert.ok(!run || /^[a-zA-Z0-9_-]+$/.test(run)); +const artifacts = `${root}/artifacts/validation/${label}/onboarding/${platform}${run ? "/" + run : ""}`; +const fixture = `${consumer}/onboarding`; +await mkdir(artifacts, { recursive: true }); +await mkdir(fixture, { recursive: true }); +await copyFile(new URL("worker.mjs", import.meta.url), `${fixture}/worker.mjs`); +await writeFile( + `${fixture}/wrangler.json`, + JSON.stringify({ + name: "published-onboarding", + main: "worker.mjs", + compatibility_date: "2026-10-17", + }), +); +const harness = new Miniflare( + convertV4MiniflareOptions({ + modules: true, + script: await readFile(`${fixture}/worker.mjs`, "utf8"), + compatibilityDate: "2026-10-17", + }), +); +await harness.ready; +let startupAt; +const backend = await nativeBackend({ + onReport: (report) => { + if (report.stage === "startup") startupAt = Date.now(); + }, + dispatch: (...args) => harness.dispatchFetch(...args), + dispose: () => harness.dispose(), + driver: `(async()=>{ + const report=async value=>fetch('/__acceptance/report',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(value)}); + try { + const path=location.pathname; + if(path==='/') { + if(document.querySelector('h1')?.textContent!=='My Workers app') throw Error('Initial page did not render'); + await report({stage:'startup',documentReadyMs:performance.now()}); + document.querySelector('a').click(); + } else if(path==='/details') { + const form=document.querySelector('form'); + form.elements.message.value='From the mobile app'; + form.requestSubmit(); + } else if(path==='/saved') { + if(document.querySelector('output')?.textContent!=='1') throw Error('Mutation did not execute exactly once'); + await report({stage:'complete',navigation:true,form:true,submissions:1}); + } else throw Error('Unexpected page '+path); + } catch(error) {await report({stage:'failure',message:String(error)});} + })();`, +}); +await runNativeAcceptance({ + backend, + measurements: (launched) => ({ launchToFirstPageMs: startupAt - launched }), + artifacts, + platform, + device, + shell: consumer, + appId: "jp.rdlabo.workerscapacitor.events", + buildPlugin: workersCapacitorPlugin, + assetsExecution: null, + bootstrapDirectory: `${fixture}/bootstrap`, + forbiddenModuleRoots: [`${root}/src`, `${root}/dist`], + worker: `${fixture}/worker.mjs`, + workerConfig: `${fixture}/wrangler.json`, + validate: async (backend) => { + assert.ok( + backend.reports.some( + (r) => r.stage === "startup" && Number.isFinite(r.documentReadyMs), + ), + ); + const mutations = backend.requests.filter( + (r) => r.path === "/save" && r.method === "POST", + ); + assert.equal(mutations.length, 1); + assert.equal(mutations[0].status, 303); + for (const path of ["/", "/details", "/saved"]) + assert.ok( + backend.requests.some((r) => r.path === path && r.status === 200), + path, + ); + }, +}); +await writeFile( + `${artifacts}/package.json`, + JSON.stringify( + { + ...evidence, + packageBytes: (await stat(evidence.tarball)).size, + workerSha256: createHash("sha256") + .update(await readFile(`${fixture}/worker.mjs`)) + .digest("hex"), + }, + null, + 2, + ) + "\n", +); +console.log(`${platform}: ${label} page, link, form and redirect passed`); diff --git a/demo/tests/experiments/published-package/worker.mjs b/demo/tests/experiments/published-package/worker.mjs new file mode 100644 index 00000000..7cddc8e5 --- /dev/null +++ b/demo/tests/experiments/published-package/worker.mjs @@ -0,0 +1,28 @@ +// Ordinary Workers application: no SDK imports or mobile-specific routes. +let submissions = 0; +const page = (body) => + new Response( + `Workers mobile acceptance${body}`, + { headers: { "content-type": "text/html;charset=utf-8" } }, + ); +export default { + async fetch(request) { + const url = new URL(request.url); + if (url.pathname === "/") + return page('

My Workers app

Open details'); + if (url.pathname === "/details") + return page( + '

Details

', + ); + if (url.pathname === "/save" && request.method === "POST") { + const form = await request.formData(); + if (form.get("message") !== "From the mobile app") + return new Response("Invalid input", { status: 400 }); + submissions++; + return Response.redirect(new URL("/saved", url).href, 303); + } + if (url.pathname === "/saved") + return page(`

Saved

${submissions}`); + return new Response("Not found", { status: 404 }); + }, +}; diff --git a/demo/tests/native/acceptance.mjs b/demo/tests/native/acceptance.mjs index e22595d8..ee8030fd 100644 --- a/demo/tests/native/acceptance.mjs +++ b/demo/tests/native/acceptance.mjs @@ -11,6 +11,7 @@ import { readdir, stat, access, + realpath, } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { join } from "node:path"; @@ -28,6 +29,7 @@ export async function runNativeAcceptance({ publicDir = false, kvExecution, remoteBindings, + assetsExecution = "remote", execution = "network", expectedMode = "worker", render = "/", @@ -38,7 +40,11 @@ export async function runNativeAcceptance({ appId = "jp.rdlabo.workerscapacitor.opennext", bootstrapCode, configureNative, + buildPlugin = workersCapacitorPlugin, + bootstrapDirectory, + forbiddenModuleRoots = [], timeout = 60000, + measurements = () => ({}), }) { const physicalIOS = platform === "ios" && process.env.IOS_PHYSICAL_DEVICE === "1"; @@ -73,8 +79,9 @@ export async function runNativeAcceptance({ "Physical iOS acceptance requires a deployed HTTPS backend; local test CAs are simulator-only", ); await mkdir(artifacts, { recursive: true }); - const bootstrap = join(artifacts, "bootstrap"); - await mkdir(bootstrap, { recursive: true }); + const bootstrapPath = bootstrapDirectory ?? join(artifacts, "bootstrap"); + await mkdir(bootstrapPath, { recursive: true }); + const bootstrap = await realpath(bootstrapPath); await writeFile( join(bootstrap, "index.html"), '', @@ -89,13 +96,25 @@ export async function runNativeAcceptance({ root: bootstrap, publicDir, plugins: [ - workersCapacitorPlugin({ + { + name: "acceptance-package-isolation", + generateBundle() { + for (const id of this.getModuleIds()) { + for (const root of forbiddenModuleRoots) + assert.ok( + !id.startsWith(root + "/"), + `Checkout module entered installed-package build: ${id}`, + ); + } + }, + }, + buildPlugin({ execution, worker, workerConfig, kvExecution, remoteBindings, - assetsExecution: "remote", + assetsExecution: assetsExecution ?? undefined, }), ], build: { outDir: join(shell, "www"), emptyOutDir: true }, @@ -262,6 +281,7 @@ export async function runNativeAcceptance({ JSON.stringify( { platform, + ...measurements(launched), elapsedFlowMs: Date.now() - launched, bundleBytes: ( await Promise.all( diff --git a/demo/tests/native/backend.mjs b/demo/tests/native/backend.mjs index 927e1169..305c010c 100644 --- a/demo/tests/native/backend.mjs +++ b/demo/tests/native/backend.mjs @@ -12,6 +12,7 @@ export async function nativeBackend({ dispose, instrument = () => true, tlsDirectory, + onReport = () => {}, }) { const directory = tlsDirectory ?? (await mkdtemp(join(tmpdir(), "native-acceptance-"))); @@ -51,7 +52,9 @@ export async function nativeBackend({ const chunks = []; for await (const chunk of req) chunks.push(chunk); if (req.url === "/__acceptance/report") { - reports.push(JSON.parse(Buffer.concat(chunks).toString())); + const report = JSON.parse(Buffer.concat(chunks).toString()); + reports.push(report); + onReport(report); res.end("ok"); return; } diff --git a/docs/README.md b/docs/README.md index c78d9e7f..026b0abb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -57,7 +57,7 @@ Start with network execution: requests go to your existing deployed Worker. Bund Follow the [native embedding verification](native-embedding.md#verification) or [Capacitor acceptance flow](integration.md#6-verify-the-existing-application-in-capacitor). Test navigation, authentication, mutations, lifecycle and network failures in your own app. Simulator/emulator results do not replace checking the devices you intend to support. -Review [security responsibilities](integration.md#5-security-the-device-is-not-a-trusted-server) and [known limitations](compatibility.md). This release is **0.1.4**; native embedding does not supply general Capacitor plugin integration or automatic store publication. +Review [security responsibilities](integration.md#5-security-the-device-is-not-a-trusted-server) and [known limitations](compatibility.md). This release is **0.1.5**; native embedding does not supply general Capacitor plugin integration or automatic store publication. ## Examples and development diff --git a/docs/build-setup.md b/docs/build-setup.md index 62ae3e5d..50a59cc2 100644 --- a/docs/build-setup.md +++ b/docs/build-setup.md @@ -31,7 +31,7 @@ application's runtime import changes; Worker source and execution options are sh | Android | `workersAndroid` from `cf-workers-native/android` | [Existing Android app](native-embedding.md#android) | | Capacitor | `workersCapacitor` from `cf-workers-native/capacitor` | [Capacitor app](integration.md) | -Use `cf-workers-native@0.1.4` with matching +Use `cf-workers-native@0.1.5` with matching native dependencies; see [dependency setup](native-embedding.md#dependency-setup). Build the corresponding entry for each target, then package the entire output directory. Existing Capacitor examples below use `workersCapacitor`; the same diff --git a/docs/compatibility.md b/docs/compatibility.md index 1c45e204..242ef4e1 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -11,7 +11,7 @@ The earlier `workers-capacitor` package releases 0.1.0 Preview and the explicitl | Host | Scope | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| iOS embedding | 0.1.4 `WorkersIOS` SwiftPM product for UIKit/SwiftUI. Close before removing a UIKit controller; SwiftUI dismantling closes automatically. | +| iOS embedding | 0.1.5 `WorkersIOS` SwiftPM product for UIKit/SwiftUI. Close before removing a UIKit controller; SwiftUI dismantling closes automatically. | | Android embedding | Maven Central SDK `io.github.rdlabo:cf-workers-native-android:0.1.2`. Requires WebView `MULTI_PROFILE`; one SDK screen at a time, with persistent SDK cookies isolated from host cookies. | | Capacitor | Existing Capacitor 8 integration, verified with 8.5.3; use this path for general native plugins. | diff --git a/docs/development.md b/docs/development.md index 5a439f82..14cfb6e9 100644 --- a/docs/development.md +++ b/docs/development.md @@ -135,7 +135,7 @@ requires a verified namespace, release signing key and explicit publication. Run the independent consumer suite against an exact registry version: ```sh -CONSUMER_PACKAGE=cf-workers-native@0.1.4 npm run test:package +CONSUMER_PACKAGE=cf-workers-native@0.1.5 npm run test:package ``` This downloads the published tarball instead of packing the checkout. The same @@ -144,3 +144,26 @@ shared runtime contracts. `artifacts/package-results.json` records the temporary consumer directory. Its `node_modules/cf-workers-native` directory can also be passed as `WORKERS_NPM_PACKAGE_DIR` to the native embedding runners. Without `CONSUMER_PACKAGE`, the suite builds and packs the local checkout as before. + +For native acceptance from a fresh registry installation, prepare once, then run +the ordinary Worker onboarding flow on each booted simulator/emulator: + +```sh +CONSUMER_VERSION=0.1.5 npm run test:events:native:prepare --prefix demo +node demo/tests/experiments/published-package/native.mjs ios +node demo/tests/experiments/published-package/native.mjs android +``` + +Run these commands serially: the consumer shares its generated web directory. +Preparation verifies npm's SHA-512 integrity and records the archive SHA-256. +The native build imports the installed Vite plugin and runtime, and rejects SDK +modules from the checkout. The fixture checks initial rendering, link navigation, +form submission exactly once and the POST/303/GET flow against a local HTTPS +Workers backend. It does not contact a production service. + +Evidence is written to `artifacts/validation/published-/onboarding/`. +`elapsedFlowMs` measures native launch through completion of the whole flow; +it is not a startup benchmark. `documentReadyMs` is the document's performance +clock, and `bundleBytes` covers generated web assets, not the native app binary. +The [published-package fixture](../demo/tests/experiments/published-package/README.md) +documents the procedure and verification limits. diff --git a/docs/integration.md b/docs/integration.md index 1a1636f5..f77b66cd 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -87,7 +87,7 @@ For the Vite example below, install `vite@7` as a dev dependency if it is not al Set the application's own ID/name and `webDir: "www"`. Use Swift Package Manager for iOS; this library does not include CocoaPods integration. Install the npm Preview using its exact version. The `latest` tag includes 0.x Preview releases: ```sh -npm install --save-exact cf-workers-native@0.1.4 +npm install --save-exact cf-workers-native@0.1.5 ``` For a source build instead, create a package tarball from a checkout of this repository: @@ -98,7 +98,7 @@ npm install npm pack # In the application package: -npm install /path/to/cf-workers-native/cf-workers-native-0.1.4.tgz +npm install /path/to/cf-workers-native/cf-workers-native-0.1.5.tgz ``` `npm pack` runs the SDK build through `prepack`. Install the generated tarball rather than the checkout directory: directory installs can create a symlink back to the SDK tree, including a recursive path when the app lives inside that checkout. The tarball filename above uses the current source version. @@ -107,11 +107,11 @@ The installed package is named `cf-workers-native`; the checkout directory can r ### Migrating from the old npm package -If your application already depends on `workers-capacitor`, remove it before installing the renamed package. Keeping both dependencies can make Capacitor discover and register the same native plugin twice. In the application package, using 0.1.4: +If your application already depends on `workers-capacitor`, remove it before installing the renamed package. Keeping both dependencies can make Capacitor discover and register the same native plugin twice. In the application package, using 0.1.5: ```sh npm uninstall workers-capacitor -npm install --save-exact cf-workers-native@0.1.4 +npm install --save-exact cf-workers-native@0.1.5 ``` Change runtime imports from `workers-capacitor` to `cf-workers-native/capacitor`, and change other package subpaths to the `cf-workers-native` prefix. Rebuild the application's mobile web assets with its existing build command, then run `npx cap sync` to regenerate native plugin dependencies. New applications use the installation steps above directly. diff --git a/docs/native-embedding.md b/docs/native-embedding.md index f496fc6b..31b3b3c9 100644 --- a/docs/native-embedding.md +++ b/docs/native-embedding.md @@ -12,9 +12,10 @@ You will build the web assets in your web project, add the native SDK to your app, then open and close a Workers screen through its native API. The SDK resolves Capacitor internally; your native app does not need Capacitor configuration or CLI commands. -**0.1.4:** install `cf-workers-native@0.1.4` with matching +**0.1.5:** install `cf-workers-native@0.1.5` with matching [native dependencies](#dependency-setup). Android requires WebView `MULTI_PROFILE` -support. The native Android SDK is available from Maven Central at version `0.1.2`. +support. The previously published Maven `0.1.2` SDK does not include the +0.1.5 TLS upgrade bridge; see the Android version boundary below. ## Shared web build @@ -90,18 +91,19 @@ entries are subpath imports, not separate npm packages. There is no bare package Install the npm Preview in the web project. The `latest` tag includes 0.x Preview releases: ```sh -npm install --save-exact cf-workers-native@0.1.4 +npm install --save-exact cf-workers-native@0.1.5 ``` -Use the dependency set below. npm 0.1.4 and SwiftPM `v0.1.4` share the release -commit. The Android native implementation is unchanged from Maven 0.1.2, so -existing Android applications can continue using that published artifact: +Use the dependency set below. npm 0.1.5 and SwiftPM `v0.1.5` share the release +commit. For Capacitor, npm includes the matching Android native implementation. +The separately published Maven 0.1.2 artifact predates the 0.1.5 TLS upgrade +bridge; do not treat it as the matching native SDK for all 0.1.5 features: -| Component | Dependency | -| ---------- | --------------------------------------------------------------------- | -| JavaScript | `cf-workers-native@0.1.4` | -| iOS | SwiftPM tag `v0.1.4`, product `WorkersIOS` | -| Android | `io.github.rdlabo:cf-workers-native-android:0.1.2` from Maven Central | +| Component | Dependency | +| ---------- | ------------------------------------------------------------------------------------------------------------- | +| JavaScript | `cf-workers-native@0.1.5` | +| iOS | SwiftPM tag `v0.1.5`, product `WorkersIOS` | +| Android | Matching `v0.1.5` native sources via local publication; Maven Central `0.1.2` predates the TLS upgrade bridge | For local SDK development, create a JavaScript package tarball from the matching checkout: @@ -111,7 +113,7 @@ npm install npm pack # In the web project: -npm install /path/to/cf-workers-native/cf-workers-native-0.1.4.tgz +npm install /path/to/cf-workers-native/cf-workers-native-0.1.5.tgz ``` `npm pack` runs the build through `prepack`; the filename above uses the current @@ -128,7 +130,7 @@ The source checkout's npm and build tools belong in the web build or CI. ## iOS Add `https://github.com/rdlabo-dev/cf-workers-native` as a Swift package pinned to -tag `v0.1.4` for npm 0.1.4 (or the matching revision for a source build), and select the `WorkersIOS` product. The existing `v0.1.2` tag predates the package rename; use `v0.1.4` for this release. Use Xcode 26 / +tag `v0.1.5` for npm 0.1.5 (or the matching revision for a source build), and select the `WorkersIOS` product. The existing `v0.1.2` tag predates the package rename; use `v0.1.5` for this release. Use Xcode 26 / Swift 6.2 or newer, as required by the native dependency toolchain. Add your generated assets as a folder resource, for example `WorkerWeb`, then create the controller from that directory on the main actor: @@ -172,12 +174,15 @@ from JavaScript. Explicit closure cancels the pending startup notification. Add the SDK's Android Maven publication to your app's dependencies and include the generated web directory under `src/main/assets/`, for example `worker-web`. The publication resolves Capacitor and its other native dependencies. -The new Maven coordinate is `io.github.rdlabo:cf-workers-native-android:0.1.2`. -It is published and resolves from Maven Central. npm 0.1.4 does not change the -Android native implementation; this existing artifact remains compatible. +The previously published Maven coordinate is `io.github.rdlabo:cf-workers-native-android:0.1.2`. +This is the previously published SDK, not the matching Android native code for +npm 0.1.5. In particular, it does not include the new same-connection TLS upgrade +bridge. For the current implementation, use the Android native sources from the +matching release with the local publication workflow in [development](development.md#android-maven-publication), +or use Capacitor, which installs those sources from npm. Use Java 21 and Android SDK 36, with `ANDROID_HOME` pointing to the installed SDK. Include `google()` and `mavenCentral()` in your app's dependency repositories. -In your app's Groovy `build.gradle`: +For an application deliberately retaining that older SDK, the Groovy dependency is: ```groovy dependencies { diff --git a/scripts/build-codecs.mjs b/scripts/build-codecs.mjs index fa1a645f..a3bedfd2 100644 --- a/scripts/build-codecs.mjs +++ b/scripts/build-codecs.mjs @@ -1,9 +1,13 @@ import { build } from "esbuild"; -import { readFile } from "node:fs/promises"; +import { readFile, copyFile } from "node:fs/promises"; import { createHash } from "node:crypto"; // Fail before packaging if a generated implementation and its provenance drift. for (const [asset, manifest] of [ + [ + "src/compat/zlib/native-zlib.wasm", + "src/compat/zlib/native-zlib.build.json", + ], [ "src/compat/crypto/native-crypto.wasm", "src/compat/crypto/native-crypto.build.json", @@ -70,3 +74,17 @@ await build({ target: "es2022", loader: { ".wasm": "binary" }, }); + +await build({ + entryPoints: ["src/compat/zlib/native-zlib.ts"], + outfile: "dist/compat/zlib/native-zlib.js", + bundle: true, + format: "esm", + target: "es2022", + loader: { ".wasm": "binary" }, +}); + +await copyFile( + "src/compat/zlib/classic-base.d.cts", + "dist/compat/zlib/classic-base.d.cts", +); diff --git a/scripts/build-native-codecs.mjs b/scripts/build-native-codecs.mjs index a5909f54..86fd0c07 100644 --- a/scripts/build-native-codecs.mjs +++ b/scripts/build-native-codecs.mjs @@ -56,6 +56,7 @@ execFileSync( "-sFILESYSTEM=0", "-sASSERTIONS=0", "-sMALLOC=emmalloc", + "-sABORTING_MALLOC=0", "--no-entry", '-sEXPORTED_FUNCTIONS=["_malloc","_free"]', "-o", @@ -68,6 +69,9 @@ writeFileSync( JSON.stringify( { revisions, + bindingSha256: createHash("sha256") + .update(readFileSync("src/compat/zlib/native-codecs.c")) + .digest("hex"), sha256: createHash("sha256").update(readFileSync(output)).digest("hex"), }, null, diff --git a/scripts/build-native-zlib.mjs b/scripts/build-native-zlib.mjs new file mode 100644 index 00000000..1137af5c --- /dev/null +++ b/scripts/build-native-zlib.mjs @@ -0,0 +1,48 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve, join } from "node:path"; +import { createHash } from "node:crypto"; +const sdk = resolve(process.env.CODEC_EMSDK ?? "artifacts/toolchains/emsdk"); +const revision = execFileSync("git", ["rev-parse", "HEAD"], { + cwd: sdk, + encoding: "utf8", +}).trim(); +if (revision !== "e4fe26ef59168ff44f4c23c466e497bf60b3411e") + throw Error("Unexpected emsdk revision"); +const binding = "src/compat/zlib/native-zlib.c", + output = "src/compat/zlib/native-zlib.wasm"; +execFileSync( + join(sdk, "upstream/emscripten/emcc"), + [ + binding, + "-O3", + "-sUSE_ZLIB=1", + "-sSTANDALONE_WASM=1", + "-sALLOW_MEMORY_GROWTH=1", + "-sFILESYSTEM=0", + "-sASSERTIONS=0", + "-sMALLOC=emmalloc", + "-sABORTING_MALLOC=0", + "--no-entry", + '-sEXPORTED_FUNCTIONS=["_malloc","_free"]', + "-o", + output, + ], + { stdio: "inherit" }, +); +const hash = (file) => + createHash("sha256").update(readFileSync(file)).digest("hex"); +writeFileSync( + "src/compat/zlib/native-zlib.build.json", + JSON.stringify( + { + emsdk: revision, + zlib: "1.3.1", + portSha256: hash(join(sdk, "upstream/emscripten/tools/ports/zlib.py")), + bindingSha256: hash(binding), + sha256: hash(output), + }, + null, + 2, + ) + "\n", +); diff --git a/scripts/test-package.mjs b/scripts/test-package.mjs index 9e49ade0..3b24e537 100644 --- a/scripts/test-package.mjs +++ b/scripts/test-package.mjs @@ -43,6 +43,9 @@ console.log(`Consumer package: ${pack.name}@${pack.version} (${temp})`); for (const required of [ "dist/ios.js", "dist/android.js", + "dist/compat/zlib/classic-base.cjs", + "dist/compat/zlib/classic-base.d.cts", + "dist/compat/zlib/native-zlib.js", "native/ios-host/WorkersView.swift", "native/android/consumer-rules.pro", "native/android/src/main/java/jp/rdlabo/workerscapacitor/WorkersNative.java", @@ -111,7 +114,9 @@ for (const notice of [ ), "dist/compat/crypto/LICENSE-APACHE", "dist/compat/crypto/LICENSE-3RD-PARTY.md", - ...["brotli", "zstd", "emscripten", "musl"].map( + "dist/compat/zlib/LICENSE-APACHE", + "dist/compat/zlib/LICENSE-3RD-PARTY.md", + ...["brotli", "zstd", "zlib", "emscripten", "musl"].map( (name) => `dist/compat/zlib/LICENSE.${name}`, ), ]) { diff --git a/src/compat/crypto/cipher-info.ts b/src/compat/crypto/cipher-info.ts new file mode 100644 index 00000000..d2033951 --- /dev/null +++ b/src/compat/crypto/cipher-info.ts @@ -0,0 +1,333 @@ +// Cipher metadata from workerd@5358e3d337336eaf696a48bc1563d808b10feba5. +// Metadata lookup does not imply that the local backend implements the cipher. +import { validateObject } from "../net/validators.js"; +import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +export interface CipherInfo { + name: string; + nid: number; + blockSize: number; + ivLength: number; + keyLength: number; + mode: string; +} +const ciphers: Record = { + "aes-128-cbc": { + name: "AES-128-CBC", + nid: 419, + blockSize: 16, + ivLength: 16, + keyLength: 16, + mode: "cbc", + }, + "aes-192-cbc": { + name: "AES-192-CBC", + nid: 423, + blockSize: 16, + ivLength: 16, + keyLength: 24, + mode: "cbc", + }, + "aes-256-cbc": { + name: "AES-256-CBC", + nid: 427, + blockSize: 16, + ivLength: 16, + keyLength: 32, + mode: "cbc", + }, + "aes-128-ctr": { + name: "AES-128-CTR", + nid: 904, + blockSize: 1, + ivLength: 16, + keyLength: 16, + mode: "ctr", + }, + "aes-192-ctr": { + name: "AES-192-CTR", + nid: 905, + blockSize: 1, + ivLength: 16, + keyLength: 24, + mode: "ctr", + }, + "aes-256-ctr": { + name: "AES-256-CTR", + nid: 906, + blockSize: 1, + ivLength: 16, + keyLength: 32, + mode: "ctr", + }, + "aes-128-ecb": { + name: "AES-128-ECB", + nid: 418, + blockSize: 16, + ivLength: 0, + keyLength: 16, + mode: "ecb", + }, + "aes-192-ecb": { + name: "AES-192-ECB", + nid: 422, + blockSize: 16, + ivLength: 0, + keyLength: 24, + mode: "ecb", + }, + "aes-256-ecb": { + name: "AES-256-ECB", + nid: 426, + blockSize: 16, + ivLength: 0, + keyLength: 32, + mode: "ecb", + }, + "aes-128-ofb": { + name: "AES-128-OFB", + nid: 420, + blockSize: 1, + ivLength: 16, + keyLength: 16, + mode: "ofb", + }, + "aes-192-ofb": { + name: "AES-192-OFB", + nid: 424, + blockSize: 1, + ivLength: 16, + keyLength: 24, + mode: "ofb", + }, + "aes-256-ofb": { + name: "AES-256-OFB", + nid: 428, + blockSize: 1, + ivLength: 16, + keyLength: 32, + mode: "ofb", + }, + "aes-128-gcm": { + name: "id-aes128-GCM", + nid: 895, + blockSize: 1, + ivLength: 12, + keyLength: 16, + mode: "gcm", + }, + "aes-192-gcm": { + name: "id-aes192-GCM", + nid: 898, + blockSize: 1, + ivLength: 12, + keyLength: 24, + mode: "gcm", + }, + "aes-256-gcm": { + name: "id-aes256-GCM", + nid: 901, + blockSize: 1, + ivLength: 12, + keyLength: 32, + mode: "gcm", + }, + "des-cbc": { + name: "DES-CBC", + nid: 31, + blockSize: 8, + ivLength: 8, + keyLength: 8, + mode: "cbc", + }, + "des-ecb": { + name: "DES-ECB", + nid: 29, + blockSize: 8, + ivLength: 0, + keyLength: 8, + mode: "ecb", + }, + "des-ede": { + name: "DES-EDE", + nid: 32, + blockSize: 8, + ivLength: 0, + keyLength: 16, + mode: "ecb", + }, + "des-ede-cbc": { + name: "DES-EDE-CBC", + nid: 43, + blockSize: 8, + ivLength: 8, + keyLength: 16, + mode: "cbc", + }, + "des-ede3-cbc": { + name: "DES-EDE3-CBC", + nid: 44, + blockSize: 8, + ivLength: 8, + keyLength: 24, + mode: "cbc", + }, + "rc2-cbc": { + name: "RC2-CBC", + nid: 37, + blockSize: 8, + ivLength: 8, + keyLength: 16, + mode: "cbc", + }, + rc4: { + name: "RC4", + nid: 5, + blockSize: 1, + ivLength: 0, + keyLength: 16, + mode: "stream", + }, + "aes-256-eax": { + name: "aes-256-eax", + nid: 0, + blockSize: 1, + ivLength: 16, + keyLength: 32, + mode: "stream", + }, + "aes-128-ccm-matter": { + name: "aes-128-ccm-matter", + nid: 0, + blockSize: 1, + ivLength: 13, + keyLength: 16, + mode: "ccm", + }, + "aes-128-ccm-bluetooth-8": { + name: "aes-128-ccm-bluetooth-8", + nid: 0, + blockSize: 1, + ivLength: 13, + keyLength: 16, + mode: "ccm", + }, + "aes-128-gcm-randnonce": { + name: "aes-128-gcm-randnonce", + nid: 0, + blockSize: 1, + ivLength: 0, + keyLength: 16, + mode: "gcm", + }, + "aes-256-gcm-randnonce": { + name: "aes-256-gcm-randnonce", + nid: 0, + blockSize: 1, + ivLength: 0, + keyLength: 32, + mode: "gcm", + }, + "aes-128-gcm-siv": { + name: "aes-128-gcm-siv", + nid: 0, + blockSize: 1, + ivLength: 12, + keyLength: 16, + mode: "gcm", + }, + "aes-128-ccm-bluetooth": { + name: "aes-128-ccm-bluetooth", + nid: 0, + blockSize: 1, + ivLength: 13, + keyLength: 16, + mode: "ccm", + }, + "aes-256-ctr-hmac-sha256": { + name: "aes-256-ctr-hmac-sha256", + nid: 0, + blockSize: 1, + ivLength: 12, + keyLength: 64, + mode: "ctr", + }, + "aes-128-ctr-hmac-sha256": { + name: "aes-128-ctr-hmac-sha256", + nid: 0, + blockSize: 1, + ivLength: 12, + keyLength: 48, + mode: "ctr", + }, + "xchacha20-poly1305": { + name: "xchacha20-poly1305", + nid: 0, + blockSize: 1, + ivLength: 24, + keyLength: 32, + mode: "stream", + }, + "aes-256-gcm-siv": { + name: "aes-256-gcm-siv", + nid: 0, + blockSize: 1, + ivLength: 12, + keyLength: 32, + mode: "gcm", + }, + "aes-128-eax": { + name: "aes-128-eax", + nid: 0, + blockSize: 1, + ivLength: 16, + keyLength: 16, + mode: "stream", + }, + "chacha20-poly1305": { + name: "chacha20-poly1305", + nid: 950, + blockSize: 1, + ivLength: 12, + keyLength: 32, + mode: "stream", + }, +}; +export function getCipherInfo( + nameOrId: string | number, + options: { keyLength?: number; ivLength?: number } = {}, +): CipherInfo | undefined { + if (typeof nameOrId !== "string" && typeof nameOrId !== "number") + throw new ERR_INVALID_ARG_TYPE("nameOrId", ["string", "number"], nameOrId); + validateObject(options, "options"); + // The native binding converts both optional lengths before looking up a cipher. + const keyLength = + options.keyLength === undefined ? undefined : Number(options.keyLength) | 0; + const ivLength = + options.ivLength === undefined ? undefined : Number(options.ivLength) | 0; + const found = + typeof nameOrId === "string" + ? ciphers[nameOrId.toLowerCase()] + : Object.values(ciphers).find( + (c) => c.nid !== 0 && c.nid !== 950 && c.nid === (nameOrId | 0), + ); + if (!found) return; + const result = { ...found }; + // BoringSSL AEAD metadata uses a separate lookup, without length overrides. + if (found.nid === 0 || found.nid === 950) { + result.name = String(nameOrId); + return result; + } + if (keyLength !== undefined) { + if (found.nid === 5 || found.nid === 37) { + if (keyLength <= 0) return; + } else if (keyLength !== found.keyLength) return; + result.keyLength = keyLength; + } + if (ivLength !== undefined) { + if (found.mode === "gcm") { + if (ivLength <= 0) return; + } else if (ivLength !== found.ivLength) return; + result.ivLength = ivLength; + } + return result; +} diff --git a/src/compat/crypto/metadata.ts b/src/compat/crypto/metadata.ts index 0a574ee8..c9be3a2d 100644 --- a/src/compat/crypto/metadata.ts +++ b/src/compat/crypto/metadata.ts @@ -20,3 +20,5 @@ export function setFips(_enabled: boolean): never { } // Do not copy Workers' fips=true: this adapter uses JavaScript algorithms, // not Workers' FIPS backend. That capability remains an explicit boundary. + +export { getCipherInfo } from "./cipher-info.js"; diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index 2f9ccd22..a493934a 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -13,7 +13,7 @@ hash/HKDF/scrypt, the four Workers ECDH curves, crypto engine/secure-heap stubs, writable memory-backed /tmp, module metadata, HTTP internal helpers, adapter-owned secret and asymmetric KeyObjects, prime generation and checking, incremental signatures, Certificate SPKAC and X509Certificate operations, -Brotli/Zstd codecs and CRC32. +zlib/Brotli/Zstd codecs and CRC32, and cipher metadata lookup. Additional profiles cover process resource/permission/report stubs, its pinned wall-clock hrtime behavior, and TLS hostname/ALPN/options contracts. Native runs also test real TLS exchange and hostname mismatch rejection. @@ -22,15 +22,14 @@ The SDK starts a fresh /tmp volume for each local fetch dispatch. Handles opened inside that request retain their volume; embedded bundle assets stay read-only. Standalone imports outside SDK dispatch share a fallback volume. Copying embedded files to /tmp preserves immutable embedded bytes. Permission changes are validated no-ops and statfs returns the pinned Workers zero values. -These checks do not cover every descriptor or cross-volume operation. Watching files is unsupported in Workers too. +Embedded files support read-only descriptors, FileHandles, streams and links; copies into /tmp are writable and independent. Numeric file timestamps use milliseconds; embedded timestamps remain unchanged. Watching files is unsupported in Workers too. These profiles do not certify every filesystem operation. -Brotli 1.1.0 and Zstd 1.5.7 use bundled, synchronously initialized WebAssembly. +zlib 1.3.1, Brotli 1.1.0 and Zstd 1.5.7 use bundled, synchronously initialized WebAssembly. The profile covers parameters, synchronous/callback operations, incremental streams, flush/reset, byte accounting and output limits for synchronous processing. Streaming output is not cumulatively capped by maxOutputLength. The pinned source revisions and artifact hashes accompany the codecs; rebuild -with scripts/build-native-codecs.mjs and the pinned Emscripten SDK. Remaining -codec edge cases and inherited gzip/deflate behavior still require acceptance. +with scripts/build-native-codecs.mjs or scripts/build-native-zlib.mjs and the pinned Emscripten SDK. Gzip/deflate and Brotli/Zstd streams use the pinned Workers stream lifecycle. The comparative cases include constructor validation, callback timing, reset, close, byte accounting, dictionaries and output limits; whole-module acceptance remains a separate gate. Scrypt uses a synchronous JavaScript backend, including for callback completion, so large parameters can block the WebView. @@ -40,7 +39,7 @@ synchronous extraction of its opaque key material is unavailable. RSA, EC, Ed255 bundled OpenSSL 3.5.9 WebAssembly; RSA/EC streaming and one-shot signatures are compared with Workers and independent test signatures. This covers selected algorithms and operations, not the whole crypto module. Additional key-generation -options and remaining error contracts still need work. +options and remaining error contracts still need work. `getCipherInfo` returns pinned Workers metadata; a successful lookup does not establish local availability of the named encryption algorithm. X509Certificate parses PEM/DER, exposes certificate fields and legacy objects, extracts RSA/EC public keys, checks names and issuers, and verifies signatures with certificate public keys. The pinned Workers runtime returns false for diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index e3bd0a1f..e22b01c9 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,457 @@ export const eventCases = { + fsTimes: async function ({ fs }) { + const readonly = "/bundle/readonly.txt", + writable = "/tmp/time-file"; + fs.writeFileSync(writable, "x"); + const result = []; + for (const path of [readonly, writable]) { + fs.utimesSync(path, 1000, 2000); + result.push(fs.statSync(path).mtimeMs); + const fd = fs.openSync(path, "r"); + fs.futimesSync(fd, 3000n, new Date(4000)); + result.push(fs.fstatSync(fd).mtimeMs); + fs.closeSync(fd); + const handle = await fs.promises.open(path, "r"); + await handle.utimes(new Date(5000), 6000); + result.push((await handle.stat()).mtimeMs); + await handle.close(); + for (const method of ["writeFile", "truncate"]) { + try { + await handle[method](method === "writeFile" ? "x" : 0); + result.push(null); + } catch (error) { + result.push(error.code); + } + } + for (const value of [null, {}, true, undefined]) { + try { + fs.utimesSync(path, value, 0); + result.push(null); + } catch (e) { + result.push(e.code); + } + } + } + return result; + }, + cipherMetadata: async function ({ crypto: c }) { + const results = []; + for (const name of [ + "aes-128-cbc", + "AES-192-CTR", + "aes-256-gcm", + "des-ede3-cbc", + "rc2-cbc", + "rc4", + "chacha20-poly1305", + "aes-128-ccm-matter", + "unknown", + 419, + 419.8, + 950, + 0, + NaN, + ]) { + for (const options of [ + {}, + { ivLength: 0 }, + { ivLength: 1 }, + { ivLength: 16 }, + { ivLength: -1 }, + { ivLength: NaN }, + { ivLength: "12" }, + { keyLength: 0 }, + { keyLength: 1 }, + { keyLength: 16 }, + { keyLength: 32 }, + null, + 3, + ]) { + try { + results.push(c.getCipherInfo(name, options) ?? null); + } catch (e) { + results.push({ error: e.name, code: e.code ?? null }); + } + } + } + for (const input of [null, {}, true, [], undefined]) { + try { + results.push(c.getCipherInfo(input) ?? null); + } catch (e) { + results.push({ error: e.name, code: e.code ?? null }); + } + } + const first = c.getCipherInfo("aes-128-cbc"); + first.keyLength = 999; + results.push(c.getCipherInfo("aes-128-cbc")); + return results; + }, + codecLifecycle: async function ({ zlib: z }) { + const failure = (error) => ({ name: error.name, code: error.code ?? null }); + const result = { options: [], callbacks: [], lifecycle: [] }; + for (const name of ["brotli", "zstd"]) { + const Constructor = + z[name === "brotli" ? "BrotliCompress" : "ZstdCompress"]; + const compress = z[name + "CompressSync"]; + for (const options of [ + { chunkSize: null }, + { chunkSize: NaN }, + { chunkSize: 63 }, + { chunkSize: 64.5 }, + { flush: -1 }, + { flush: 6 }, + { finishFlush: -1 }, + { maxOutputLength: 0 }, + { maxOutputLength: 1 }, + { params: { 999: 1 } }, + { params: { 1: "bad" } }, + { params: { 1: 2, "01": 1 } }, + { params: null }, + null, + 3, + ]) { + for (const mode of ["fast", "info", "stream"]) { + try { + const value = + mode === "stream" + ? new Constructor(options) + : compress( + "hello", + mode === "info" ? { ...options, info: true } : options, + ); + value.destroy?.(); + result.options.push(null); + } catch (error) { + result.options.push(failure(error)); + } + } + } + for (const options of [ + { chunkSize: 63 }, + { chunkSize: NaN }, + { params: null }, + { params: { 999: 1 } }, + {}, + ]) { + let returned = false; + const pending = new Promise((resolve) => { + try { + z[name + "Compress"]("hello", options, (error) => + resolve({ + when: "callback", + returned, + error: error ? failure(error) : null, + }), + ); + } catch (error) { + resolve({ when: "throw", returned, error: failure(error) }); + } + }); + returned = true; + result.callbacks.push(await pending); + } + } + for (const name of [ + "Deflate", + "Inflate", + "Gzip", + "Gunzip", + "DeflateRaw", + "InflateRaw", + "Unzip", + "BrotliCompress", + "BrotliDecompress", + "ZstdCompress", + "ZstdDecompress", + ]) { + const stream = new z[name](); + stream.bytesRead = 7; + const before = [stream.bytesWritten, stream.bytesRead, stream._closed]; + stream.reset(); + const afterReset = [stream.bytesWritten, stream.bytesRead]; + let closes = 0; + stream.on("close", () => closes++); + const closed = new Promise((resolve) => + stream.close((error) => resolve(error?.code ?? null)), + ); + stream.destroy(); + const closeCode = await closed; + let resetError; + try { + stream.reset(); + } catch (error) { + resetError = failure(error); + } + result.lifecycle.push({ + name, + before, + afterReset, + closed: stream._closed, + destroyed: stream.destroyed, + closes, + closeCode, + resetError, + }); + } + const unzip = new z.Unzip(); + const decoded = []; + unzip.on("data", (chunk) => decoded.push(chunk.toString())); + const write = (input) => + new Promise((resolve, reject) => + unzip.write(input, (error) => (error ? reject(error) : resolve())), + ); + await write(z.deflateSync("first")); + unzip.reset(); + await write(z.gzipSync("second")); + await write(z.gzipSync("third")); + unzip.destroy(); + result.resetFormats = decoded.join(""); + return result; + }, + + fsEmbedded: async function ({ fs }) { + const path = "/bundle/readonly.txt"; + const attempt = (fn) => { + try { + return fn() ?? null; + } catch (error) { + return { code: error.code }; + } + }; + const fd = fs.openSync(path, "r"); + const buffer = new Uint8Array(3); + const read = fs.readSync(fd, buffer, 0, 3, 1); + const result = { + read, + buffer: Array.from(buffer), + mode: fs.fstatSync(fd).mode, + write: attempt(() => fs.writeSync(fd, "x")), + truncate: attempt(() => fs.ftruncateSync(fd, 0)), + }; + fs.closeSync(fd); + result.openWrite = attempt(() => fs.openSync(path, "r+")); + const handle = await fs.promises.open(path, "r"); + result.promiseRead = await handle.readFile("utf8"); + try { + await handle.truncate(0); + } catch (error) { + result.promiseTruncate = error.code; + } + await handle.close(); + fs.symlinkSync(path, "/tmp/readonly-link"); + result.link = fs.readlinkSync("/tmp/readonly-link"); + result.linkRead = fs.readFileSync("/tmp/readonly-link", "utf8"); + result.linkMode = fs.statSync("/tmp/readonly-link").mode; + result.linkAccess = attempt(() => + fs.accessSync("/tmp/readonly-link", fs.constants.W_OK), + ); + result.linkWrite = attempt(() => + fs.writeFileSync("/tmp/readonly-link", "changed"), + ); + let text = ""; + for await (const chunk of fs.createReadStream(path, { start: 1, end: 3 })) + text += chunk.toString(); + result.stream = text; + result.content = fs.readFileSync(path, "hex"); + fs.unlinkSync("/tmp/readonly-link"); + fs.linkSync(path, "/tmp/hard-link"); + result.hardRead = fs.readFileSync("/tmp/hard-link", "utf8"); + result.hardWrite = attempt(() => + fs.writeFileSync("/tmp/hard-link", "changed"), + ); + result.hardMetadata = [ + fs.statSync("/tmp/hard-link").nlink, + fs.statSync("/tmp/hard-link").ino, + fs.statSync("/tmp/hard-link").mode, + ]; + await fs.promises.cp("/bundle/nested", "/tmp/copied", { recursive: true }); + result.copied = fs.readFileSync("/tmp/copied/child.txt", "utf8"); + fs.writeFileSync("/tmp/copied/child.txt", "changed copy"); + result.unchanged = fs.readFileSync("/bundle/nested/child.txt", "utf8"); + result.filter = attempt(() => + fs.cpSync(path, "/tmp/filtered", { filter: () => true }), + ); + result.readonlyMutation = attempt(() => fs.writeFileSync(path, "changed")); + const big = fs.statSync(path, { bigint: true }); + result.bigintMetadata = [ + big.size, + big.mode, + big.ino, + big.nlink, + big.atimeNs, + ].map(String); + return result; + }, + + classicCodecs: async function ({ zlib: z }) { + const result = { roundtrips: [], validation: [], streams: [] }; + const failure = (error) => ({ name: error.name, code: error.code ?? null }); + const source = new Uint8Array([0, 1, 2, 3, 254, 255]); + for (const [encode, decode] of [ + ["gzip", "gunzip"], + ["deflate", "inflate"], + ["deflateRaw", "inflateRaw"], + ]) { + for (const input of [ + source, + new DataView(source.buffer, 1, 4), + source.buffer, + "hello", + ]) { + const compressed = z[encode + "Sync"](input); + result.roundtrips.push(Array.from(z[decode + "Sync"](compressed))); + const info = z[encode + "Sync"](input, { info: true }); + result.roundtrips.push({ + decoded: Array.from(z[decode + "Sync"](info.buffer)), + written: info.engine.bytesWritten, + closed: info.engine._closed, + }); + let returned = false; + const done = new Promise((resolve, reject) => + z[encode](input, (error, output) => + error + ? reject(error) + : resolve({ + async: returned, + decoded: Array.from(z[decode + "Sync"](output)), + }), + ), + ); + returned = true; + result.roundtrips.push(await done); + } + for (const info of [false, true]) { + try { + z[decode + "Sync"](z[encode + "Sync"]("output exceeds limit"), { + info, + maxOutputLength: 3, + }); + result.validation.push("unexpected success"); + } catch (error) { + result.validation.push(failure(error)); + } + } + const ctor = encode[0].toUpperCase() + encode.slice(1); + const engine = z["create" + ctor]({ chunkSize: 64 }); + const chunks = []; + const end = new Promise((resolve, reject) => { + engine.on("data", (chunk) => chunks.push(chunk)); + engine.on("error", reject); + engine.on("end", resolve); + }); + engine.write("first"); + await new Promise((resolve) => engine.flush(resolve)); + await new Promise((resolve) => + engine.params(1, z.constants.Z_DEFAULT_STRATEGY, resolve), + ); + engine.end("second"); + await end; + const compressed = new Uint8Array( + chunks.reduce((n, c) => n + c.length, 0), + ); + let offset = 0; + for (const chunk of chunks) { + compressed.set(chunk, offset); + offset += chunk.length; + } + result.streams.push({ + plain: new TextDecoder().decode(z[decode + "Sync"](compressed)), + written: engine.bytesWritten, + }); + } + for (const options of [ + { chunkSize: 63 }, + { chunkSize: NaN }, + { windowBits: 8 }, + { windowBits: 0 }, + { level: 10 }, + { level: null }, + { flush: 6 }, + { maxOutputLength: 0 }, + { maxOutputLength: 3 }, + ]) { + for (const kind of ["fast", "info", "stream"]) { + try { + const value = + kind === "stream" + ? z.createGzip(options) + : z.gzipSync("hello", { ...options, info: kind === "info" }); + value.destroy?.(); + result.validation.push(null); + } catch (error) { + result.validation.push(failure(error)); + } + } + } + const compressed = z.deflateSync("dictionary words", { + dictionary: new Uint8Array([1, 2, 3]), + }); + result.dictionary = Array.from( + z.inflateSync(compressed, { + dictionary: new DataView(new Uint8Array([1, 2, 3]).buffer), + }), + ); + result.unzip = Array.from(z.unzipSync(z.gzipSync(source))); + return result; + }, + + fsAccess: async function ({ fs }) { + fs.writeFileSync("/tmp/access-contract", "value"); + const results = []; + for (const path of ["/tmp/access-contract", "/tmp/access-missing"]) { + for (let mode = 0; mode < 8; mode++) { + let sync = null; + try { + fs.accessSync(path, mode); + } catch (error) { + sync = error.code; + } + let promise = null; + try { + await fs.promises.access(path, mode); + } catch (error) { + promise = error.code; + } + let returned = false; + const pending = new Promise((resolve) => + fs.access(path, mode, (error) => + resolve([error?.code ?? null, returned]), + ), + ); + returned = true; + results.push({ path, mode, sync, promise, callback: await pending }); + } + } + const invalid = []; + for (const mode of [-1, 8, 1.5, "4"]) { + let callbackCalled = false; + try { + fs.access("/tmp/access-contract", mode, () => { + callbackCalled = true; + }); + } catch (error) { + invalid.push([error.name, error.code, callbackCalled]); + } + } + const beforeDeletion = new Promise((resolve) => + fs.access("/tmp/access-contract", (error) => + resolve(error?.code ?? null), + ), + ); + fs.unlinkSync("/tmp/access-contract"); + const beforeCreation = new Promise((resolve) => + fs.access("/tmp/access-contract", (error) => + resolve(error?.code ?? null), + ), + ); + fs.writeFileSync("/tmp/access-contract", "created"); + return { + results, + invalid, + beforeDeletion: await beforeDeletion, + beforeCreation: await beforeCreation, + }; + }, x509: function ({ crypto: c }) { const pem = "-----BEGIN CERTIFICATE-----\nMIIDhDCCAmygAwIBAgIUdN1my4bhw3mAm+rrvYEZBzi4nuAwDQYJKoZIhvcNAQEL\nBQAwJjESMBAGA1UEAwwJbG9jYWxob3N0MRAwDgYDVQQKDAdXb3JrZXJzMB4XDTI2\nMTAyMjIzMTEyNVoXDTI2MTAyNDIzMTEyNVowJjESMBAGA1UEAwwJbG9jYWxob3N0\nMRAwDgYDVQQKDAdXb3JrZXJzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAmdA4QcozdOsYQJXG5oDzOJEKHIoyM1HDVXgH2ZWTL0Qm9yJJ02uV55IhW2AY\nshAs1ZROFCae5891g4ohl1H4sFDE5HYuD4BLkh9RY8QcZ7170nA1fnxNfO0K0GZs\nJyCOerlt5fLJjcEQiiyjbs8/2yukxHJraHQIZ1cO/PbWJx9O7iZ5Ru7u2ompDrsz\nzTbVgjI/u4g+sHEf+11me+7WW13Lj4FvpDHZp1K9zoMtOj/ruURVs/5wO3gygNnO\nVVrtNc/0nOU0Qqlqyr0QC9ZkHpn374hL2m0KC9BNixbLdQ+Oy4O406UhQXUGOSPg\ndfN3MBgPJrs1yZ1Io6pL41LWXwIDAQABo4GpMIGmMB0GA1UdDgQWBBSlprvE05R3\nZRC3gulykkeMzbOIZzAPBgNVHRMBAf8EBTADAQH/MFUGA1UdEQROMEyCCWxvY2Fs\naG9zdIIOKi5leGFtcGxlLnRlc3SHBH8AAAGBEXRlc3RAZXhhbXBsZS50ZXN0hhZo\ndHRwczovL2V4YW1wbGUudGVzdC9hMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\nBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAWO4DydLOPOBNyKYw50dBT/UgF9l7zvk+\nl2C7v1dMVCtpKW4egHUjINf+tOHC7PLjMMSKDvM/NGszAePPRSAJzfvoIEF5nahz\nLAzcM3zIdp7vviP1yeSSgJH5QRvo4pMV13K4FHARR5L/ShZqEKvKbVwDCrzymkND\nV5SGa11p5xf0Fm6061vfgBQ85vNFANXByqznGhn9tBRf5pJGBl4cjTJ9WCl8zVCs\n++24wsetgXwxL9O1p+NvfM8SdTVGBSevAA/OAc2Fjoye4oJHH4n3sCsIxoEeRnb4\nZYhC9Q4UwNucHDFJASAMWC5AfX7P+HnaguZXGHsfBrw/39sZFdncwg==\n-----END CERTIFICATE-----\n"; diff --git a/src/compat/fs/embedded.ts b/src/compat/fs/embedded.ts index d8136190..d6f08002 100644 --- a/src/compat/fs/embedded.ts +++ b/src/compat/fs/embedded.ts @@ -4,6 +4,7 @@ // surface. Mutating calls and real device filesystem access are not // provided. +import { Buffer } from "node:buffer"; const encoder = new TextEncoder(); const files = new Map(); @@ -136,7 +137,7 @@ export class Stats { return this.#mtime.getTime(); } get mode() { - return this.#directory ? 0o40755 : 0o100644; + return this.#directory ? 0o40444 : 0o100444; } get dev() { return 0; @@ -174,6 +175,7 @@ function statFor(path: string, syscall: string): Stats { function toPath(input: unknown): string { if (input instanceof URL) return normalize(input.href); + if (Buffer.isBuffer(input)) return normalize(input.toString()); if (typeof input !== "string" && !(input instanceof String)) throw new TypeError("path must be a string or URL"); return normalize(String(input)); @@ -193,7 +195,9 @@ export function readFileSync(path: unknown, options?: unknown): unknown { typeof options === "string" ? options : (options as { encoding?: string } | undefined)?.encoding; - return encoding ? decode(data, encoding) : new Uint8Array(data); + return encoding + ? Buffer.from(data).toString(encoding as BufferEncoding) + : Buffer.from(data); } export function readFile( diff --git a/src/compat/fs/index.ts b/src/compat/fs/index.ts index 0b02148a..73d36704 100644 --- a/src/compat/fs/index.ts +++ b/src/compat/fs/index.ts @@ -7,13 +7,17 @@ import { clearTemporaryFiles, writablePath, pathOf, + readonlyFile, + embeddedStat, } from "./writable.js"; import { Buffer } from "node:buffer"; import { validateFunction, validateObject, validateBoolean, + validateUint32, } from "../net/validators.js"; +import { ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_TYPE } from "../net/errors.js"; export const registerVirtualFiles = embedded.registerVirtualFiles; export function clearVirtualFiles() { embedded.clearVirtualFiles(); @@ -42,8 +46,10 @@ for (const name of [ ]) { api[name] = new Proxy(api[name], { construct(_target, args) { + if (name === "ReadStream" || name === "FileReadStream") + return temporary.createReadStream(...args); args[0] = writablePath(args[0], name); - return Reflect.construct(temporary[name], args); + return temporary.createWriteStream(...args); }, }); } @@ -99,11 +105,8 @@ for (const name of [ "chown", "lchmod", "lchown", - "open", "rename", "copyFile", - "cp", - "link", "symlink", ]) { promises[name] = async (...args: any[]) => { @@ -124,8 +127,8 @@ for (const name of [ Buffer.isBuffer(args[i]) ) { if (!temporaryPath(args[i])) - throw Object.assign(new Error("EROFS: read-only file system"), { - code: "EROFS", + throw Object.assign(new Error("operation not permitted"), { + code: "EPERM", }); } return temporary.promises[name](...args); @@ -154,23 +157,90 @@ function installOperation( }; if (promise) promises[name] = async (...args: any[]) => handler(...args); } -function temporaryStats(stat: any) { +function temporaryStats(stat: any, readonly = false) { if (stat == null) return stat; const bigint = typeof stat.mode === "bigint"; - const mode = (Number(stat.mode) & ~0o777) | 0o666; + const mode = (Number(stat.mode) & ~0o777) | (readonly ? 0o444 : 0o666); stat.mode = bigint ? BigInt(mode) : mode; - stat.uid = stat.gid = bigint ? 0n : 0; + for (const name of [ + "dev", + "ino", + "uid", + "gid", + "rdev", + "blksize", + "blocks", + "atimeMs", + ]) + stat[name] = bigint ? 0n : 0; + stat.nlink = bigint ? 1n : 1; + stat.atime = new Date(0); + stat.ctime = stat.mtime; + stat.ctimeMs = stat.mtimeMs; + if (bigint) { + stat.atimeNs = 0n; + stat.ctimeNs = stat.mtimeNs; + } + if (readonly) + for (const name of ["mtime", "ctime", "birthtime"]) { + stat[name] = new Date(0); + stat[name + "Ms"] = bigint ? 0n : 0; + if (bigint) stat[name + "Ns"] = 0n; + } return stat; } +// Workers has no executable files. Read-only bundle files also report ENOENT +// for W_OK rather than exposing host-style permission bits. +function accessArgs(path: unknown, mode: unknown = 0) { + const normalized = pathOf(path); + mode ??= 0; + validateInteger(mode, "mode", 0, 7); + return { path: normalized, mode: mode as number }; +} +function checkAccess({ path, mode }: ReturnType) { + if (mode & 1 || (mode & 2 && readonlyFile(path)) || !api.existsSync(path)) + throw Object.assign( + new Error(`ENOENT: no such file or directory, access '${path}'`), + { + code: "ENOENT", + errno: -2, + syscall: "access", + path, + }, + ); +} +api.accessSync = (path: unknown, mode?: unknown) => + checkAccess(accessArgs(path, mode)); +api.access = (path: unknown, mode: unknown, callback?: any) => { + if (typeof mode === "function") { + callback = mode; + mode = undefined; + } + const args = accessArgs(path, mode); + validateFunction(callback, "callback"); + let failure: unknown = null; + try { + checkAccess(args); + } catch (error) { + failure = error; + } + queueMicrotask(() => callback(failure)); +}; +promises.access = async (path: unknown, mode?: unknown) => + checkAccess(accessArgs(path, mode)); for (const name of ["stat", "lstat"]) installOperation(name, (path: any, options?: any) => temporaryPath(path) - ? temporaryStats(temporary[name + "Sync"](path, options)) - : embedded.statSync(path), + ? temporaryStats( + temporary[name + "Sync"](path, options), + readonlyFile(path, name !== "lstat"), + ) + : temporaryStats(embeddedStat(path, options), true), ); installOperation( "fstat", - (...args: any[]) => temporaryStats(temporary.fstatSync(...args)), + (...args: any[]) => + temporaryStats(temporary.fstatSync(...args), readonlyFile(args[0])), false, ); for (const name of ["chmod", "lchmod", "fchmod", "chown", "lchown", "fchown"]) { @@ -208,14 +278,51 @@ installOperation("statfs", (path: any, options: any = {}) => { ffree: value, }; }); +// Workers interprets numeric timestamps as milliseconds and does not change +// metadata belonging to embedded files. Convert before the memfs boundary. +function fileTime(value: any): Date { + if (value instanceof Date) return value; + if (typeof value === "number" || typeof value === "string") + return new Date(value); + if (typeof value === "bigint") return new Date(Number(value)); + throw new ERR_INVALID_ARG_TYPE( + "time", + ["string", "number", "bigint", "Date"], + value, + ); +} +for (const name of ["utimes", "lutimes", "futimes"]) { + installOperation( + name, + (path: any, atime: any, mtime: any) => { + const access = fileTime(atime), + modified = fileTime(mtime); + if (name === "futimes") { + validateInteger(path, "fd", 0, 2147483647); + api.fstatSync(path); + } else api[name === "lutimes" ? "lstatSync" : "statSync"](path); + if (!readonlyFile(path, name !== "lutimes")) + temporary[name + "Sync"](path, access, modified); + }, + name !== "futimes", + ); +} const openPromise = promises.open; promises.open = async (...args: any[]) => { fileMode(args[2], 0o666); args[2] = 0o666; const handle = await openPromise(...args); const stat = handle.stat.bind(handle); + const isReadonly = readonlyFile(handle.fd); + const utimes = handle.utimes.bind(handle); + handle.utimes = async (atime: any, mtime: any) => { + const access = fileTime(atime), + modified = fileTime(mtime); + await stat(); + if (!isReadonly) await utimes(access, modified); + }; handle.stat = async (...options: any[]) => - temporaryStats(await stat(...options)); + temporaryStats(await stat(...options), isReadonly); handle.chmod = async (mode: unknown) => { fileMode(mode); await stat(); @@ -246,6 +353,54 @@ installOperation("copyFile", (source: any, destination: any, mode = 0) => { throw Object.assign(new Error("File already exists"), { code: "EEXIST" }); temporary.writeFileSync(destination, embedded.readFileSync(pathOf(source))); }); +const copyTree = api.cpSync; +installOperation("cp", (source: any, destination: any, options: any = {}) => { + validateObject(options, "options"); + const { + dereference = false, + errorOnExist = false, + force = true, + preserveTimestamps = false, + recursive = false, + verbatimSymlinks = false, + mode = 0, + } = options; + for (const [key, value] of Object.entries({ + dereference, + errorOnExist, + force, + preserveTimestamps, + recursive, + verbatimSymlinks, + })) + validateBoolean(value, "options." + key); + validateUint32(mode, "options.mode"); + if (mode & 4) + throw new ERR_INVALID_ARG_VALUE( + "options.mode", + "COPYFILE_FICLONE_FORCE is not supported", + ); + if (options.filter !== undefined) { + if (typeof options.filter !== "function") + throw new ERR_INVALID_ARG_TYPE( + "options.filter", + "function", + options.filter, + ); + throw Object.assign(new Error("The requested operation is unsupported"), { + code: "ERR_UNSUPPORTED_OPERATION", + }); + } + return copyTree(source, destination, { + dereference, + errorOnExist, + force, + recursive, + verbatimSymlinks: false, + preserveTimestamps: false, + mode: 0, + }); +}); api.promises = promises; export { promises }; export const { diff --git a/src/compat/fs/writable.spec.ts b/src/compat/fs/writable.spec.ts index a582c4dd..11cab151 100644 --- a/src/compat/fs/writable.spec.ts +++ b/src/compat/fs/writable.spec.ts @@ -2,6 +2,56 @@ import { expect, test } from "vitest"; import * as fs from "./index.js"; import { runWithTemporaryFiles } from "./writable.js"; +test("access uses Workers permissions for temporary and embedded files", async () => { + fs.registerVirtualFiles({ "/bundle/access-data": "read only" }); + await runWithTemporaryFiles(async () => { + fs.writeFileSync("/tmp/access-data", "writable"); + for (let mode = 0; mode < 8; mode++) { + for (const path of [ + "/tmp/access-data", + "/bundle/access-data", + "/tmp/missing", + ]) { + const fails = + !!(mode & fs.X_OK) || + (path.startsWith("/bundle") && !!(mode & fs.W_OK)) || + path.endsWith("missing"); + if (fails) { + expect(() => fs.accessSync(path, mode)).toThrow( + expect.objectContaining({ code: "ENOENT" }), + ); + await expect(fs.promises.access(path, mode)).rejects.toMatchObject({ + code: "ENOENT", + }); + } else { + expect(fs.accessSync(path, mode)).toBeUndefined(); + await expect(fs.promises.access(path, mode)).resolves.toBeUndefined(); + } + let returned = false; + const done = new Promise((resolve, reject) => { + fs.access(path, mode, (error: any) => { + try { + expect(returned).toBe(true); + expect(error?.code ?? null).toBe(fails ? "ENOENT" : null); + resolve(); + } catch (failure) { + reject(failure); + } + }); + }); + returned = true; + await done; + } + } + expect(() => fs.access("/tmp/access-data", 8, () => {})).toThrow( + expect.objectContaining({ code: "ERR_OUT_OF_RANGE" }), + ); + await expect( + fs.promises.access("/tmp/access-data", "4"), + ).rejects.toMatchObject({ code: "ERR_INVALID_ARG_TYPE" }); + }); +}); + test("temporary files are isolated between requests and from the fallback volume", () => { fs.clearVirtualFiles(); runWithTemporaryFiles(() => { @@ -30,9 +80,20 @@ test("concurrent asynchronous requests retain separate temporary files", () => { return Promise.all([operation("first"), operation("second")]); }); -test("a temporary symlink cannot mutate embedded assets", () => { +test("a temporary symlink can read but cannot mutate embedded assets", async () => { fs.registerVirtualFiles({ "/bundle/secret": "original" }); - expect(() => fs.symlinkSync("/bundle/secret", "/tmp/link")).toThrow(); + fs.symlinkSync("/bundle/secret", "/tmp/link"); + expect(fs.readFileSync("/tmp/link", "utf8")).toBe("original"); + expect(() => fs.writeFileSync("/tmp/link", "changed")).toThrow( + expect.objectContaining({ code: "EPERM" }), + ); + await expect( + fs.promises.writeFile("/tmp/link", "changed"), + ).rejects.toMatchObject({ code: "EPERM" }); + expect(() => fs.accessSync("/tmp/link", fs.W_OK)).toThrow( + expect.objectContaining({ code: "ENOENT" }), + ); + expect(fs.statSync("/tmp/link").mode & 0o777).toBe(0o444); expect(() => fs.writeFileSync("/bundle/secret", "changed")).toThrow(); expect(fs.readFileSync("/bundle/secret", "utf8")).toBe("original"); }); @@ -47,9 +108,11 @@ test("relative temporary symlinks resolve against their parent directory", async expect(await fs.promises.readFile("/tmp/nested/async-link", "utf8")).toBe( "local", ); - expect(() => - fs.symlinkSync("../../bundle/secret", "/tmp/nested/escape"), - ).toThrow(); + fs.symlinkSync("../../bundle/secret", "/tmp/nested/escape"); + expect(fs.readlinkSync("/tmp/nested/escape")).toBe("/bundle/secret"); + expect(() => fs.writeFileSync("/tmp/nested/escape", "changed")).toThrow( + expect.objectContaining({ code: "EPERM" }), + ); }); }); @@ -59,7 +122,7 @@ test("callback writes report read-only paths asynchronously", async () => { fs.writeFile("/bundle/secret", "changed", (error: any) => { try { expect(returned).toBe(true); - expect(error.code).toBe("EROFS"); + expect(error.code).toBe("EPERM"); resolve(); } catch (failure) { reject(failure); @@ -127,3 +190,87 @@ test("permission operations preserve file metadata and statfs is a Workers stub" ).toEqual(Array(7).fill(0n)); }); }); + +test("embedded descriptors, FileHandles and streams preserve read-only data", async () => { + fs.registerVirtualFiles({ "/bundle/readonly": "abcdef" }); + await runWithTemporaryFiles(async () => { + const fd = fs.openSync("/bundle/readonly", "r"); + const buffer = Buffer.alloc(3); + expect(fs.readSync(fd, buffer, 0, 3, 1)).toBe(3); + expect(buffer.toString()).toBe("bcd"); + expect(fs.fstatSync(fd).mode & 0o777).toBe(0o444); + expect(() => fs.writeSync(fd, "x")).toThrow( + expect.objectContaining({ code: "EPERM" }), + ); + expect(() => fs.ftruncateSync(fd, 0)).toThrow( + expect.objectContaining({ code: "EPERM" }), + ); + fs.closeSync(fd); + expect(() => fs.openSync("/bundle/readonly", "r+")).toThrow( + expect.objectContaining({ code: "EPERM" }), + ); + const handle = await fs.promises.open("/bundle/readonly", "r"); + expect(await handle.readFile("utf8")).toBe("abcdef"); + await expect(handle.writeFile("changed")).rejects.toMatchObject({ + code: "EPERM", + }); + await expect(handle.truncate(0)).rejects.toMatchObject({ code: "EPERM" }); + await handle.close(); + for (const stream of [ + fs.createReadStream("/bundle/readonly"), + new fs.ReadStream("/bundle/readonly"), + ]) { + let text = ""; + for await (const chunk of stream) text += chunk.toString(); + expect(text).toBe("abcdef"); + } + expect(fs.readFileSync("/bundle/readonly", "hex")).toBe("616263646566"); + expect(Buffer.isBuffer(fs.readFileSync("/bundle/readonly"))).toBe(true); + }); +}); + +test("copy and hard links preserve embedded inode write restrictions", async () => { + fs.registerVirtualFiles({ + "/bundle/tree/a": "alpha", + "/bundle/tree/sub/b": "beta", + }); + await runWithTemporaryFiles(async () => { + await fs.promises.cp("/bundle/tree", "/tmp/tree", { recursive: true }); + expect(fs.readFileSync("/tmp/tree/sub/b", "utf8")).toBe("beta"); + fs.writeFileSync("/tmp/tree/a", "copy changed"); + await fs.promises.link("/bundle/tree/a", "/tmp/hard"); + expect(fs.readFileSync("/tmp/hard", "utf8")).toBe("alpha"); + expect(() => fs.writeFileSync("/tmp/hard", "change")).toThrow( + expect.objectContaining({ code: "EPERM" }), + ); + await expect(fs.promises.open("/tmp/hard", "r+")).rejects.toMatchObject({ + code: "EPERM", + }); + expect(fs.statSync("/tmp/hard").mode & 0o777).toBe(0o444); + fs.unlinkSync("/tmp/hard"); + expect(fs.readFileSync("/bundle/tree/a", "utf8")).toBe("alpha"); + expect(() => + fs.cpSync("/bundle/tree/a", "/tmp/filter", { filter: () => true }), + ).toThrow(expect.objectContaining({ code: "ERR_UNSUPPORTED_OPERATION" })); + }); +}); + +test("FileHandle metadata retains its originating volume after request scope exits", async () => { + fs.registerVirtualFiles({ "/bundle/handle-origin": "original" }); + const handles = await runWithTemporaryFiles(async () => { + fs.writeFileSync("/tmp/handle-origin", "temporary"); + return [ + await fs.promises.open("/bundle/handle-origin", "r"), + await fs.promises.open("/tmp/handle-origin", "r"), + ]; + }); + try { + for (const handle of handles) await handle.utimes(1000, 2000); + expect((await handles[0].stat()).mtimeMs).toBe(0); + expect((await handles[0].stat()).mode & 0o777).toBe(0o444); + expect((await handles[1].stat()).mtimeMs).toBe(2000); + expect((await handles[1].stat()).mode & 0o777).toBe(0o666); + } finally { + await Promise.all(handles.map((handle) => handle.close())); + } +}); diff --git a/src/compat/fs/writable.ts b/src/compat/fs/writable.ts index a0db6b0f..02970396 100644 --- a/src/compat/fs/writable.ts +++ b/src/compat/fs/writable.ts @@ -1,3 +1,4 @@ +import * as embedded from "./embedded.js"; import { fileMode } from "./mode.js"; import { registerRequestScope } from "../../shared/request-scope.js"; // Writable, memory-only /tmp. Embedded bundle files remain read-only. @@ -24,8 +25,8 @@ export function writablePath(value: any, syscall: string) { const path = pathOf(value); if (!(path === "/tmp" || path.startsWith("/tmp/"))) throw Object.assign( - new Error(`EROFS: read-only file system, ${syscall} '${path}'`), - { code: "EROFS", errno: -30, syscall, path }, + new Error(`operation not permitted, ${syscall} '${path}'`), + { code: "EPERM", errno: -1, syscall, path }, ); return path; } @@ -34,6 +35,86 @@ function createTemporary() { const volume = new Volume(); volume.mkdirSync("/tmp"); const fs = createFsFromVolume(volume) as any; + const readonlyDescriptors = new Set(); + const readonlyInodes = new Set(); + const denied = (syscall: string, path?: string) => + Object.assign(new Error("operation not permitted"), { + code: "EPERM", + errno: -1, + syscall, + ...(path ? { path } : {}), + }); + function mount(path: string) { + if ( + temporaryPath(path) || + (fs.existsSync(path) && fs.statSync(path).isFile()) + ) + return; + let stat; + try { + stat = embedded.statSync(path); + } catch (error) { + if ((error as any).code === "ENOENT") return; + throw error; + } + if (stat.isDirectory()) { + fs.mkdirSync(path, { recursive: true }); + for (const name of embedded.readdirSync(path) as string[]) + mount(posix.join(path, name)); + } else { + fs.mkdirSync(posix.dirname(path), { recursive: true }); + fs.writeFileSync(path, embedded.readFileSync(path)); + readonlyInodes.add(fs.statSync(path).ino); + } + } + function resolved(path: string): string { + let parent = path; + const missing = []; + while (!fs.existsSync(parent) && parent !== "/") { + missing.unshift(posix.basename(parent)); + parent = posix.dirname(parent); + } + return posix.join(fs.realpathSync(parent), ...missing); + } + function mutable(value: any, syscall: string, follow = true) { + if (typeof value === "number") { + if (readonlyDescriptors.has(value)) throw denied(syscall); + return value; + } + const path = writablePath(value, syscall) as string; + const target = follow + ? resolved(path) + : posix.join(resolved(posix.dirname(path)), posix.basename(path)); + if ( + !temporaryPath(target) || + (follow && + fs.existsSync(target) && + readonlyInodes.has(fs.statSync(target).ino)) + ) + throw denied(syscall, path); + return path; + } + function openPath(value: any, flags: any = "r") { + const path = pathOf(value); + mount(path); + const target = resolved(path); + const writes = + typeof flags === "number" + ? !!( + flags & + (fs.constants.O_WRONLY | + fs.constants.O_RDWR | + fs.constants.O_CREAT | + fs.constants.O_TRUNC | + fs.constants.O_APPEND) + ) + : typeof flags === "string" && !["r", "rs", "sr"].includes(flags); + const readonly = + !temporaryPath(target) || + (fs.existsSync(target) && readonlyInodes.has(fs.statSync(target).ino)); + if (readonly && writes) throw denied("open", path); + return { path, readonly }; + } const mutating = new Set([ "appendFile", "writeFile", @@ -80,7 +161,7 @@ function createTemporary() { }; } if (base === "symlink") { - args[1] = writablePath(args[1], base); + args[1] = mutable(args[1], base, false); const target = Buffer.isBuffer(args[0]) ? args[0].toString() : args[0]; @@ -90,16 +171,22 @@ function createTemporary() { ["string", "Buffer"], target, ); - writablePath(posix.resolve(posix.dirname(args[1]), target), base); - } else if (["rename", "copyFile", "cp", "link"].includes(base)) { - args[0] = writablePath(args[0], base); - args[1] = writablePath(args[1], base); - } else if ( - mutating.has(base) || - base === "open" || - base === "createWriteStream" - ) - args[0] = writablePath(args[0], base); + const absolute = posix.resolve(posix.dirname(args[1]), target); + mount(absolute); + args[0] = absolute; + } else if (["copyFile", "cp", "link"].includes(base)) { + args[0] = pathOf(args[0]); + mount(args[0]); + args[1] = mutable(args[1], base, base !== "link"); + } else if (base === "rename") { + args[0] = mutable(args[0], base, false); + args[1] = mutable(args[1], base, false); + } else if (mutating.has(base) || base === "createWriteStream") + args[0] = mutable( + args[0], + base, + !["unlink", "rm", "rmdir", "mkdir"].includes(base), + ); else if ( typeof args[0] === "string" || args[0] instanceof URL || @@ -114,14 +201,129 @@ function createTemporary() { } throw error; } + if ( + ["write", "writev", "ftruncate"].includes(base) && + readonlyDescriptors.has(args[0]) + ) { + const error = denied(base); + const callback = args[args.length - 1]; + if (!name.endsWith("Sync") && typeof callback === "function") { + queueMicrotask(() => callback(error)); + return; + } + throw error; + } + if (base === "close") readonlyDescriptors.delete(args[0]); return fs[name](...args); }; } + api.openSync = (path: any, flags?: any, mode?: any) => { + const entry = openPath(path, flags); + const fd = fs.openSync(entry.path, flags, mode); + if (entry.readonly) readonlyDescriptors.add(fd); + return fd; + }; + api.open = (path: any, flags: any, mode: any, callback?: any) => { + if (typeof mode === "function") { + callback = mode; + mode = undefined; + } + let fd: number | undefined, + error: unknown = null; + try { + fd = api.openSync(path, flags, mode); + } catch (failure) { + error = failure; + } + queueMicrotask(() => callback(error, fd)); + }; + api.createReadStream = (path: any, options?: any) => { + if (typeof options?.fd === "number") + return fs.createReadStream(path, options); + const entry = openPath(path, options?.flags); + const stream = fs.createReadStream(entry.path, options); + if (entry.readonly) { + stream.prependListener("open", (fd: number) => + readonlyDescriptors.add(fd), + ); + let fd: number | undefined; + stream.on("open", (value: number) => { + fd = value; + }); + stream.on("close", () => { + if (fd !== undefined) readonlyDescriptors.delete(fd); + }); + } + return stream; + }; + api.promises = { + ...fs.promises, + open: async (path: any, flags?: any, mode?: any) => { + const entry = openPath(path, flags); + const handle = await fs.promises.open(entry.path, flags, mode); + for (const name of [ + "write", + "writev", + "writeFile", + "appendFile", + "truncate", + ]) { + const operation = handle[name].bind(handle); + handle[name] = async (...args: any[]) => { + await handle.stat(); + if (entry.readonly) throw denied(name); + return operation(...args); + }; + } + if (entry.readonly) { + readonlyDescriptors.add(handle.fd); + const close = handle.close.bind(handle); + handle.close = async () => { + readonlyDescriptors.delete(handle.fd); + return close(); + }; + } + return handle; + }, + }; + for (const name of [ + ...mutating, + "rename", + "copyFile", + "cp", + "link", + "symlink", + ]) + api.promises[name] = (...args: any[]) => + new Promise((resolve, reject) => + api[name](...args, (error: unknown, value: unknown) => + error ? reject(error) : resolve(value), + ), + ); return { api, + statEmbedded(path: any, options?: any) { + const normalized = pathOf(path); + mount(normalized); + return fs.statSync(normalized, options); + }, + readonly(value: any, follow = true) { + if (typeof value === "number") return readonlyDescriptors.has(value); + const path = pathOf(value); + const target = follow ? resolved(path) : path; + return ( + !temporaryPath(target) || + (follow && + fs.existsSync(target) && + readonlyInodes.has(fs.statSync(target).ino)) + ); + }, clear() { - for (const name of fs.readdirSync("/tmp")) - fs.rmSync(`/tmp/${name}`, { recursive: true, force: true }); + for (const name of fs.readdirSync("/")) + fs.rmSync(`/${name}`, { recursive: true, force: true }); + fs.mkdirSync("/tmp"); + readonlyDescriptors.clear(); + readonlyInodes.clear(); }, }; } @@ -151,3 +353,11 @@ export function runWithTemporaryFiles(callback: () => T): T { } registerRequestScope(runWithTemporaryFiles); + +export function readonlyFile(value: any, follow = true): boolean { + return active().readonly(value, follow); +} + +export function embeddedStat(path: any, options?: any): any { + return active().statEmbedded(path, options); +} diff --git a/src/compat/zlib/LICENSE-3RD-PARTY.md b/src/compat/zlib/LICENSE-3RD-PARTY.md new file mode 100644 index 00000000..5af46473 --- /dev/null +++ b/src/compat/zlib/LICENSE-3RD-PARTY.md @@ -0,0 +1,11 @@ +# Upstream notices + +This module ports Cloudflare workerd tag `v1.20261012.1`, revision +`5358e3d337336eaf696a48bc1563d808b10feba5`: + +- `src/node/_tls_common.ts` +- `src/node/internal/internal_tls_common.ts` + +Copyright Cloudflare, Inc.; Apache License 2.0. Node.js/Joyent MIT notices +are preserved in the source files. Imports, resource-management symbols and +host constructor references are adapted for the Capacitor runtime. diff --git a/src/compat/zlib/LICENSE-APACHE b/src/compat/zlib/LICENSE-APACHE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/src/compat/zlib/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/src/compat/zlib/LICENSE.zlib b/src/compat/zlib/LICENSE.zlib new file mode 100644 index 00000000..ab8ee6f7 --- /dev/null +++ b/src/compat/zlib/LICENSE.zlib @@ -0,0 +1,22 @@ +Copyright notice: + + (C) 1995-2022 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/src/compat/zlib/classic-base.cjs b/src/compat/zlib/classic-base.cjs new file mode 100644 index 00000000..6eafbbc0 --- /dev/null +++ b/src/compat/zlib/classic-base.cjs @@ -0,0 +1,778 @@ +// Derived from Cloudflare workerd 5358e3d337336eaf696a48bc1563d808b10feba5 +// Copyright (c) 2017-2022 Cloudflare, Inc. Apache-2.0. +// Copyright Joyent and Node contributors. MIT license. +// Native binding imports adapted for the WebView implementation. +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if ((from && typeof from === "object") || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { + get: () => from[key], + enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, + }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => ( + (target = mod != null ? __create(__getProtoOf(mod)) : {}), + __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule + ? __defProp(target, "default", { value: mod, enumerable: true }) + : target, + mod, + ) +); +var __toCommonJS = (mod) => + __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var stdin_exports = {}; +__export(stdin_exports, { + Brotli: () => Brotli, + Zlib: () => Zlib, + ZlibBase: () => ZlibBase, + Zstd: () => Zstd, + kMaxZstdCParam: () => kMaxZstdCParam, + kMaxZstdDParam: () => kMaxZstdDParam, + owner_symbol: () => owner_symbol, + zstdInitCParamsArray: () => zstdInitCParamsArray, + zstdInitDParamsArray: () => zstdInitDParamsArray, +}); + +var import_classic_binding = __toESM(require("./classic-binding.js")); +var import_node_buffer = require("node:buffer"); +var import_validators = require("./validators.js"); +var import_errors = require("./errors.js"); +var import_node_stream = require("node:stream"); +var import_node_stream2 = require("node:stream"); +var import_types = require("node:util/types"); +var constants = __toESM(require("./constants.js")); +var import_node_assert = __toESM(require("node:assert")); +const { + CONST_INFLATE, + CONST_GUNZIP, + CONST_GZIP, + CONST_UNZIP, + CONST_Z_DEFAULT_CHUNK, + CONST_Z_DEFAULT_STRATEGY, + CONST_Z_DEFAULT_MEMLEVEL, + CONST_Z_DEFAULT_WINDOWBITS, + CONST_Z_DEFAULT_COMPRESSION, + CONST_Z_FIXED, + CONST_Z_MAX_LEVEL, + CONST_Z_MAX_MEMLEVEL, + CONST_Z_MAX_WINDOWBITS, + CONST_Z_MIN_LEVEL, + CONST_Z_MIN_MEMLEVEL, + CONST_Z_SYNC_FLUSH, + CONST_Z_NO_FLUSH, + CONST_Z_BLOCK, + CONST_Z_MIN_CHUNK, + CONST_Z_PARTIAL_FLUSH, + CONST_Z_FULL_FLUSH, + CONST_Z_FINISH, + CONST_BROTLI_ENCODE, + CONST_BROTLI_DECODE, + CONST_BROTLI_OPERATION_PROCESS, + CONST_BROTLI_OPERATION_EMIT_METADATA, + CONST_BROTLI_OPERATION_FINISH, + CONST_BROTLI_OPERATION_FLUSH, + CONST_ZSTD_ENCODE, + CONST_ZSTD_DECODE, + CONST_ZSTD_e_continue, + CONST_ZSTD_e_end, + CONST_ZSTD_e_flush, +} = import_classic_binding.default; +const owner_symbol = /* @__PURE__ */ Symbol("owner"); +const FLUSH_BOUND_IDX_NORMAL = 0; +const FLUSH_BOUND_IDX_BROTLI = 1; +const FLUSH_BOUND_IDX_ZSTD = 2; +const FLUSH_BOUND = [ + [CONST_Z_NO_FLUSH, CONST_Z_BLOCK], + [CONST_BROTLI_OPERATION_PROCESS, CONST_BROTLI_OPERATION_EMIT_METADATA], + [CONST_ZSTD_e_continue, CONST_ZSTD_e_end], +]; +const kFlushFlag = /* @__PURE__ */ Symbol("kFlushFlag"); +const kError = /* @__PURE__ */ Symbol("kError"); +function processCallback() { + const handle = this; + const self = this[owner_symbol]; + (0, import_node_assert.ok)(self, "Owner symbol should exist"); + const state = self._writeState; + if (self.destroyed) { + this.buffer = null; + this.cb(); + return; + } + const availOutAfter = state[0]; + const availInAfter = state[1]; + const inDelta = handle.availInBefore - availInAfter; + self.bytesWritten += inDelta; + const have = handle.availOutBefore - availOutAfter; + let streamBufferIsFull = false; + if (have > 0) { + const out = self._outBuffer.slice(self._outOffset, self._outOffset + have); + self._outOffset += have; + streamBufferIsFull = !self.push(out); + } else { + import_node_assert.default.strictEqual(have, 0, "have should not go down"); + } + if (self.destroyed) { + this.cb(); + return; + } + if (availOutAfter === 0 || self._outOffset >= self._chunkSize) { + handle.availOutBefore = self._chunkSize; + self._outOffset = 0; + self._outBuffer = import_node_buffer.Buffer.allocUnsafe(self._chunkSize); + } + if (availOutAfter === 0) { + handle.inOff += inDelta; + handle.availInBefore = availInAfter; + if (!streamBufferIsFull) { + (0, import_node_assert.ok)( + this.buffer, + "Buffer should not have been null", + ); + this.write( + handle.flushFlag, + this.buffer, + // in + handle.inOff, + // in_off + handle.availInBefore, + // in_len + self._outBuffer, + // out + self._outOffset, + // out_off + self._chunkSize, + ); + } else { + const oldRead = self._read; + self._read = (n) => { + (0, import_node_assert.ok)( + this.buffer, + "Buffer should not have been null", + ); + self._read = oldRead; + this.write( + handle.flushFlag, + this.buffer, + // in + handle.inOff, + // in_off + handle.availInBefore, + // in_len + self._outBuffer, + // out + self._outOffset, + // out_off + self._chunkSize, + // out_len + ); + self._read(n); + }; + } + return; + } + if (availInAfter > 0) { + self.push(null); + } + this.buffer = null; + this.cb(); +} +const flushiness = []; +const kFlushFlagList = [ + CONST_Z_NO_FLUSH, + CONST_Z_BLOCK, + CONST_Z_PARTIAL_FLUSH, + CONST_Z_SYNC_FLUSH, + CONST_Z_FULL_FLUSH, + CONST_Z_FINISH, +]; +for (let i = 0; i < kFlushFlagList.length; i++) { + flushiness[kFlushFlagList[i]] = i; +} +function maxFlush(a, b) { + return flushiness[a] > flushiness[b] ? a : b; +} +const kFlushBuffers = []; +{ + const dummyArrayBuffer = new ArrayBuffer(0); + for (const flushFlag of kFlushFlagList) { + const buf = import_node_buffer.Buffer.from(dummyArrayBuffer); + buf[kFlushFlag] = flushFlag; + kFlushBuffers[flushFlag] = buf; + } +} +function zlibOnError(errno, code, message) { + const self = this[owner_symbol]; + (0, import_node_assert.ok)(self, "Owner symbol should exist"); + const error = new import_errors.NodeError(code, message); + error.errno = errno; + self.destroy(error); + self[kError] = error; +} +function processChunkSync(self, chunk, flushFlag) { + let availInBefore = chunk.byteLength; + let availOutBefore = self._chunkSize - self._outOffset; + let inOff = 0; + let availOutAfter; + let availInAfter; + const buffers = []; + let nread = 0; + let inputRead = 0; + const state = self._writeState; + const handle = self._handle; + let buffer = self._outBuffer; + let offset = self._outOffset; + const chunkSize = self._chunkSize; + let error; + self.on("error", function onError(er) { + error = er; + }); + while (true) { + (0, import_node_assert.ok)(handle, "Handle should have been defined"); + handle.writeSync( + flushFlag, + chunk, + // in + inOff, + // in_off + availInBefore, + // in_len + buffer, + // out + offset, + // out_off + availOutBefore, + // out_len + ); + if (error) throw error; + else if (self[kError]) throw self[kError]; + [availOutAfter, availInAfter] = state; + const inDelta = availInBefore - availInAfter; + inputRead += inDelta; + const have = availOutBefore - availOutAfter; + if (have > 0) { + const out = buffer.slice(offset, offset + have); + offset += have; + buffers.push(out); + nread += out.byteLength; + if (nread > self._maxOutputLength) { + _close(self); + throw new import_errors.ERR_BUFFER_TOO_LARGE(self._maxOutputLength); + } + } else { + import_node_assert.default.strictEqual( + have, + 0, + "have should not go down", + ); + } + if (availOutAfter === 0 || offset >= chunkSize) { + availOutBefore = chunkSize; + offset = 0; + buffer = import_node_buffer.Buffer.allocUnsafe(chunkSize); + } + if (availOutAfter === 0) { + inOff += inDelta; + availInBefore = availInAfter; + } else { + break; + } + } + self.bytesWritten = inputRead; + _close(self); + if (nread === 0) return import_node_buffer.Buffer.alloc(0); + return buffers.length === 1 + ? buffers[0] + : import_node_buffer.Buffer.concat(buffers, nread); +} +function _close(engine) { + engine._handle?.close(); + engine._handle = null; +} +const zlibDefaultOptions = { + flush: CONST_Z_NO_FLUSH, + finishFlush: CONST_Z_FINISH, + fullFlush: CONST_Z_FULL_FLUSH, +}; +class ZlibBase extends import_node_stream.Transform { + bytesWritten = 0; + _maxOutputLength; + _outBuffer; + _outOffset = 0; + _chunkSize; + _defaultFlushFlag; + _finishFlushFlag; + _defaultFullFlushFlag; + _info; + _handle = null; + _writeState = new Uint32Array(2); + _writesInProgress = 0; + _hadWrites = false; + [kError]; + constructor( + opts, + mode, + handle, + { flush, finishFlush, fullFlush } = zlibDefaultOptions, + ) { + let chunkSize = CONST_Z_DEFAULT_CHUNK; + let maxOutputLength = import_node_buffer.kMaxLength; + let flushBoundIdx; + if (mode === CONST_BROTLI_ENCODE || mode === CONST_BROTLI_DECODE) { + flushBoundIdx = FLUSH_BOUND_IDX_BROTLI; + } else if (mode === CONST_ZSTD_ENCODE || mode === CONST_ZSTD_DECODE) { + flushBoundIdx = FLUSH_BOUND_IDX_ZSTD; + } else { + flushBoundIdx = FLUSH_BOUND_IDX_NORMAL; + } + if (opts) { + if (opts.chunkSize != null) { + chunkSize = opts.chunkSize; + } + if ( + !(0, import_validators.checkFiniteNumber)( + chunkSize, + "options.chunkSize", + ) + ) { + chunkSize = CONST_Z_DEFAULT_CHUNK; + } else if (chunkSize < CONST_Z_MIN_CHUNK) { + throw new import_errors.ERR_OUT_OF_RANGE( + "options.chunkSize", + `>= ${CONST_Z_MIN_CHUNK}`, + chunkSize, + ); + } + flush = (0, import_validators.checkRangesOrGetDefault)( + opts.flush, + "options.flush", + FLUSH_BOUND[flushBoundIdx]?.[0], + FLUSH_BOUND[flushBoundIdx]?.[1], + flush, + ); + finishFlush = (0, import_validators.checkRangesOrGetDefault)( + opts.finishFlush, + "options.finishFlush", + FLUSH_BOUND[flushBoundIdx]?.[0], + FLUSH_BOUND[flushBoundIdx]?.[1], + finishFlush, + ); + maxOutputLength = (0, import_validators.checkRangesOrGetDefault)( + opts.maxOutputLength, + "options.maxOutputLength", + 1, + import_node_buffer.kMaxLength, + import_node_buffer.kMaxLength, + ); + if (opts.encoding || opts.objectMode || opts.writableObjectMode) { + opts = { ...opts }; + opts.encoding = void 0; + opts.objectMode = false; + opts.writableObjectMode = false; + } + } + super({ autoDestroy: true, ...opts }); + handle.setErrorHandler(zlibOnError.bind(handle)); + handle[owner_symbol] = this; + this._handle = handle; + this._outBuffer = import_node_buffer.Buffer.allocUnsafe(chunkSize); + this._outOffset = 0; + this._chunkSize = chunkSize; + this._defaultFlushFlag = flush; + this._finishFlushFlag = finishFlush; + this._defaultFullFlushFlag = fullFlush; + this._info = Boolean(opts.info); + this._maxOutputLength = maxOutputLength; + } + // Note: This is intentionally a getter that shadows the property from Transform + // @ts-expect-error TS2611 Property/accessor mismatch with Transform._closed + get _closed() { + return this._handle == null; + } + // @deprecated Use `bytesWritten` instead. + get bytesRead() { + return this.bytesWritten; + } + // @deprecated Use `bytesWritten` instead. + set bytesRead(value) { + this.bytesWritten = value; + } + reset() { + (0, import_node_assert.ok)(this._handle, "zlib binding closed"); + this._handle.reset(); + } + // This is the _flush function called by the transform class, + // internally, when the last chunk has been written. + _flush(callback) { + if (this._writesInProgress > 0) { + queueMicrotask(() => this._flush(callback)); + return; + } + if (this._hadWrites) { + queueMicrotask(() => { + const chunk = import_node_buffer.Buffer.alloc(0); + chunk[kFlushFlag] = this._finishFlushFlag; + this._transform(chunk, "utf8", callback); + }); + } else { + const chunk = import_node_buffer.Buffer.alloc(0); + chunk[kFlushFlag] = this._finishFlushFlag; + this._transform(chunk, "utf8", callback); + } + } + // Force Transform compat behavior. + _final(callback) { + callback(); + } + flush(kind, callback) { + if (typeof kind === "function" || (kind === void 0 && !callback)) { + callback = kind; + kind = this._defaultFullFlushFlag; + } + if (this.writableFinished) { + if (callback) { + queueMicrotask(callback); + } + } else if (this.writableEnded) { + if (callback) { + this.once("end", callback); + } + } else { + this.write(kFlushBuffers[kind], "utf8", callback); + } + } + close(callback) { + if (callback) { + (0, import_node_stream2.finished)(this, callback); + } + this.destroy(); + } + _destroy(err, callback) { + _close(this); + callback(err); + } + _transform(chunk, _encoding, cb) { + let flushFlag = this._defaultFlushFlag; + if (typeof chunk[kFlushFlag] === "number") { + flushFlag = chunk[kFlushFlag]; + } + if (this.writableEnded && this.writableLength === chunk.byteLength) { + flushFlag = maxFlush(flushFlag, this._finishFlushFlag); + } + this.#processChunk(chunk, flushFlag, cb); + } + _processChunk(chunk, flushFlag, cb) { + if (cb != null && typeof cb === "function") { + this.#processChunk(chunk, flushFlag, cb); + return; + } + return processChunkSync(this, chunk, flushFlag); + } + #processChunk(chunk, flushFlag, cb) { + if (!this._handle) { + queueMicrotask(cb); + return; + } + if (chunk.byteLength > 0) { + this._hadWrites = true; + } + this._writesInProgress++; + const originalCb = cb; + const wrappedCb = () => { + this._writesInProgress--; + originalCb(); + }; + this._handle.buffer = chunk; + this._handle.cb = wrappedCb; + this._handle.availOutBefore = this._chunkSize - this._outOffset; + this._handle.availInBefore = chunk.byteLength; + this._handle.inOff = 0; + this._handle.flushFlag = flushFlag; + this._handle.write( + flushFlag, + chunk, + // in + 0, + // in_off + this._handle.availInBefore, + // in_len + this._outBuffer, + // out + this._outOffset, + // out_off + this._handle.availOutBefore, + // out_len + ); + } +} +class Zlib extends ZlibBase { + _level = CONST_Z_DEFAULT_COMPRESSION; + _strategy = CONST_Z_DEFAULT_STRATEGY; + constructor(options, mode) { + let windowBits = CONST_Z_DEFAULT_WINDOWBITS; + let level = CONST_Z_DEFAULT_COMPRESSION; + let memLevel = CONST_Z_DEFAULT_MEMLEVEL; + let strategy = CONST_Z_DEFAULT_STRATEGY; + let dictionary; + if (options != null) { + if ( + (options.windowBits == null || options.windowBits === 0) && + (mode === CONST_INFLATE || + mode === CONST_GUNZIP || + mode === CONST_UNZIP) + ) { + windowBits = 0; + } else { + const min = + import_classic_binding.default.CONST_Z_MIN_WINDOWBITS + + (mode === CONST_GZIP ? 1 : 0); + windowBits = (0, import_validators.checkRangesOrGetDefault)( + options.windowBits, + "options.windowBits", + min, + CONST_Z_MAX_WINDOWBITS, + CONST_Z_DEFAULT_WINDOWBITS, + ); + } + level = (0, import_validators.checkRangesOrGetDefault)( + options.level, + "options.level", + CONST_Z_MIN_LEVEL, + CONST_Z_MAX_LEVEL, + CONST_Z_DEFAULT_COMPRESSION, + ); + memLevel = (0, import_validators.checkRangesOrGetDefault)( + options.memLevel, + "options.memLevel", + CONST_Z_MIN_MEMLEVEL, + CONST_Z_MAX_MEMLEVEL, + CONST_Z_DEFAULT_MEMLEVEL, + ); + strategy = (0, import_validators.checkRangesOrGetDefault)( + options.strategy, + "options.strategy", + CONST_Z_DEFAULT_STRATEGY, + CONST_Z_FIXED, + CONST_Z_DEFAULT_STRATEGY, + ); + dictionary = options.dictionary; + if ( + dictionary !== void 0 && + !(0, import_types.isArrayBufferView)(dictionary) + ) { + if ((0, import_types.isAnyArrayBuffer)(dictionary)) { + dictionary = import_node_buffer.Buffer.from(dictionary); + } else { + throw new import_errors.ERR_INVALID_ARG_TYPE( + "options.dictionary", + ["Buffer", "TypedArray", "DataView", "ArrayBuffer"], + dictionary, + ); + } + } + } + const writeState = new Uint32Array(2); + const handle = new import_classic_binding.default.ZlibStream(mode); + handle.initialize( + windowBits, + level, + memLevel, + strategy, + writeState, + () => { + queueMicrotask(processCallback.bind(handle)); + }, + dictionary, + ); + super(options ?? {}, mode, handle); + this._level = level; + this._strategy = strategy; + this._handle = handle; + this._writeState = writeState; + } + params(level, strategy, callback) { + (0, import_validators.checkRangesOrGetDefault)( + level, + "level", + CONST_Z_MIN_LEVEL, + CONST_Z_MAX_LEVEL, + ); + (0, import_validators.checkRangesOrGetDefault)( + strategy, + "strategy", + CONST_Z_DEFAULT_STRATEGY, + CONST_Z_FIXED, + ); + if (this._level !== level || this._strategy !== strategy) { + this.flush( + CONST_Z_SYNC_FLUSH, + this.#paramsAfterFlushCallback.bind(this, level, strategy, callback), + ); + } else { + queueMicrotask(callback); + } + } + // This callback is used by `.params()` to wait until a full flush happened + // before adjusting the parameters. In particular, the call to the native + // `params()` function should not happen while a write is currently in progress + // on the threadpool. + #paramsAfterFlushCallback(level, strategy, callback) { + (0, import_node_assert.ok)(this._handle, "zlib binding closed"); + this._handle.params(level, strategy); + if (!this.destroyed) { + this._level = level; + this._strategy = strategy; + callback?.(); + } + } +} +const kMaxBrotliParam = Math.max( + ...Object.entries(constants).map(([key, value]) => + key.startsWith("BROTLI_PARAM_") ? value : 0, + ), +); +const brotliInitParamsArray = new Uint32Array(kMaxBrotliParam + 1); +const brotliDefaultOptions = { + flush: CONST_BROTLI_OPERATION_PROCESS, + finishFlush: CONST_BROTLI_OPERATION_FINISH, + fullFlush: CONST_BROTLI_OPERATION_FLUSH, +}; +class Brotli extends ZlibBase { + constructor(options, mode) { + (0, import_node_assert.ok)( + mode === CONST_BROTLI_DECODE || mode === CONST_BROTLI_ENCODE, + ); + brotliInitParamsArray.fill(-1); + if (options?.params) { + for (const [origKey, value] of Object.entries(options.params)) { + const key = +origKey; + if ( + Number.isNaN(key) || + key < 0 || + key > kMaxBrotliParam || + (brotliInitParamsArray[key] | 0) !== -1 + ) { + throw new import_errors.ERR_BROTLI_INVALID_PARAM(origKey); + } + if (typeof value !== "number" && typeof value !== "boolean") { + throw new import_errors.ERR_INVALID_ARG_TYPE( + "options.params[key]", + "number", + value, + ); + } + brotliInitParamsArray[key] = value; + } + } + const handle = + mode === CONST_BROTLI_DECODE + ? new import_classic_binding.default.BrotliDecoder(mode) + : new import_classic_binding.default.BrotliEncoder(mode); + const _writeState = new Uint32Array(2); + if ( + !handle.initialize( + brotliInitParamsArray, + _writeState, + processCallback.bind(handle), + ) + ) { + throw new import_errors.ERR_ZLIB_INITIALIZATION_FAILED(); + } + super(options ?? {}, mode, handle, brotliDefaultOptions); + this._writeState = _writeState; + } +} +const kMaxZstdCParam = Math.max( + ...Object.entries(constants).map(([key, value]) => + key.startsWith("ZSTD_c_") ? value : 0, + ), +); +const zstdInitCParamsArray = new Int32Array(kMaxZstdCParam + 1); +const kMaxZstdDParam = Math.max( + ...Object.entries(constants).map(([key, value]) => + key.startsWith("ZSTD_d_") ? value : 0, + ), +); +const zstdInitDParamsArray = new Int32Array(kMaxZstdDParam + 1); +const zstdDefaultOptions = { + flush: CONST_ZSTD_e_continue, + finishFlush: CONST_ZSTD_e_end, + fullFlush: CONST_ZSTD_e_flush, +}; +class Zstd extends ZlibBase { + constructor(options, mode, initParamsArray, maxParam) { + (0, import_node_assert.ok)( + mode === CONST_ZSTD_DECODE || mode === CONST_ZSTD_ENCODE, + ); + initParamsArray.fill(-1); + if (options?.params) { + for (const [origKey, value] of Object.entries(options.params)) { + const key = +origKey; + if ( + Number.isNaN(key) || + key < 0 || + key > maxParam || + (initParamsArray[key] | 0) !== -1 + ) { + throw new import_errors.ERR_ZSTD_INVALID_PARAM(origKey); + } + if (typeof value !== "number" && typeof value !== "boolean") { + throw new import_errors.ERR_INVALID_ARG_TYPE( + "options.params[key]", + "number", + value, + ); + } + initParamsArray[key] = value; + } + } + const handle = + mode === CONST_ZSTD_DECODE + ? new import_classic_binding.default.ZstdDecoder(mode) + : new import_classic_binding.default.ZstdEncoder(mode); + const _writeState = new Uint32Array(2); + const pledgedSrcSize = options?.pledgedSrcSize; + if ( + !handle.initialize( + initParamsArray, + _writeState, + () => { + queueMicrotask(processCallback.bind(handle)); + }, + pledgedSrcSize, + ) + ) { + throw new import_errors.ERR_ZLIB_INITIALIZATION_FAILED(); + } + super(options ?? {}, mode, handle, zstdDefaultOptions); + this._writeState = _writeState; + } +} + +module.exports = { + Zlib, + ZlibBase, + Brotli, + Zstd, + kMaxZstdCParam, + kMaxZstdDParam, + zstdInitCParamsArray, + zstdInitDParamsArray, +}; diff --git a/src/compat/zlib/classic-base.d.cts b/src/compat/zlib/classic-base.d.cts new file mode 100644 index 00000000..f68bcdee --- /dev/null +++ b/src/compat/zlib/classic-base.d.cts @@ -0,0 +1,37 @@ +import { Transform } from "node:stream"; +import { Buffer } from "node:buffer"; +declare class Zlib extends Transform { + constructor(options: any, mode: number); + bytesWritten: number; + bytesRead: number; + readonly _closed: boolean; + _finishFlushFlag: number; + _processChunk(input: ArrayBufferView | ArrayBuffer, flush: number): Buffer; + _processChunk( + input: ArrayBufferView | ArrayBuffer, + flush: number, + callback: () => void, + ): void; + flush(callback?: () => void): void; + flush(kind: number, callback?: () => void): void; + params(level: number, strategy: number, callback: () => void): void; + reset(): void; + close(callback?: () => void): void; +} +declare class Brotli extends Zlib { + constructor(options: any, mode: number); +} +declare class Zstd extends Zlib { + constructor(options: any, mode: number, params: Int32Array, max: number); +} +declare const base: { + Zlib: typeof Zlib; + Brotli: typeof Brotli; + Zstd: typeof Zstd; + zstdInitCParamsArray: Int32Array; + zstdInitDParamsArray: Int32Array; + kMaxZstdCParam: number; + kMaxZstdDParam: number; +}; + +export = base; diff --git a/src/compat/zlib/classic-binding.ts b/src/compat/zlib/classic-binding.ts new file mode 100644 index 00000000..5ee93381 --- /dev/null +++ b/src/compat/zlib/classic-binding.ts @@ -0,0 +1,146 @@ +import * as codecs from "./codec-binding.js"; +import { Buffer } from "node:buffer"; +import { library, message } from "./native-zlib.js"; +import * as constants from "./constants.js"; +const codes: Record = { + 0: "Z_OK", + 1: "Z_STREAM_END", + 2: "Z_NEED_DICT", + [-1]: "Z_ERRNO", + [-2]: "Z_STREAM_ERROR", + [-3]: "Z_DATA_ERROR", + [-4]: "Z_MEM_ERROR", + [-5]: "Z_BUF_ERROR", + [-6]: "Z_VERSION_ERROR", +}; +export class ZlibStream { + private api = library(); + private context = 0; + private state!: Uint32Array; + private callback!: () => void; + private onerror?: (errno: number, code: string, message: string) => void; + constructor(readonly mode: number) {} + initialize( + windowBits: number, + level: number, + memLevel: number, + strategy: number, + state: Uint32Array, + callback: () => void, + dictionary?: ArrayBufferView, + ) { + this.state = state; + this.callback = callback; + const bytes = + dictionary && + Buffer.from( + dictionary.buffer, + dictionary.byteOffset, + dictionary.byteLength, + ); + const pointer = bytes ? this.api.malloc(bytes.length || 1) : 0; + try { + if (bytes) { + if (!pointer) throw new RangeError("Memory limit exceeded"); + new Uint8Array(this.api.memory.buffer).set(bytes, pointer); + } + this.context = this.api.cz_create( + this.mode, + windowBits, + Math.trunc(level), + Math.trunc(memLevel), + Math.trunc(strategy), + pointer, + bytes?.length ?? 0, + ); + if (!this.context) throw new Error("Zlib error"); + } finally { + if (pointer) this.api.free(pointer); + } + } + setErrorHandler( + handler: (errno: number, code: string, message: string) => void, + ) { + this.onerror = handler; + } + writeSync( + flush: number, + input: Uint8Array, + inputOffset: number, + inputLength: number, + output: Uint8Array, + outputOffset: number, + outputLength: number, + ) { + const api = this.api; + const buffer = ArrayBuffer.isView(input) + ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) + : new Uint8Array(input); + const pointer = api.malloc(Math.max(1, inputLength) + outputLength + 8); + if (!pointer) throw new RangeError("Memory limit exceeded"); + const out = pointer + Math.max(1, inputLength), + state = out + outputLength; + try { + new Uint8Array(api.memory.buffer).set( + buffer.subarray(inputOffset, inputOffset + inputLength), + pointer, + ); + const result = api.cz_process( + this.context, + pointer, + inputLength, + out, + outputLength, + flush, + state, + ); + const view = new DataView(api.memory.buffer); + this.state[0] = view.getUint32(state, true); + this.state[1] = view.getUint32(state + 4, true); + output.set( + new Uint8Array(api.memory.buffer, out, outputLength - this.state[0]), + outputOffset, + ); + if ( + result !== 0 && + result !== 1 && + (result !== -5 || (flush === 4 && this.state[0] !== 0)) + ) + this.onerror?.( + result, + codes[result], + result === -5 + ? "unexpected end of file" + : message(api, api.cz_message(this.context)), + ); + } finally { + api.free(pointer); + } + } + write(...args: Parameters) { + this.writeSync(...args); + this.callback(); + } + reset() { + const result = this.api.cz_reset(this.context); + if (result !== 0) + this.onerror?.(result, codes[result], "Failed to reset stream"); + } + params(level: number, strategy: number) { + const result = this.api.cz_params(this.context, level, strategy); + if (result !== 0) + this.onerror?.(result, codes[result], "Failed to set parameters"); + } + close() { + if (this.context) { + this.api.cz_free(this.context); + this.context = 0; + } + } +} +export default Object.assign( + { ZlibStream, ...codecs }, + Object.fromEntries( + Object.entries(constants).map(([name, value]) => ["CONST_" + name, value]), + ), +); diff --git a/src/compat/zlib/classic.ts b/src/compat/zlib/classic.ts new file mode 100644 index 00000000..ff1cb98c --- /dev/null +++ b/src/compat/zlib/classic.ts @@ -0,0 +1,245 @@ +import { Buffer } from "node:buffer"; +import { Zlib } from "./classic-base.cjs"; +import { ZlibStream } from "./classic-binding.js"; +import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +export class Deflate extends Zlib { + constructor(options?: any) { + super(options, 1); + } +} +export class Inflate extends Zlib { + constructor(options?: any) { + super(options, 2); + } +} +export class Gzip extends Zlib { + constructor(options?: any) { + super(options, 3); + } +} +export class Gunzip extends Zlib { + constructor(options?: any) { + super(options, 4); + } +} +export class DeflateRaw extends Zlib { + constructor(options?: any) { + if (options?.windowBits === 8) options.windowBits = 9; + super(options, 5); + } +} +export class InflateRaw extends Zlib { + constructor(options?: any) { + super(options, 6); + } +} +export class Unzip extends Zlib { + constructor(options?: any) { + super(options, 7); + } +} +const constructors = [ + Deflate, + Inflate, + Gzip, + Gunzip, + DeflateRaw, + InflateRaw, + Unzip, +]; +export const createDeflate = (options?: any) => new Deflate(options); +export const createInflate = (options?: any) => new Inflate(options); +export const createGzip = (options?: any) => new Gzip(options); +export const createGunzip = (options?: any) => new Gunzip(options); +export const createDeflateRaw = (options?: any) => new DeflateRaw(options); +export const createInflateRaw = (options?: any) => new InflateRaw(options); +export const createUnzip = (options?: any) => new Unzip(options); +const maxChunk = 134217728; +function bytes(input: any, parameter = 1): Buffer { + if (typeof input === "string") return Buffer.from(input); + if (ArrayBuffer.isView(input)) + return Buffer.from(input.buffer, input.byteOffset, input.byteLength); + if (input instanceof ArrayBuffer) return Buffer.from(input); + throw new TypeError( + `Failed to execute 'zlibSync' on 'ZlibUtil': parameter ${parameter} is not of type 'string or ArrayBuffer or ArrayBufferView'.`, + ); +} +function integer(value: any, fallback: number, unsigned = true) { + if (value === undefined) return fallback; + const number = Number(value); + if ( + !Number.isFinite(number) || + number < (unsigned ? 0 : -2147483648) || + number > (unsigned ? 4294967295 : 2147483647) + ) + throw new TypeError( + "The value cannot be converted because it is not an integer.", + ); + return Math.trunc(number); +} +function bounded(value: number, name: string, min: number, max: number) { + if (value < min || value > max) + throw new RangeError( + `The value of "options.${name}" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`, + ); + return value; +} +function prepare(input: any, options: any) { + const data = bytes(input); + if (typeof options !== "object" || options === null) + throw new TypeError( + "Failed to execute 'zlibSync' on 'ZlibUtil': parameter 2 is not of type 'Options'.", + ); + // Conversion precedes native validation, including otherwise unused flush. + const opts = { + flush: integer(options.flush, 0, false), + finishFlush: integer(options.finishFlush, 4, false), + chunkSize: integer(options.chunkSize, 16384), + windowBits: integer(options.windowBits, 15), + level: integer(options.level, -1, false), + memLevel: integer(options.memLevel, 8), + strategy: integer(options.strategy, 0), + dictionary: + options.dictionary === undefined ? undefined : bytes(options.dictionary), + maxOutputLength: integer(options.maxOutputLength, maxChunk), + }; + return { data, opts }; +} +function fast( + mode: number, + data: Buffer, + opts: ReturnType["opts"], +) { + bounded(opts.chunkSize, "chunkSize", 64, maxChunk); + bounded(opts.maxOutputLength, "maxOutputLength", 1, maxChunk); + if (!(opts.windowBits === 0 && [2, 4, 7].includes(mode))) + bounded(opts.windowBits, "windowBits", 8, 15); + bounded(opts.level, "level", -1, 9); + bounded(opts.memLevel, "memLevel", 1, 9); + if (opts.strategy > 4) throw new Error("invalid strategy"); + if (mode === 3 && opts.windowBits === 8) throw new Error("Zlib error"); + bounded(opts.flush, "flush", 0, 6); + bounded(opts.finishFlush, "finishFlush", 0, 6); + const state = new Uint32Array(2), + handle = new ZlibStream(mode), + chunks: Buffer[] = []; + let error: Error | undefined, + offset = 0, + length = 0; + handle.initialize( + opts.windowBits, + opts.level, + opts.memLevel, + opts.strategy, + state, + () => {}, + opts.dictionary, + ); + handle.setErrorHandler((_errno, _code, message) => { + error = new Error(message); + }); + try { + for (;;) { + const size = Math.min(16384, opts.maxOutputLength - length + 1); + const output = Buffer.allocUnsafe(size); + handle.writeSync( + opts.finishFlush, + data, + offset, + data.length - offset, + output, + 0, + size, + ); + if (error) throw error; + offset = data.length - state[1]; + const produced = size - state[0]; + length += produced; + if (length > opts.maxOutputLength) + throw new RangeError("Memory limit exceeded"); + if (produced) chunks.push(output.subarray(0, produced)); + if (state[0] > 0) break; + } + return Buffer.concat(chunks, length); + } finally { + handle.close(); + } +} +function sync(mode: number, input: any, options: any = {}) { + if (options.info) { + const engine = new constructors[mode - 1](options); + return { + buffer: engine._processChunk( + typeof input === "string" ? Buffer.from(input) : input, + engine._finishFlushFlag, + ), + engine, + }; + } + const { data, opts } = prepare(input, options); + return fast(mode, data, opts); +} +function callbackOperation( + mode: number, + input: any, + options: any, + callback?: any, +) { + if (typeof options === "function") { + callback = options; + options = {}; + } + if (typeof callback !== "function") + throw new ERR_INVALID_ARG_TYPE("callback", "Function", callback); + if (options.info) { + const engine = new constructors[mode - 1](options); + let result: any, error: unknown; + try { + result = { + buffer: engine._processChunk( + typeof input === "string" ? Buffer.from(input) : input, + engine._finishFlushFlag, + ), + engine, + }; + } catch (e) { + error = e; + } + queueMicrotask(() => (error ? callback(error) : callback(null, result))); + return; + } + const { data, opts } = prepare(input, options); + let result: Buffer | undefined, error: unknown; + try { + result = fast(mode, data, opts); + } catch (e) { + error = e; + } + queueMicrotask(() => (error ? callback(error) : callback(null, result))); +} +export const deflateSync = (input: any, options?: any) => + sync(1, input, options); +export const inflateSync = (input: any, options?: any) => + sync(2, input, options); +export const gzipSync = (input: any, options?: any) => sync(3, input, options); +export const gunzipSync = (input: any, options?: any) => + sync(4, input, options); +export const deflateRawSync = (input: any, options?: any) => + sync(5, input, options); +export const inflateRawSync = (input: any, options?: any) => + sync(6, input, options); +export const unzipSync = (input: any, options?: any) => sync(7, input, options); +export const deflate = (input: any, options: any, callback?: any) => + callbackOperation(1, input, options, callback); +export const inflate = (input: any, options: any, callback?: any) => + callbackOperation(2, input, options, callback); +export const gzip = (input: any, options: any, callback?: any) => + callbackOperation(3, input, options, callback); +export const gunzip = (input: any, options: any, callback?: any) => + callbackOperation(4, input, options, callback); +export const deflateRaw = (input: any, options: any, callback?: any) => + callbackOperation(5, input, options, callback); +export const inflateRaw = (input: any, options: any, callback?: any) => + callbackOperation(6, input, options, callback); +export const unzip = (input: any, options: any, callback?: any) => + callbackOperation(7, input, options, callback); diff --git a/src/compat/zlib/codec-binding.ts b/src/compat/zlib/codec-binding.ts new file mode 100644 index 00000000..f2e88a8f --- /dev/null +++ b/src/compat/zlib/codec-binding.ts @@ -0,0 +1,191 @@ +import { library, string } from "./native-codecs.js"; +class CodecBinding { + private api = library(); + private context = 0; + private state!: Uint32Array; + private callback!: () => void; + private onerror?: (errno: number, code: string, message: string) => void; + private params!: Uint32Array | Int32Array; + private pledged?: number; + private finished = false; + constructor( + private readonly kind: "brotli" | "zstd", + private readonly encode: boolean, + ) {} + initialize( + params: Uint32Array | Int32Array, + state: Uint32Array, + callback: () => void, + pledged?: number, + ): boolean { + this.finished = false; + this.params = params.slice(); + this.state = state; + this.callback = callback; + this.pledged = pledged; + const api = this.api; + this.context = (this.kind === "brotli" ? api.b_create : api.z_create)( + +this.encode, + ); + if (!this.context) return false; + for (let key = 0; key < params.length; key++) + if ((params[key] | 0) !== -1) { + const result = (this.kind === "brotli" ? api.b_param : api.z_param)( + this.context, + +this.encode, + key, + params[key], + ); + if (this.kind === "brotli" ? !result : api.z_is_error(result)) { + this.close(); + return false; + } + } + if (pledged !== undefined && this.kind === "zstd" && this.encode) { + if ( + typeof pledged !== "number" || + !Number.isFinite(pledged) || + pledged < 0 || + pledged >= 18446744073709551616 + ) { + this.close(); + throw new TypeError( + "The value cannot be converted because it is not an integer.", + ); + } + if ( + api.z_is_error( + api.z_size64( + this.context, + pledged >>> 0, + Math.floor(pledged / 4294967296) >>> 0, + ), + ) + ) { + this.close(); + return false; + } + } + return true; + } + setErrorHandler( + handler: (errno: number, code: string, message: string) => void, + ) { + this.onerror = handler; + } + writeSync( + flush: number, + input: Uint8Array, + inputOffset: number, + inputLength: number, + output: Uint8Array, + outputOffset: number, + outputLength: number, + ) { + if ( + this.finished && + !this.encode && + (inputLength === 0 || this.kind === "brotli") + ) { + this.state[0] = outputLength; + this.state[1] = inputLength; + return; + } + const api = this.api, + buffer = ArrayBuffer.isView(input) + ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) + : new Uint8Array(input); + const pointer = api.malloc(Math.max(1, inputLength) + outputLength + 8); + if (!pointer) throw new RangeError("Memory limit exceeded"); + const out = pointer + Math.max(1, inputLength), + state = out + outputLength; + try { + new Uint8Array(api.memory.buffer).set( + buffer.subarray(inputOffset, inputOffset + inputLength), + pointer, + ); + const result = (this.kind === "brotli" ? api.b_process : api.z_process)( + this.context, + +this.encode, + pointer, + inputLength, + out, + outputLength, + flush, + state, + ); + this.finished = this.kind === "brotli" ? result === 1 : result === 0; + const view = new DataView(api.memory.buffer); + const consumed = view.getUint32(state, true), + produced = view.getUint32(state + 4, true); + this.state[0] = outputLength - produced; + this.state[1] = inputLength - consumed; + output.set( + new Uint8Array(api.memory.buffer, out, produced), + outputOffset, + ); + if (this.kind === "brotli" && !result) { + const errno = this.encode ? 0 : api.b_error(this.context); + this.onerror?.( + errno, + this.encode + ? "ERR_BROTLI_COMPRESSION_FAILED" + : "ERR_" + string(api, api.b_error_string(errno)), + "Compression failed", + ); + } else if (this.kind === "zstd" && api.z_is_error(result)) + this.onerror?.( + result | 0, + "ZSTD_ERROR", + string(api, api.z_error(result)), + ); + else if ( + !this.encode && + flush === 2 && + produced < outputLength && + (this.kind === "brotli" ? result !== 1 : result !== 0) + ) + this.onerror?.(-5, "Z_BUF_ERROR", "Unexpected end of file"); + } finally { + api.free(pointer); + } + } + write(...args: Parameters) { + this.writeSync(...args); + this.callback(); + } + reset() { + this.close(); + if (!this.initialize(this.params, this.state, this.callback, this.pledged)) + throw new Error("Initialization failed"); + } + close() { + if (this.context) { + (this.kind === "brotli" ? this.api.b_free : this.api.z_free)( + this.context, + +this.encode, + ); + this.context = 0; + } + } +} +export class BrotliEncoder extends CodecBinding { + constructor() { + super("brotli", true); + } +} +export class BrotliDecoder extends CodecBinding { + constructor() { + super("brotli", false); + } +} +export class ZstdEncoder extends CodecBinding { + constructor() { + super("zstd", true); + } +} +export class ZstdDecoder extends CodecBinding { + constructor() { + super("zstd", false); + } +} diff --git a/src/compat/zlib/compression.ts b/src/compat/zlib/compression.ts index ae37e328..3c1ed199 100644 --- a/src/compat/zlib/compression.ts +++ b/src/compat/zlib/compression.ts @@ -1,238 +1,32 @@ import { Buffer } from "node:buffer"; -import { Transform } from "node:stream"; import { NativeCodec, type Kind } from "./native-codecs.js"; -import { validateFunction, validateObject } from "../net/validators.js"; -import { validateInteger } from "../crypto/validation.js"; -import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "../net/errors.js"; -const flushMarker = Symbol("codec flush"); -function bytes(value: unknown): Uint8Array { - if (typeof value === "string") return Buffer.from(value); - if (ArrayBuffer.isView(value)) - return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); - if (value instanceof ArrayBuffer) return new Uint8Array(value); - throw new ERR_INVALID_ARG_TYPE( - "buffer", - ["string", "Buffer", "TypedArray", "DataView", "ArrayBuffer"], - value, - ); -} -// Pinned workerd validators: NaN selects defaults; finite fractional values -// are accepted here and converted by the underlying buffer/codec operation. -function finite(value: unknown, name: string): value is number { - if (value === undefined || Number.isNaN(value)) return false; - if (typeof value !== "number") - throw new ERR_INVALID_ARG_TYPE(name, "number", value); - if (!Number.isFinite(value)) - throw new ERR_OUT_OF_RANGE(name, "a finite number", value); - return true; -} -function range( - value: unknown, - name: string, - low: number, - high: number, - fallback: number, -): number { - if (!finite(value, name)) return fallback; - if (value < low || value > high) - throw new ERR_OUT_OF_RANGE(name, `>= ${low} and <= ${high}`, value); - return value; -} -function settings(kind: Kind, encode: boolean, value: any = {}) { - if (value == null) value = {}; - validateObject(value, "options"); - const chunkSize = finite(value.chunkSize, "options.chunkSize") - ? value.chunkSize - : 16384; - if (chunkSize < 64) - throw new ERR_OUT_OF_RANGE("options.chunkSize", ">= 64", chunkSize); - const maxOutputLength = range( - value.maxOutputLength, - "options.maxOutputLength", - 1, - 2147483647, - 2147483647, - ); - const params = value.params ?? {}; - validateObject(params, "options.params"); - const maximum = kind === "brotli" ? 8 : encode ? 402 : 100; - const seen = new Set(); - for (const [key, val] of Object.entries(params)) { - const number = Number(key); - if ( - !Number.isInteger(number) || - number < 0 || - number > maximum || - seen.has(number) - ) - throw Object.assign( - new RangeError( - `${key} is not a valid ${kind === "brotli" ? "Brotli" : "Zstd"} parameter`, - ), - { - code: - kind === "brotli" - ? "ERR_BROTLI_INVALID_PARAM" - : "ERR_ZSTD_INVALID_PARAM", - }, - ); - seen.add(number); - if (typeof val !== "number" && typeof val !== "boolean") - throw new ERR_INVALID_ARG_TYPE("options.params[key]", "number", val); - } - const flush = range( - value.flush, - "options.flush", - 0, - kind === "brotli" ? 3 : 2, - 0, - ); - const finishFlush = range( - value.finishFlush, - "options.finishFlush", - 0, - kind === "brotli" ? 3 : 2, - 2, - ); - if (value.pledgedSrcSize !== undefined) - validateInteger( - value.pledgedSrcSize, - "options.pledgedSrcSize", - 0, - 0xffffffff, - ); - return { - chunkSize, - maxOutputLength, - params, - flush, - finishFlush, - pledged: value.pledgedSrcSize, - info: !!value.info, - }; -} -class CodecTransform extends Transform { - private codec: NativeCodec; - private readonly config: ReturnType; - bytesWritten = 0; - private readonly kind: Kind; - private readonly encode: boolean; - constructor(kind: Kind, encode: boolean, options?: any) { - const config = settings(kind, encode, options); - super(options ?? {}); - this.kind = kind; - this.encode = encode; - this.config = config; - this.codec = new NativeCodec( - kind, - encode, - config.chunkSize, - config.params, - config.pledged, - ); - } - _transform(input: any, _encoding: any, callback: any) { - try { - const operation = input[flushMarker] ?? this.config.flush; - this.codec.process(bytes(input), operation, (output) => - this.push(Buffer.from(output)), - ); - this.bytesWritten = this.codec.consumed; - callback(); - } catch (error) { - callback(error); - } - } - _flush(callback: any) { - try { - this.codec.process(new Uint8Array(), this.config.finishFlush, (output) => - this.push(Buffer.from(output)), - ); - this.bytesWritten = this.codec.consumed; - callback(); - } catch (error) { - callback(error); - } - } - _destroy(error: any, callback: any) { - this.codec.close(); - callback(error); - } - flush(kind?: number | (() => void), callback?: () => void) { - if (typeof kind === "function") { - callback = kind; - kind = 1; - } - kind ??= 1; - validateInteger(kind, "kind", 0, this.kind === "brotli" ? 3 : 2); - if (callback !== undefined) validateFunction(callback, "callback"); - if (this.writableFinished) { - if (callback) queueMicrotask(callback); - return; - } - const marker: any = Buffer.alloc(0); - marker[flushMarker] = kind; - this.write(marker, callback); - } - reset() { - if (this.destroyed) throw new Error("Codec is closed"); - this.codec.close(); - this.codec = new NativeCodec( - this.kind, - this.encode, - this.config.chunkSize, - this.config.params, - this.config.pledged, - ); - this.bytesWritten = 0; - } - close(callback?: () => void) { - if (callback) { - validateFunction(callback, "callback"); - this.once("close", callback); - } - this.destroy(); - } - _processChunk(input: unknown, operation = 2) { - const chunks: Uint8Array[] = []; - let length = 0; - try { - this.codec.process(bytes(input), operation, (output) => { - length += output.length; - if (length > this.config.maxOutputLength) - throw Object.assign( - new RangeError( - "Cannot create a Buffer larger than maxOutputLength", - ), - { code: "ERR_BUFFER_TOO_LARGE" }, - ); - chunks.push(output); - }); - this.bytesWritten = this.codec.consumed; - return Buffer.concat(chunks, length); - } finally { - this.codec.close(); - } - } -} -export class BrotliCompress extends CodecTransform { +import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +import { + Brotli, + Zstd, + zstdInitCParamsArray, + zstdInitDParamsArray, + kMaxZstdCParam, + kMaxZstdDParam, +} from "./classic-base.cjs"; +export class BrotliCompress extends Brotli { constructor(options?: any) { - super("brotli", true, options); + super(options, 9); } } -export class BrotliDecompress extends CodecTransform { +export class BrotliDecompress extends Brotli { constructor(options?: any) { - super("brotli", false, options); + super(options, 8); } } -export class ZstdCompress extends CodecTransform { +export class ZstdCompress extends Zstd { constructor(options?: any) { - super("zstd", true, options); + super(options, 10, zstdInitCParamsArray, kMaxZstdCParam); } } -export class ZstdDecompress extends CodecTransform { +export class ZstdDecompress extends Zstd { constructor(options?: any) { - super("zstd", false, options); + super(options, 11, zstdInitDParamsArray, kMaxZstdDParam); } } export const createBrotliCompress = (options?: any) => @@ -242,35 +36,97 @@ export const createBrotliDecompress = (options?: any) => export const createZstdCompress = (options?: any) => new ZstdCompress(options); export const createZstdDecompress = (options?: any) => new ZstdDecompress(options); -function sync(kind: Kind, encode: boolean, input: unknown, options?: any) { - const data = bytes(input), - config = settings(kind, encode, options); - if (config.info) { - const Constructor = - kind === "brotli" - ? encode - ? BrotliCompress - : BrotliDecompress - : encode - ? ZstdCompress - : ZstdDecompress; - const engine = new Constructor(options); - return { buffer: engine._processChunk(data, config.finishFlush), engine }; - } + +function bytes(value: unknown): Uint8Array { + if (typeof value === "string") return Buffer.from(value); + if (ArrayBuffer.isView(value)) + return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + if (value instanceof ArrayBuffer) return new Uint8Array(value); + throw new TypeError( + "Input must be a string or ArrayBuffer or ArrayBufferView", + ); +} +function integer(value: unknown, fallback: number, unsigned = true) { + if (value === undefined) return fallback; + const number = Number(value); + if ( + !Number.isFinite(number) || + number < (unsigned ? 0 : -2147483648) || + number > (unsigned ? 4294967295 : 2147483647) + ) + throw new TypeError( + "The value cannot be converted because it is not an integer.", + ); + return Math.trunc(number); +} +function pledged(value: any) { + if (value === undefined) return undefined; + const number = Number(value); + if (!Number.isFinite(number) || number < 0 || number >= 18446744073709551616) + throw new TypeError( + "The value cannot be converted because it is not an integer.", + ); + return Math.trunc(number); +} +function convert(options: any) { + if (!options || typeof options !== "object") + throw new TypeError("Options must be an object"); + if ( + options.params !== undefined && + (!options.params || typeof options.params !== "object") + ) + throw new TypeError( + "Incorrect type for the 'params' field on 'Options': the provided value is not of type 'object'.", + ); + const params = Object.fromEntries( + Object.entries(options.params ?? {}).map(([key, value]) => [ + key, + Number(value) | 0, + ]), + ); + return { + chunkSize: integer(options.chunkSize, 16384), + maxOutputLength: integer(options.maxOutputLength, 134217728), + flush: integer(options.flush, 0, false), + finishFlush: integer(options.finishFlush, 2, false), + params, + pledged: pledged(options.pledgedSrcSize), + }; +} +function bounded(value: number, name: string, min: number, max: number) { + if (value < min || value > max) + throw new RangeError( + `The value of "options.${name}" is out of range. It must be >= ${min} and <= ${max}. Received ${value}`, + ); +} +function fast( + kind: Kind, + encode: boolean, + data: Uint8Array, + config: ReturnType, +) { + bounded(config.chunkSize, "chunkSize", 64, 134217728); + bounded(config.maxOutputLength, "maxOutputLength", 1, 134217728); + for (const key of Object.keys(config.params)) + if (!/^[+-]?\d+$/.test(key)) throw new Error("Invalid parameter name"); const codec = new NativeCodec( kind, encode, - config.chunkSize, + Math.min(16384, config.maxOutputLength), config.params, config.pledged ?? (encode && kind === "zstd" ? data.length : undefined), ); const chunks: Uint8Array[] = []; let length = 0; try { + bounded(config.flush, "flush", 0, kind === "brotli" ? 3 : 2); + bounded(config.finishFlush, "finishFlush", 0, kind === "brotli" ? 3 : 2); codec.process(data, config.finishFlush, (output) => { length += output.length; if (length > config.maxOutputLength) - throw new RangeError("Memory limit exceeded"); + throw kind === "zstd" && encode + ? new Error("Unexpected end of file") + : new RangeError("Memory limit exceeded"); chunks.push(output); }); return Buffer.concat(chunks, length); @@ -285,6 +141,32 @@ function sync(kind: Kind, encode: boolean, input: unknown, options?: any) { codec.close(); } } +function engine(kind: Kind, encode: boolean, options: any) { + return new ( + kind === "brotli" + ? encode + ? BrotliCompress + : BrotliDecompress + : encode + ? ZstdCompress + : ZstdDecompress + )(options); +} +function sync(kind: Kind, encode: boolean, input: unknown, options: any = {}) { + if (options.info) { + const instance = engine(kind, encode, options); + return { + buffer: instance._processChunk( + typeof input === "string" + ? Buffer.from(input) + : (input as ArrayBufferView), + instance._finishFlushFlag, + ), + engine: instance, + }; + } + return fast(kind, encode, bytes(input), convert(options)); +} function asyncOperation( kind: Kind, encode: boolean, @@ -294,41 +176,51 @@ function asyncOperation( ) { if (typeof options === "function") { callback = options; - options = undefined; + options = {}; + } + if (typeof callback !== "function") + throw new ERR_INVALID_ARG_TYPE("callback", "Function", callback); + let run: () => unknown; + if (options.info) { + const instance = engine(kind, encode, options); + run = () => ({ + buffer: instance._processChunk( + typeof input === "string" + ? Buffer.from(input) + : (input as ArrayBufferView), + instance._finishFlushFlag, + ), + engine: instance, + }); + } else { + const data = bytes(input), + config = convert(options); + run = () => fast(kind, encode, data, config); } - validateFunction(callback, "callback"); - const data = bytes(input); - settings(kind, encode, options); - new Promise((resolve) => resolve(sync(kind, encode, data, options))).then( - (result) => callback(null, result), - (error) => callback(error), - ); -} -export function brotliCompressSync(input: unknown, options?: any) { - return sync("brotli", true, input, options); -} -export function brotliDecompressSync(input: unknown, options?: any) { - return sync("brotli", false, input, options); -} -export function zstdCompressSync(input: unknown, options?: any) { - return sync("zstd", true, input, options); -} -export function zstdDecompressSync(input: unknown, options?: any) { - return sync("zstd", false, input, options); -} -export function brotliCompress(input: unknown, options?: any, callback?: any) { + let result: unknown, error: unknown; + try { + result = run(); + } catch (failure) { + error = failure; + } + queueMicrotask(() => (error ? callback(error) : callback(null, result))); +} +export const brotliCompressSync = (input: unknown, options?: any) => + sync("brotli", true, input, options); +export const brotliDecompressSync = (input: unknown, options?: any) => + sync("brotli", false, input, options); +export const zstdCompressSync = (input: unknown, options?: any) => + sync("zstd", true, input, options); +export const zstdDecompressSync = (input: unknown, options?: any) => + sync("zstd", false, input, options); +export const brotliCompress = (input: unknown, options?: any, callback?: any) => asyncOperation("brotli", true, input, options, callback); -} -export function brotliDecompress( +export const brotliDecompress = ( input: unknown, options?: any, callback?: any, -) { - asyncOperation("brotli", false, input, options, callback); -} -export function zstdCompress(input: unknown, options?: any, callback?: any) { +) => asyncOperation("brotli", false, input, options, callback); +export const zstdCompress = (input: unknown, options?: any, callback?: any) => asyncOperation("zstd", true, input, options, callback); -} -export function zstdDecompress(input: unknown, options?: any, callback?: any) { +export const zstdDecompress = (input: unknown, options?: any, callback?: any) => asyncOperation("zstd", false, input, options, callback); -} diff --git a/src/compat/zlib/errors.ts b/src/compat/zlib/errors.ts new file mode 100644 index 00000000..f01c48c1 --- /dev/null +++ b/src/compat/zlib/errors.ts @@ -0,0 +1,33 @@ +export { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "../net/errors.js"; +export class NodeError extends Error { + constructor( + readonly code: string, + message: string, + ) { + super(message); + } +} +export class ERR_BUFFER_TOO_LARGE extends RangeError { + readonly code = "ERR_BUFFER_TOO_LARGE"; + constructor(size: number) { + super(`Cannot create a Buffer larger than ${size} bytes`); + } +} +export class ERR_BROTLI_INVALID_PARAM extends RangeError { + readonly code = "ERR_BROTLI_INVALID_PARAM"; + constructor(key: string) { + super(`${key} is not a valid Brotli parameter`); + } +} +export class ERR_ZSTD_INVALID_PARAM extends RangeError { + readonly code = "ERR_ZSTD_INVALID_PARAM"; + constructor(key: string) { + super(`${key} is not a valid Zstd parameter`); + } +} +export class ERR_ZLIB_INITIALIZATION_FAILED extends Error { + readonly code = "ERR_ZLIB_INITIALIZATION_FAILED"; + constructor() { + super("Initialization failed"); + } +} diff --git a/src/compat/zlib/index.ts b/src/compat/zlib/index.ts index 28e3c0ec..e6112e98 100644 --- a/src/compat/zlib/index.ts +++ b/src/compat/zlib/index.ts @@ -1,42 +1,31 @@ import { Buffer } from "node:buffer"; -// @ts-expect-error browserify-zlib has no declarations -import original from "browserify-zlib/lib/index.js"; import { validateInteger } from "../crypto/validation.js"; import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; import * as compression from "./compression.js"; export * from "./compression.js"; -export const { - Zlib, - codes, - Deflate, - Inflate, - Gzip, - Gunzip, - DeflateRaw, - InflateRaw, - Unzip, - createDeflate, - createInflate, - createDeflateRaw, - createInflateRaw, - createGzip, - createGunzip, - createUnzip, - deflate, - deflateSync, - gzip, - gzipSync, - deflateRaw, - deflateRawSync, - unzip, - unzipSync, - inflate, - inflateSync, - gunzip, - gunzipSync, - inflateRaw, - inflateRawSync, -} = original; +import * as classic from "./classic.js"; +export * from "./classic.js"; +export const codes = Object.freeze( + Object.fromEntries( + [ + "Z_OK", + "Z_STREAM_END", + "Z_NEED_DICT", + "Z_ERRNO", + "Z_STREAM_ERROR", + "Z_DATA_ERROR", + "Z_MEM_ERROR", + "Z_BUF_ERROR", + "Z_VERSION_ERROR", + ].flatMap((name) => { + const value = pinnedConstants[name as keyof typeof pinnedConstants]; + return [ + [name, value], + [value, name], + ]; + }), + ), +); import * as pinnedConstants from "./constants.js"; const topLevelConstants = { Z_NO_FLUSH: pinnedConstants.Z_NO_FLUSH, @@ -390,7 +379,8 @@ export function crc32(input: unknown, value = 0): number { return (crc ^ -1) >>> 0; } export default { - ...original, + codes, + ...classic, ...topLevelConstants, constants, ...compression, diff --git a/src/compat/zlib/native-codecs.build.json b/src/compat/zlib/native-codecs.build.json index 2d4d5d2d..fa83ee45 100644 --- a/src/compat/zlib/native-codecs.build.json +++ b/src/compat/zlib/native-codecs.build.json @@ -4,5 +4,6 @@ "zstd": "f8745da6ff1ad1e7bab384bd1f9d742439278e99", "brotli": "ed738e842d2fbdf2d6459e39267a633c4a9b2f5d" }, - "sha256": "52f2d0edcc0193e5289a0e9b945e2b1a2de8b6ea3efadf11ebd60b88c53d11a4" + "bindingSha256": "de7a1c0cb1f58ec553a00b574e08ff21626bcbb134050da90ca5a46dc66d164a", + "sha256": "c20e4002af3f5482cf2b022034e641e2ee2e8623023971de5176c2d5144946b3" } diff --git a/src/compat/zlib/native-codecs.c b/src/compat/zlib/native-codecs.c index eb9bc829..2269c96b 100644 --- a/src/compat/zlib/native-codecs.c +++ b/src/compat/zlib/native-codecs.c @@ -24,6 +24,7 @@ API size_t z_process(void* ctx, int encode, const void* src, size_t len, void* d } API int z_is_error(size_t code) { return ZSTD_isError(code); } API const char* z_error(size_t code) { return ZSTD_getErrorName(code); } +API size_t z_size64(void* ctx, uint32_t low, uint32_t high) { return ZSTD_CCtx_setPledgedSrcSize(ctx, ((uint64_t)high << 32) | low); } API size_t z_size(void* ctx, uint32_t length) { return ZSTD_CCtx_setPledgedSrcSize(ctx, length); } API void* b_create(int encode) { return encode ? (void*)BrotliEncoderCreateInstance(NULL, NULL, NULL) : (void*)BrotliDecoderCreateInstance(NULL, NULL, NULL); } diff --git a/src/compat/zlib/native-codecs.ts b/src/compat/zlib/native-codecs.ts index f4b2acff..74d839c1 100644 --- a/src/compat/zlib/native-codecs.ts +++ b/src/compat/zlib/native-codecs.ts @@ -3,7 +3,7 @@ type API = Record number> & { memory: WebAssembly.Memory; }; let api: API | undefined; -function library(): API { +export function library(): API { if (!api) { const module = new WebAssembly.Module(binary as BufferSource); api = new WebAssembly.Instance(module, { @@ -21,7 +21,7 @@ function library(): API { } return api; } -function string(api: API, pointer: number) { +export function string(api: API, pointer: number) { const bytes = new Uint8Array(api.memory.buffer); let end = pointer; while (bytes[end]) end++; @@ -60,12 +60,20 @@ export class NativeCodec { Number(value), ); if (kind === "brotli" ? !result : api.z_is_error(result)) - throw Object.assign(new Error("Initialization failed"), { - code: "ERR_ZLIB_INITIALIZATION_FAILED", - }); + throw new Error( + kind === "brotli" + ? "Setting parameter failed" + : "Setting parameter failed: " + string(api, api.z_error(result)), + ); } if (pledged !== undefined && kind === "zstd" && encode) - this.zcheck(api.z_size(this.ctx, pledged)); + this.zcheck( + api.z_size64( + this.ctx, + pledged >>> 0, + Math.floor(pledged / 4294967296) >>> 0, + ), + ); } catch (error) { this.close(); throw error; diff --git a/src/compat/zlib/native-codecs.wasm b/src/compat/zlib/native-codecs.wasm index a3cdd9c7348a548a8eb6674be22058f083980283..684db3bb90f7ef463c9b0eb8cc5b4ebf4fac3af4 100755 GIT binary patch delta 15054 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≪ zKax-DYpL)9xlvzBfFDUzy4Ea=oz8xju-8~^S;-rku64ot#>@s<1bH=GYh(h7L#qHk zq-%Ev0G&+T+^8yDaRaT3hTAaO%Jbk~Kak>NEsC6Ope=@<=#+-qJ0jBNO|{N^*jenX zk*S-cv3{1!YoQ&2OLS-(VZ7Q+V|&d*-8Ht?9P5tXO4~xEClk^cJ+=01JD)IKV;km_ambd^mc6xmn6$%$ z>Qbez){{v?CTZz(WGF-s?FF(iAk);MrIoM!gd9%YJ*an;(ASI24bwCg}^DN}th z2$P1#`oSjRzQN|a**jR-0YfQ zx}lghJZaR(7{%;;#$L%5NF1^ zH?;4;N9S+TR^#7H(WLF#1s=u9PVHq7>FYbSfdWWW*{ivE*g(?vX$O&9--m1R84~xN l=7tTl?Ry#pSU>;DehvRS@xKfIhvNS*{IAn1`@^%n{|6FlYBm4> diff --git a/src/compat/zlib/native-zlib.build.json b/src/compat/zlib/native-zlib.build.json new file mode 100644 index 00000000..7658be4d --- /dev/null +++ b/src/compat/zlib/native-zlib.build.json @@ -0,0 +1,7 @@ +{ + "emsdk": "e4fe26ef59168ff44f4c23c466e497bf60b3411e", + "zlib": "1.3.1", + "portSha256": "d3d8ca9a7caac82fa68e4984e47c03386093b8fa2b068e8e9eccedd3aa406926", + "bindingSha256": "df6ddf030f9c09f6cd341ed81555c0c913cb37f1e94984b5c6859aedc017d384", + "sha256": "862cb0b55eec1d22aedcff581b883fc278f0e00243ba0ee780e025c82cc6512f" +} diff --git a/src/compat/zlib/native-zlib.c b/src/compat/zlib/native-zlib.c new file mode 100644 index 00000000..f787981f --- /dev/null +++ b/src/compat/zlib/native-zlib.c @@ -0,0 +1,43 @@ +// Owned bridge to zlib 1.3.1; the compiler port pins and verifies its source hash. +#include +#include +#include +#include +#include +#define API EMSCRIPTEN_KEEPALIVE +struct Codec { z_stream stream; int encode, mode, ended; unsigned char *dictionary; unsigned dictionary_size; unsigned char pending[1024]; unsigned pending_size; unsigned char header[2]; unsigned header_size; }; +API void* cz_create(int mode, int bits, int level, int memory, int strategy, const unsigned char* dictionary, unsigned length) { + struct Codec* c=calloc(1,sizeof(*c)); if(!c)return NULL; + c->mode=mode;c->encode=mode==1||mode==3||mode==5; + if(mode==3||mode==4)bits+=16;else if(mode==7)bits+=32;else if(mode==5||mode==6)bits=-bits; + int result=c->encode?deflateInit2(&c->stream,level,Z_DEFLATED,bits,memory,strategy):inflateInit2(&c->stream,bits); + if(result!=Z_OK){free(c);return NULL;} + if(length){c->dictionary=malloc(length);if(!c->dictionary){if(c->encode)deflateEnd(&c->stream);else inflateEnd(&c->stream);free(c);return NULL;}memcpy(c->dictionary,dictionary,length);c->dictionary_size=length; + if(c->encode&&mode!=3)result=deflateSetDictionary(&c->stream,dictionary,length); + else if(mode==6)result=inflateSetDictionary(&c->stream,dictionary,length); + if(result!=Z_OK){if(c->encode)deflateEnd(&c->stream);else inflateEnd(&c->stream);free(c->dictionary);free(c);return NULL;} + } + return c; +} +API void cz_free(struct Codec* c){if(!c)return;if(c->encode)deflateEnd(&c->stream);else inflateEnd(&c->stream);free(c->dictionary);free(c);} +API int cz_reset(struct Codec* c){c->ended=0;c->pending_size=0;c->header_size=0;int r=c->encode?deflateReset(&c->stream):inflateReset(&c->stream);if(r==Z_OK&&c->dictionary_size&&((c->encode&&c->mode!=3)||c->mode==6))r=c->encode?deflateSetDictionary(&c->stream,c->dictionary,c->dictionary_size):inflateSetDictionary(&c->stream,c->dictionary,c->dictionary_size);return r;} +API int cz_params(struct Codec* c,int level,int strategy){if(c->mode!=1&&c->mode!=5)return Z_OK;c->stream.next_out=c->pending;c->stream.avail_out=sizeof(c->pending);int r=deflateParams(&c->stream,level,strategy);c->pending_size=sizeof(c->pending)-c->stream.avail_out;return r;} +API const char* cz_message(struct Codec* c){return c->stream.msg?c->stream.msg:"Zlib error";} +API int cz_process(struct Codec* c,const unsigned char* input,unsigned length,unsigned char* output,unsigned capacity,int flush,unsigned* state){ + unsigned prefix=c->pending_sizepending_size:capacity; + if(prefix){memcpy(output,c->pending,prefix);memmove(c->pending,c->pending+prefix,c->pending_size-prefix);c->pending_size-=prefix;} + if(prefix==capacity){state[0]=0;state[1]=length;return Z_OK;} + c->stream.next_in=(Bytef*)input;c->stream.avail_in=length;c->stream.next_out=output+prefix;c->stream.avail_out=capacity-prefix; + if(c->mode==7){unsigned n=0;while(c->header_size<2&&nheader[c->header_size++]=input[n++];if(c->header_size==2)c->mode=(c->header[0]==31&&c->header[1]==139)?4:2;} + if(c->ended&&c->mode==4&&length&&input[0]){inflateReset(&c->stream);c->ended=0;} + int result; + do { + result=c->encode?deflate(&c->stream,flush):inflate(&c->stream,flush); + if(result==Z_NEED_DICT&&c->dictionary){result=inflateSetDictionary(&c->stream,c->dictionary,c->dictionary_size);if(result==Z_OK)result=inflate(&c->stream,flush);else if(result==Z_DATA_ERROR)result=Z_NEED_DICT;} + if(result==Z_STREAM_END){c->ended=1;if(c->mode==4&&c->stream.avail_in&&c->stream.next_in[0]&&c->stream.avail_out){inflateReset(&c->stream);c->ended=0;continue;}} + break; + }while(1); + state[0]=c->stream.avail_out;state[1]=c->stream.avail_in; + if((result==Z_OK||result==Z_BUF_ERROR)&&flush==Z_FINISH&&c->stream.avail_out)return Z_BUF_ERROR; + return result; +} diff --git a/src/compat/zlib/native-zlib.ts b/src/compat/zlib/native-zlib.ts new file mode 100644 index 00000000..09230434 --- /dev/null +++ b/src/compat/zlib/native-zlib.ts @@ -0,0 +1,31 @@ +import binary from "./native-zlib.wasm"; +export type API = Record number> & { + memory: WebAssembly.Memory; +}; +let api: API | undefined; +export function library(): API { + if (!api) { + api = new WebAssembly.Instance( + new WebAssembly.Module(binary as BufferSource), + { + env: { emscripten_notify_memory_growth() {} }, + wasi_snapshot_preview1: { + proc_exit(code: number) { + throw Error(`Codec exited: ${code}`); + }, + fd_write() { + return 8; + }, + }, + }, + ).exports as unknown as API; + api._initialize(); + } + return api; +} +export function message(api: API, pointer: number): string { + const bytes = new Uint8Array(api.memory.buffer); + let end = pointer; + while (bytes[end]) end++; + return new TextDecoder().decode(bytes.subarray(pointer, end)); +} diff --git a/src/compat/zlib/native-zlib.wasm b/src/compat/zlib/native-zlib.wasm new file mode 100755 index 0000000000000000000000000000000000000000..b13eef8bfc8de6550a4958a58c988a66ba8a469b GIT binary patch literal 58126 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 EzfRiF&Hw-a literal 0 HcmV?d00001 diff --git a/src/compat/zlib/validators.ts b/src/compat/zlib/validators.ts new file mode 100644 index 00000000..20f12fda --- /dev/null +++ b/src/compat/zlib/validators.ts @@ -0,0 +1,25 @@ +import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "../net/errors.js"; +export { validateUint32 } from "../net/validators.js"; +export function checkFiniteNumber( + value: unknown, + name: string, +): value is number { + if (value === undefined || Number.isNaN(value)) return false; + if (typeof value !== "number") + throw new ERR_INVALID_ARG_TYPE(name, "number", value); + if (!Number.isFinite(value)) + throw new ERR_OUT_OF_RANGE(name, "a finite number", value); + return true; +} +export function checkRangesOrGetDefault( + value: unknown, + name: string, + min: number, + max: number, + fallback?: number, +): number | undefined { + if (!checkFiniteNumber(value, name)) return fallback; + if (value < min || value > max) + throw new ERR_OUT_OF_RANGE(name, `>= ${min} and <= ${max}`, value); + return value; +} From 409417ace079c2c9adf5cd9e230c46cae3423cff Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 01:06:11 +0900 Subject: [PATCH 02/30] feat: align virtual filesystem and stream contracts with Workers --- .../node-compat/completion-report.mjs | 2 +- scripts/build-native-zlib.mjs | 3 +- src/compat/expansion/README.md | 12 +- src/compat/expansion/index.spec.mjs | 247 ++ src/compat/fs/LICENSE-3RD-PARTY.md | 11 + src/compat/fs/LICENSE-APACHE | 177 ++ src/compat/fs/directory-backend.ts | 65 + src/compat/fs/directory.spec.ts | 69 + src/compat/fs/directory.ts | 186 ++ src/compat/fs/glob-internal.ts | 556 +++++ src/compat/fs/glob.spec.ts | 7 + src/compat/fs/glob.ts | 102 + src/compat/fs/index.ts | 597 ++++- src/compat/fs/io-validation.ts | 186 ++ src/compat/fs/io.ts | 321 +++ src/compat/fs/stream-errors.ts | 15 + src/compat/fs/stream-utils.ts | 55 + src/compat/fs/streams.ts | 1187 +++++++++ src/compat/fs/upstream-access.fixture.mjs | 354 +++ .../fs/upstream-chown-chmod.fixture.mjs | 594 +++++ .../upstream-cp-into-subdirectory.fixture.mjs | 56 + src/compat/fs/upstream-cp.fixture.mjs | 2149 +++++++++++++++++ src/compat/fs/upstream-directory.fixture.mjs | 920 +++++++ src/compat/fs/upstream-filehandle.fixture.mjs | 170 ++ src/compat/fs/upstream-glob.fixture.mjs | 357 +++ src/compat/fs/upstream-link.fixture.mjs | 468 ++++ src/compat/fs/upstream-misc.fixture.mjs | 240 ++ src/compat/fs/upstream-nodejs.fixture.mjs | 1615 +++++++++++++ src/compat/fs/upstream-readstream.fixture.mjs | 1057 ++++++++ .../upstream-readsync-byteoffset.fixture.mjs | 97 + src/compat/fs/upstream-stat.fixture.mjs | 451 ++++ src/compat/fs/upstream-utimes.fixture.mjs | 186 ++ .../fs/upstream-writestream.fixture.mjs | 516 ++++ src/compat/fs/writable.spec.ts | 7 +- src/compat/fs/writable.ts | 265 +- src/compat/process/directory.ts | 35 + src/compat/process/index.ts | 7 + src/compat/zlib/native-zlib.wasm | Bin vitest.config.ts | 23 + 39 files changed, 13308 insertions(+), 57 deletions(-) create mode 100644 src/compat/fs/LICENSE-3RD-PARTY.md create mode 100644 src/compat/fs/LICENSE-APACHE create mode 100644 src/compat/fs/directory-backend.ts create mode 100644 src/compat/fs/directory.spec.ts create mode 100644 src/compat/fs/directory.ts create mode 100644 src/compat/fs/glob-internal.ts create mode 100644 src/compat/fs/glob.spec.ts create mode 100644 src/compat/fs/glob.ts create mode 100644 src/compat/fs/io-validation.ts create mode 100644 src/compat/fs/io.ts create mode 100644 src/compat/fs/stream-errors.ts create mode 100644 src/compat/fs/stream-utils.ts create mode 100644 src/compat/fs/streams.ts create mode 100644 src/compat/fs/upstream-access.fixture.mjs create mode 100644 src/compat/fs/upstream-chown-chmod.fixture.mjs create mode 100644 src/compat/fs/upstream-cp-into-subdirectory.fixture.mjs create mode 100644 src/compat/fs/upstream-cp.fixture.mjs create mode 100644 src/compat/fs/upstream-directory.fixture.mjs create mode 100644 src/compat/fs/upstream-filehandle.fixture.mjs create mode 100644 src/compat/fs/upstream-glob.fixture.mjs create mode 100644 src/compat/fs/upstream-link.fixture.mjs create mode 100644 src/compat/fs/upstream-misc.fixture.mjs create mode 100644 src/compat/fs/upstream-nodejs.fixture.mjs create mode 100644 src/compat/fs/upstream-readstream.fixture.mjs create mode 100644 src/compat/fs/upstream-readsync-byteoffset.fixture.mjs create mode 100644 src/compat/fs/upstream-stat.fixture.mjs create mode 100644 src/compat/fs/upstream-utimes.fixture.mjs create mode 100644 src/compat/fs/upstream-writestream.fixture.mjs create mode 100644 src/compat/process/directory.ts mode change 100755 => 100644 src/compat/zlib/native-zlib.wasm diff --git a/demo/tests/experiments/node-compat/completion-report.mjs b/demo/tests/experiments/node-compat/completion-report.mjs index 4d8bfc3f..4953e204 100644 --- a/demo/tests/experiments/node-compat/completion-report.mjs +++ b/demo/tests/experiments/node-compat/completion-report.mjs @@ -35,7 +35,7 @@ const assessments = [ modules: ["fs", "fs/promises"], priority: 2, category: "implementation-gap", - next: "Compare the Workers virtual filesystem, descriptors, FileHandle, writes, permissions and watch behavior. Request-local memory-backed /tmp now supports writes, descriptors and FileHandle; permission no-ops, statfs and embedded-file copying are tested; embedded descriptors/FileHandles, links, immutable copies, cp options, millisecond timestamps and readonly metadata are also compared. Complete the remaining operation/option inventory, including devices, directory iteration, glob and stream fd options.", + next: "Compare the Workers virtual filesystem, descriptors, FileHandle, writes, permissions and watch behavior. Request-local memory-backed /tmp now supports writes, descriptors and FileHandle; permission no-ops, statfs and embedded-file copying are tested; embedded descriptors/FileHandles, links, immutable copies, cp options, millisecond timestamps and readonly metadata are also compared. Devices, directory iteration, glob, stream fd/custom-fs options, byte streams, URL normalization, FD reuse and request-local cwd now have upstream and comparative coverage. Complete the remaining per-operation/option inventory and all-platform acceptance; these cases do not certify the whole module.", evidence: [ "../../../../src/compat/fs/index.ts", "../../../../src/compat/fs/promises/index.ts", diff --git a/scripts/build-native-zlib.mjs b/scripts/build-native-zlib.mjs index 1137af5c..e08194be 100644 --- a/scripts/build-native-zlib.mjs +++ b/scripts/build-native-zlib.mjs @@ -1,5 +1,5 @@ import { execFileSync } from "node:child_process"; -import { readFileSync, writeFileSync } from "node:fs"; +import { readFileSync, writeFileSync, chmodSync } from "node:fs"; import { resolve, join } from "node:path"; import { createHash } from "node:crypto"; const sdk = resolve(process.env.CODEC_EMSDK ?? "artifacts/toolchains/emsdk"); @@ -30,6 +30,7 @@ execFileSync( ], { stdio: "inherit" }, ); +chmodSync(output, 0o644); const hash = (file) => createHash("sha256").update(readFileSync(file)).digest("hex"); writeFileSync( diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index a493934a..b1c8a0d9 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -22,7 +22,17 @@ The SDK starts a fresh /tmp volume for each local fetch dispatch. Handles opened inside that request retain their volume; embedded bundle assets stay read-only. Standalone imports outside SDK dispatch share a fallback volume. Copying embedded files to /tmp preserves immutable embedded bytes. Permission changes are validated no-ops and statfs returns the pinned Workers zero values. -Embedded files support read-only descriptors, FileHandles, streams and links; copies into /tmp are writable and independent. Numeric file timestamps use milliseconds; embedded timestamps remain unchanged. Watching files is unsupported in Workers too. These profiles do not certify every filesystem operation. +Embedded files support read-only descriptors, FileHandles, streams and links; copies into /tmp are writable and independent. Numeric file timestamps use milliseconds; embedded timestamps remain unchanged. Watching files is unsupported in Workers too. Directory iteration, recursive listings, glob patterns, device files, symlink copying, +FD reuse, byte streams and stream fd/custom-fs options follow the pinned Workers +profile. File sizes are capped at 128 MiB and paths at 4096 bytes/48 segments. +`process.cwd()` and `process.chdir()` use the request-local virtual directory; +streams retain the creating request's filesystem when external callbacks deliver +chunks. FileHandle byte streams use the owned Web Streams implementation, +including on WebViews without native BYOB support. These profiles do not certify +every filesystem operation. The vendored upstream fixtures in `src/compat/fs` +retain their license headers and run with `npx vitest run src/compat/fs/directory.spec.ts +src/compat/fs/glob.spec.ts src/compat/fs/writable.spec.ts`; the zlib integration fixture +also exercises a host stream crossing into the request-local filesystem. zlib 1.3.1, Brotli 1.1.0 and Zstd 1.5.7 use bundled, synchronously initialized WebAssembly. The profile covers parameters, synchronous/callback operations, incremental diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index e22b01c9..e8b34cd0 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,251 @@ export const eventCases = { + fsIoContracts: async function ({ fs, process }) { + const path = "/tmp/io-contract", + result = {}; + fs.writeFileSync(path, "abc"); + const fd = fs.openSync(path, "a+"); + fs.writeFileSync(fd, "def"); + result.full = fs.readFileSync(fd, "utf8"); + result.after = fs.readSync(fd, new Uint8Array(1)); + fs.closeSync(fd); + fs.closeSync(fd); + fs.closeSync(123456); + const callbackFd = await new Promise((resolve, reject) => + fs.open(path, fs.constants.O_TRUNC, (error, fd) => + error ? reject(error) : resolve(fd), + ), + ); + fs.closeSync(callbackFd); + const promiseHandle = await fs.promises.open(path, fs.constants.O_TRUNC); + await promiseHandle.close(); + result.readonlyTruncate = fs.readFileSync(path, "utf8"); + process.chdir("/tmp"); + try { + result.cwd = process.cwd(); + fs.writeFileSync("relative-io", "data"); + result.relative = fs.readFileSync("/tmp/relative-io", "utf8"); + } finally { + process.chdir("/bundle"); + } + result.errors = []; + for (const call of [ + () => fs.writeSync(1, "x", ""), + () => fs.writeSync(1, new Uint8Array(1), { offset: 2 }), + () => fs.ftruncateSync(1, -1), + () => fs.appendFileSync(path, []), + () => fs.copyFile(path, path, 123), + () => fs.readFile(path), + ]) { + try { + call(); + result.errors.push(null); + } catch (error) { + result.errors.push(error.code); + } + } + result.constants = [ + Object.getPrototypeOf(fs.constants) === null, + fs.constants.UV_FS_COPYFILE_EXCL, + ]; + return result; + }, + fsStreamsAndLinks: async function ({ fs }) { + const root = "/tmp/streams-links"; + fs.mkdirSync(root); + const path = root + "/data"; + fs.writeFileSync(path, "abcdef"); + fs.symlinkSync(path, root + "/link"); + fs.cpSync(root + "/link", root + "/copy"); + const result = { + link: fs.readlinkSync(root + "/copy"), + text: fs.readFileSync(root + "/copy", "utf8"), + }; + fs.symlinkSync(root + "/missing", root + "/broken"); + fs.cpSync(root + "/broken", root + "/broken-copy"); + result.broken = fs.realpathSync(root + "/broken-copy"); + const handle = await fs.promises.open(path, "r"); + const stream = handle.createReadStream({ + start: 1, + end: 3, + encoding: "utf8", + }); + let text = ""; + for await (const chunk of stream) text += chunk; + result.read = [ + text, + handle.fd === undefined, + stream.bytesRead, + stream.closed, + ]; + const output = fs.createWriteStream(root + "/output"); + const finished = new Promise((resolve, reject) => { + output.on("close", resolve); + output.on("error", reject); + }); + output.cork(); + output.write("one"); + output.write("two"); + output.uncork(); + output.end("three"); + await finished; + result.write = [ + fs.readFileSync(root + "/output", "utf8"), + output.bytesWritten, + output.closed, + ]; + const webHandle = await fs.promises.open(path, "r"); + let webText = ""; + for await (const chunk of webHandle.readableWebStream()) + webText += new TextDecoder().decode(chunk); + result.web = [webText, webHandle.fd === undefined]; + const aborted = await fs.promises.open(path, "r"); + const closed = new Promise((resolve) => aborted.once("close", resolve)); + const abortedStream = aborted.createReadStream({ + signal: AbortSignal.abort(), + }); + result.abort = await new Promise((resolve) => + abortedStream.once("error", (error) => resolve(error.name)), + ); + await closed; + result.closed = aborted.fd === undefined; + fs.writeFileSync(root + "/%64ata%3f", "encoded"); + result.normalized = fs.readFileSync(root + "/data%3F", "utf8"); + return result; + }, + fsDevicesAndBounds: async function ({ fs }) { + const attempt = (fn) => { + try { + return fn() ?? null; + } catch (error) { + return { name: error.name, code: error.code ?? null }; + } + }; + const result = { devices: [], limits: [], times: [] }; + for (const name of ["null", "zero", "full", "random"]) { + const path = "/dev/" + name, + fd = fs.openSync(path, "r+"), + buffer = new Uint8Array(4).fill(7); + const read = fs.readSync(fd, buffer, 0, 4, null), + stat = fs.fstatSync(fd); + result.devices.push({ + name, + read, + buffer: name === "random" ? "entropy" : Array.from(buffer), + write: attempt(() => fs.writeSync(fd, "abc")), + truncate: attempt(() => fs.ftruncateSync(fd, 0)), + stat: [ + stat.mode, + stat.dev, + stat.size, + stat.isCharacterDevice(), + stat.isFile(), + stat.mtimeMs, + ], + all: fs.readFileSync(path).length, + }); + fs.closeSync(fd); + } + const path = "/tmp/bounds", + fd = fs.openSync(path, "w+"); + result.times.push(fs.fstatSync(fd).mtimeMs); + fs.writeSync(fd, "x"); + result.times.push(fs.fstatSync(fd).mtimeMs); + fs.futimesSync(fd, 123, 456); + fs.writeSync(fd, "y"); + result.times.push(fs.fstatSync(fd).mtimeMs); + result.limits.push( + attempt(() => fs.writeSync(fd, new Uint8Array(1), 0, 1, 134217728)), + ); + fs.closeSync(fd); + result.limits.push( + attempt(() => fs.openSync("/tmp/" + "a".repeat(4097), "r")), + ); + result.limits.push(attempt(() => fs.openSync("/a".repeat(49), "r"))); + const blob = fs.openAsBlob(path, { type: "text/plain" }); + result.blob = [ + blob instanceof Blob, + blob.size, + blob.type, + await blob.text(), + ]; + return result; + }, + fsDirectoryNavigation: async function ({ fs }) { + const root = "/tmp/navigation"; + const created = fs.mkdirSync(root + "/a/nested", { recursive: true }); + fs.mkdirSync(root + "/a/nested"); + for (const name of ["a/one", "a/.hidden", "a/nested/two"]) + fs.writeFileSync(root + "/" + name, "data"); + const describe = (entry) => ({ + name: + typeof entry.name === "string" ? entry.name : Array.from(entry.name), + parent: entry.parentPath, + file: entry.isFile(), + dir: entry.isDirectory(), + link: entry.isSymbolicLink(), + instance: entry instanceof fs.Dirent, + }); + const result = { + created, + listing: fs.readdirSync(root, { recursive: true }), + entries: fs + .readdirSync(root, { recursive: true, withFileTypes: true }) + .map(describe), + patterns: [], + }; + for (const pattern of [ + "**", + "a/*", + "a/**", + "a/{one,nested/**}", + "a/+(one|nested)", + "**/.hidden", + ["a/one", "a/nested/*"], + ]) + result.patterns.push(fs.globSync(pattern, { cwd: root })); + result.exclude = fs.globSync("**", { + cwd: root, + exclude: ["**/nested/**"], + }); + result.typed = fs + .globSync("a/*", { cwd: root, withFileTypes: true }) + .map(describe); + result.callback = await new Promise((resolve, reject) => + fs.glob("a/**", { cwd: root }, (e, value) => + e ? reject(e) : resolve(value), + ), + ); + result.promise = []; + for await (const value of fs.promises.glob("a/**", { cwd: root })) + result.promise.push(value); + const dir = await fs.promises.opendir(root, { recursive: true }); + result.iterated = []; + for await (const entry of dir) result.iterated.push(describe(entry)); + result.afterIteration = dir.readSync(); + await dir.close(); + try { + dir.readSync(); + } catch (error) { + result.closed = error.code; + } + result.encoded = fs.readdirSync(root, "hex"); + result.bundle = fs + .readdirSync("/bundle/nested", { withFileTypes: true }) + .map(describe); + for (const options of [ + { encoding: null }, + { recursive: 1 }, + { withFileTypes: 1 }, + ]) { + try { + fs.readdirSync(root, options); + result.patterns.push(null); + } catch (error) { + result.patterns.push(error.code); + } + } + return result; + }, fsTimes: async function ({ fs }) { const readonly = "/bundle/readonly.txt", writable = "/tmp/time-file"; diff --git a/src/compat/fs/LICENSE-3RD-PARTY.md b/src/compat/fs/LICENSE-3RD-PARTY.md new file mode 100644 index 00000000..5af46473 --- /dev/null +++ b/src/compat/fs/LICENSE-3RD-PARTY.md @@ -0,0 +1,11 @@ +# Upstream notices + +This module ports Cloudflare workerd tag `v1.20261012.1`, revision +`5358e3d337336eaf696a48bc1563d808b10feba5`: + +- `src/node/_tls_common.ts` +- `src/node/internal/internal_tls_common.ts` + +Copyright Cloudflare, Inc.; Apache License 2.0. Node.js/Joyent MIT notices +are preserved in the source files. Imports, resource-management symbols and +host constructor references are adapted for the Capacitor runtime. diff --git a/src/compat/fs/LICENSE-APACHE b/src/compat/fs/LICENSE-APACHE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/src/compat/fs/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/src/compat/fs/directory-backend.ts b/src/compat/fs/directory-backend.ts new file mode 100644 index 00000000..6edd0dc0 --- /dev/null +++ b/src/compat/fs/directory-backend.ts @@ -0,0 +1,65 @@ +import { temporary, embeddedStat, pathOf } from "./writable.js"; +export interface DirEntryHandle { + name: string; + parentPath: string; + type: number; +} +export function readDirectory( + path: unknown, + recursive: boolean, +): DirEntryHandle[] { + const normalized = pathOf(path); + const list = (directory: string): DirEntryHandle[] => { + if (directory === "/") + return ["bundle", "tmp", "dev"].map((name) => ({ + name, + parentPath: "/", + type: 2, + })); + if (directory === "/dev") + return ["null", "zero", "full", "random"].map((name) => ({ + name, + parentPath: "/dev", + type: 6, + })); + embeddedStat(directory); + return temporary + .readdirSync(directory, { withFileTypes: true, encoding: "buffer" }) + .map((entry: any) => ({ + name: String(entry.name), + parentPath: directory, + type: entry.isDirectory() + ? 2 + : entry.isFile() + ? 1 + : entry.isSymbolicLink() + ? 3 + : entry.isFIFO() + ? 4 + : entry.isSocket() + ? 5 + : entry.isCharacterDevice() + ? 6 + : entry.isBlockDevice() + ? 7 + : 0, + })); + }; + const entries: DirEntryHandle[] = []; + const visit = (directory: string, prefix: string) => { + for (const entry of list(directory)) { + const name = prefix + entry.name; + entries.push({ ...entry, name }); + if (recursive && entry.type === 2) + visit( + (directory === "/" ? "" : directory) + "/" + entry.name, + name + "/", + ); + } + }; + visit( + normalized, + recursive && normalized !== "/" ? normalized.slice(1) + "/" : "", + ); + return entries; +} diff --git a/src/compat/fs/directory.spec.ts b/src/compat/fs/directory.spec.ts new file mode 100644 index 00000000..0541b4f1 --- /dev/null +++ b/src/compat/fs/directory.spec.ts @@ -0,0 +1,69 @@ +import { test, beforeEach } from "vitest"; +import { clearVirtualFiles, registerVirtualFiles } from "./index.js"; +beforeEach(() => { + clearVirtualFiles(); + registerVirtualFiles({ "/bundle/worker": "fixture module" }); +}); +import { runWithTemporaryFiles } from "./writable.js"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as cases from "./upstream-directory.fixture.mjs"; +for (const [name, fixture] of Object.entries(cases)) + test(`pinned Workers directories: ${name}`, () => + runWithTemporaryFiles(() => (fixture as { test: () => unknown }).test())); + +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as miscellaneous from "./upstream-misc.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as fileHandles from "./upstream-filehandle.fixture.mjs"; +for (const [group, cases] of Object.entries({ miscellaneous, fileHandles })) + for (const [name, fixture] of Object.entries(cases)) + test(`pinned Workers ${group}: ${name}`, () => + runWithTemporaryFiles(() => (fixture as { test: () => unknown }).test())); + +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as access from "./upstream-access.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as chown_chmod from "./upstream-chown-chmod.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as link from "./upstream-link.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as stat from "./upstream-stat.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as utimes from "./upstream-utimes.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as readsync_byteoffset from "./upstream-readsync-byteoffset.fixture.mjs"; +for (const [group, cases] of Object.entries({ + access, + chown_chmod, + link, + stat, + utimes, + readsync_byteoffset, +})) + for (const [name, fixture] of Object.entries(cases)) + test(`pinned Workers ${group}: ${name}`, () => + runWithTemporaryFiles(() => (fixture as { test: () => unknown }).test())); + +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as cp from "./upstream-cp.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as cp_into_subdirectory from "./upstream-cp-into-subdirectory.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as readstream from "./upstream-readstream.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as writestream from "./upstream-writestream.fixture.mjs"; +for (const [group, cases] of Object.entries({ + cp, + cp_into_subdirectory, + readstream, + writestream, +})) + for (const [name, fixture] of Object.entries(cases)) + test(`pinned Workers ${group}: ${name}`, () => + runWithTemporaryFiles(() => (fixture as { test: () => unknown }).test())); + +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as nodejs from "./upstream-nodejs.fixture.mjs"; +for (const [name, fixture] of Object.entries(nodejs)) + test(`pinned Workers nodejs: ${name}`, () => + runWithTemporaryFiles(() => (fixture as { test: () => unknown }).test())); diff --git a/src/compat/fs/directory.ts b/src/compat/fs/directory.ts new file mode 100644 index 00000000..0c84462c --- /dev/null +++ b/src/compat/fs/directory.ts @@ -0,0 +1,186 @@ +// Copyright (c) 2026 Cloudflare, Inc. Licensed under Apache-2.0. +// Ported from workerd@5358e3d337336eaf696a48bc1563d808b10feba5 internal_fs.ts. +import { Buffer } from "node:buffer"; +import { + validateFunction, + validateObject, + validateString, +} from "../net/validators.js"; +import { ERR_MISSING_ARGS } from "../net/errors.js"; +import type { DirEntryHandle } from "./directory-backend.js"; +type FilePath = string | Buffer | URL; +type ValidEncoding = BufferEncoding | "buffer"; +const UV_DIRENT_FILE = 1, + UV_DIRENT_DIR = 2, + UV_DIRENT_LINK = 3, + UV_DIRENT_FIFO = 4, + UV_DIRENT_SOCKET = 5, + UV_DIRENT_CHAR = 6, + UV_DIRENT_BLOCK = 7; +class ERR_DIR_CLOSED extends Error { + code = "ERR_DIR_CLOSED"; + constructor() { + super("Directory handle was closed"); + } +} +const kType = Symbol("type"); + +export class Dirent { + name: string | Buffer; + parentPath: string | Buffer; + private [kType]: number; + + constructor(name: string | Buffer, type: number, path: string | Buffer) { + this.name = name; + this.parentPath = path; + this[kType] = type; + } + + isDirectory(): boolean { + return this[kType] === UV_DIRENT_DIR; + } + + isFile(): boolean { + return this[kType] === UV_DIRENT_FILE; + } + + isBlockDevice(): boolean { + return this[kType] === UV_DIRENT_BLOCK; + } + + isCharacterDevice(): boolean { + return this[kType] === UV_DIRENT_CHAR; + } + + isSymbolicLink(): boolean { + return this[kType] === UV_DIRENT_LINK; + } + + isFIFO(): boolean { + return this[kType] === UV_DIRENT_FIFO; + } + + isSocket(): boolean { + return this[kType] === UV_DIRENT_SOCKET; + } +} + +export interface DirOptions { + encoding?: ValidEncoding | undefined; +} + +export type DirentReadCallback = ( + err: Error | null, + dirent: Dirent | null, +) => void; + +export class Dir { + // Unlike our Node.js counterpart, we do not not use an underlying + // native handle for the directory. Instead we use a simple array + // of DirEntryHandle objects just like the one used in the readdir + // API. + #handle: DirEntryHandle[] | undefined; + #encoding: ValidEncoding | undefined; + #path: FilePath; + + constructor( + handle: DirEntryHandle[] | undefined, + path: FilePath, + options: DirOptions, + ) { + if (handle == null) { + throw new ERR_MISSING_ARGS("handle"); + } + this.#handle = handle; + this.#path = path; + + validateObject(options, "options"); + const { encoding = "utf8" } = options; + validateString(encoding, "options.encoding"); + this.#encoding = encoding as BufferEncoding; + } + + get path(): FilePath { + return this.#path; + } + + read(): Promise; + read(callback: DirentReadCallback): void; + read(callback?: DirentReadCallback): Promise | undefined { + if (typeof callback === "function") { + validateFunction(callback, "callback"); + try { + const ent = this.readSync(); + queueMicrotask(() => { + callback(null, ent); + }); + } catch (err) { + queueMicrotask(() => { + callback(err as Error, null); + }); + } + return; + } + + try { + const ent = this.readSync(); + return Promise.resolve(ent); + } catch (err: unknown) { + return Promise.reject(err as Error); + } + } + + readSync(): Dirent | null { + if (this.#handle === undefined) throw new ERR_DIR_CLOSED(); + const ent = this.#handle.shift(); + if (ent == null) return null; + const buf = Buffer.from(ent.name); + if (this.#encoding === "buffer") { + return new Dirent(buf, ent.type, ent.parentPath); + } + return new Dirent( + buf.toString(this.#encoding != null ? this.#encoding : undefined), + ent.type, + ent.parentPath, + ); + } + + close(callback?: (err: unknown) => void): Promise | undefined { + this.closeSync(); + if (typeof callback === "function") { + validateFunction(callback, "callback"); + queueMicrotask(() => { + callback(null); + }); + return undefined; + } + return Promise.resolve(); + } + + closeSync(): void { + if (this.#handle === undefined) { + throw new ERR_DIR_CLOSED(); + } + this.#handle = undefined; + } + + async *entries(): AsyncGenerator { + for (;;) { + const ent = await this.read(); + if (ent == null) return; + yield ent; + } + } + + [Symbol.asyncIterator](): AsyncGenerator { + return this.entries(); + } + + async [Symbol.asyncDispose](): Promise { + await this.close(); + } + + [Symbol.dispose](): void { + this.closeSync(); + } +} diff --git a/src/compat/fs/glob-internal.ts b/src/compat/fs/glob-internal.ts new file mode 100644 index 00000000..37d564c2 --- /dev/null +++ b/src/compat/fs/glob-internal.ts @@ -0,0 +1,556 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +// Ported from workerd@5358e3d337336eaf696a48bc1563d808b10feba5 internal_fs_glob.ts. +// Adapted filesystem boundary; pattern matching and traversal retain the pinned upstream implementation. +import { readDirectory } from "./directory-backend.js"; +import type { DirEntryHandle } from "./directory-backend.js"; +// UV_DIRENT_DIR constant from internal_fs_constants +const UV_DIRENT_DIR = 2; + +// Maximum recursion depth to prevent stack overflow on deeply nested VFS +const MAX_WALK_DEPTH = 256; + +// ============================================================================ +// Brace Expansion +// ============================================================================ + +// Splits a string by top-level occurrences of a separator character, +// respecting nested braces/parens and backslash escapes. +function splitTopLevel(str: string, sep: string): string[] { + const parts: string[] = []; + let depth = 0; + let current = ""; + + for (let i = 0; i < str.length; i++) { + const c = str.charAt(i); + if (c === "\\" && i + 1 < str.length) { + current += c + str.charAt(i + 1); + i++; + } else if (c === "{" || c === "(") { + depth++; + current += c; + } else if (c === "}" || c === ")") { + depth--; + current += c; + } else if (c === sep && depth === 0) { + parts.push(current); + current = ""; + } else { + current += c; + } + } + + parts.push(current); + return parts; +} + +// Expands brace expressions in a glob pattern into multiple patterns. +export function expandBraces(pattern: string): string[] { + let depth = 0; + let braceStart = -1; + + for (let i = 0; i < pattern.length; i++) { + const c = pattern[i]; + if (c === "\\" && i + 1 < pattern.length) { + i++; + continue; + } + if (c === "{") { + if (depth === 0) braceStart = i; + depth++; + } else if (c === "}") { + depth--; + if (depth === 0 && braceStart !== -1) { + const prefix = pattern.slice(0, braceStart); + const body = pattern.slice(braceStart + 1, i); + const suffix = pattern.slice(i + 1); + + const alternatives = splitTopLevel(body, ","); + + if (alternatives.length === 1) { + return [pattern]; + } + + const results: string[] = []; + for (const alt of alternatives) { + for (const expanded of expandBraces(prefix + alt + suffix)) { + results.push(expanded); + } + } + return results; + } + } + } + + return [pattern]; +} + +// ============================================================================ +// Pattern Normalization +// ============================================================================ + +export function normalizePattern(pattern: string): string { + // Strip leading ./ + if (pattern.startsWith("./")) { + pattern = pattern.slice(2); + } + // Collapse multiple slashes to single + pattern = pattern.replace(/\/\/+/g, "/"); + // Strip trailing slash + if (pattern.endsWith("/") && pattern.length > 1) { + pattern = pattern.slice(0, -1); + } + return pattern; +} + +// ============================================================================ +// Segment-Level Regex (with extglob support) +// ============================================================================ + +// Converts a single path segment pattern to a RegExp. +// Supports: *, ?, [...], [!...], @(...), *(...), +(...), ?(...), !(...) +export function segmentToRegex(segment: string): RegExp { + const regex = segmentToRegexStr(segment); + return new RegExp("^" + regex + "$"); +} + +function segmentToRegexStr(segment: string): string { + let regex = ""; + let i = 0; + let inCharClass = false; + + while (i < segment.length) { + const c = segment[i] ?? ""; + + // Handle escape sequences + if (c === "\\" && i + 1 < segment.length) { + regex += "\\" + escapeRegexChar(segment[i + 1] ?? ""); + i += 2; + continue; + } + + // Inside character classes, most chars are literal + if (inCharClass) { + if (c === "]") { + regex += "]"; + inCharClass = false; + } else { + regex += c; + } + i++; + continue; + } + + // Check for extglob: @(...), *(...), +(...), ?(...), !(...) + if ( + (c === "@" || c === "*" || c === "+" || c === "?" || c === "!") && + i + 1 < segment.length && + segment[i + 1] === "(" + ) { + const closeIdx = findMatchingParen(segment, i + 1); + if (closeIdx !== -1) { + const inner = segment.slice(i + 2, closeIdx); + // Convert pipe-separated alternatives, each may contain glob chars + const alts = splitTopLevel(inner, "|"); + const altRegexes = alts.map((a) => segmentToRegexStr(a)); + const group = altRegexes.join("|"); + + switch (c) { + case "@": // exactly one + regex += "(?:" + group + ")"; + break; + case "*": // zero or more + regex += "(?:" + group + ")*"; + break; + case "+": // one or more + regex += "(?:" + group + ")+"; + break; + case "?": // zero or one + regex += "(?:" + group + ")?"; + break; + case "!": // none of (negative lookahead) + regex += "(?!(?:" + group + ")$)[^/]*"; + break; + } + + i = closeIdx + 1; + continue; + } + } + + switch (c) { + case "[": + inCharClass = true; + regex += "["; + if (i + 1 < segment.length && segment[i + 1] === "!") { + regex += "^"; + i++; + } + break; + + case "*": + regex += "[^/]*"; + break; + + case "?": + regex += "[^/]"; + break; + + case ".": + case "+": + case "^": + case "$": + case "|": + case "(": + case ")": + case "{": + case "}": + regex += "\\" + c; + break; + + default: + regex += c; + break; + } + i++; + } + + return regex; +} + +function findMatchingParen(str: string, openIdx: number): number { + let depth = 0; + for (let i = openIdx; i < str.length; i++) { + if (str[i] === "\\" && i + 1 < str.length) { + i++; + continue; + } + if (str[i] === "(") depth++; + else if (str[i] === ")") { + depth--; + if (depth === 0) return i; + } + } + return -1; +} + +function escapeRegexChar(c: string): string { + if (".+*?^$|()[]{}\\".includes(c)) { + return "\\" + c; + } + return c; +} + +// ============================================================================ +// Full-Path Regex (used for exclude pattern matching) +// ============================================================================ + +// Converts a full glob pattern (with /) to a single RegExp for exclude matching. +export function globToRegex(pattern: string): RegExp { + const normalized = normalizePattern(pattern); + const segments = normalized.split("/").filter((s) => s !== ""); + const parts: string[] = []; + + for (const seg of segments) { + if (seg === "**") { + // ** matches zero or more path segments + // We handle this by inserting a special marker + parts.push("**"); + } else if (seg === ".") { + // skip + } else if (seg === "..") { + parts.pop(); + } else { + parts.push(segmentToRegexStr(seg)); + } + } + + // Now build regex from parts, handling ** markers + let regex = ""; + for (let i = 0; i < parts.length; i++) { + const part = parts[i] ?? ""; + const prevPart = i > 0 ? (parts[i - 1] ?? "") : ""; + if (part === "**") { + if (parts.length === 1) { + // ** alone: match everything + regex = ".*"; + } else if (i === 0) { + // ** at start: match zero or more leading segments + regex += "(?:.*\\/)?"; + } else if (i === parts.length - 1) { + // ** at end: match zero or more trailing segments + regex += "(?:\\/.*)?"; + } else { + // ** in middle: match zero or more middle segments + regex += "(?:\\/[^/]+)*(?:\\/)?"; + } + } else { + if (i > 0 && prevPart !== "**") { + regex += "\\/"; + } + regex += part; + } + } + + return new RegExp("^" + regex + "$"); +} + +// ============================================================================ +// Exclude Compiler +// ============================================================================ + +// Compiles an array of glob patterns into an exclude function. +// Only accepts string arrays — user-provided functions are handled +// separately in globSync to preserve their (string | Dirent) signature. +export function compileExcludePatterns( + exclude: readonly string[], +): (path: string) => boolean { + const regexes: RegExp[] = []; + for (const pat of exclude) { + for (const expanded of expandBraces(pat)) { + regexes.push(globToRegex(expanded)); + } + } + + return (path: string): boolean => { + for (const re of regexes) { + if (re.test(path)) return true; + } + return false; + }; +} + +// ============================================================================ +// Directory Entry Cache +// ============================================================================ + +type EntryCache = Map; + +function getDirectoryEntries( + absPath: string, + cache: EntryCache, +): DirEntryHandle[] { + const cached = cache.get(absPath); + if (cached !== undefined) return cached; + + try { + const entries = readDirectory(absPath, false); + cache.set(absPath, entries); + return entries; + } catch { + const empty: DirEntryHandle[] = []; + cache.set(absPath, empty); + return empty; + } +} + +function isDirectory(entry: DirEntryHandle): boolean { + return entry.type === UV_DIRENT_DIR; +} + +// ============================================================================ +// Pattern-Driven Directory Walk +// ============================================================================ + +export interface GlobResult { + relativePath: string; + handle: DirEntryHandle | null; +} + +// Collapses consecutive '**' segments to prevent exponential blowup. +export function collapseGlobstars(segments: string[]): string[] { + const result: string[] = []; + for (const seg of segments) { + if (seg === "**" && result[result.length - 1] === "**") continue; + result.push(seg); + } + return result; +} + +// Precompiles segment regexes for all non-special segments. +export function precompileSegmentRegexes( + segments: string[], +): (RegExp | null)[] { + return segments.map((seg) => { + if (seg === "**" || seg === "." || seg === "..") return null; + return segmentToRegex(seg); + }); +} + +export function walkGlob( + cwd: string, + segments: string[], + segIdx: number, + currentAbsPath: string, + relativePath: string, + results: Map, + cache: EntryCache, + segmentRegexes: (RegExp | null)[], + visitedGlobstar?: Set, + depth: number = 0, +): void { + // Guard against excessive recursion depth + if (depth >= MAX_WALK_DEPTH) return; + + // All segments consumed: this path is a match + if (segIdx >= segments.length) { + if (!results.has(relativePath)) { + // Resolve handle for the matched path by reading the parent directory + const lastSlash = currentAbsPath.lastIndexOf("/"); + const parentDir = + lastSlash > 0 ? currentAbsPath.slice(0, lastSlash) : currentAbsPath; + const basename = relativePath.split("/").pop() ?? ""; + const entries = getDirectoryEntries(parentDir, cache); + const handle = entries.find((e) => e.name === basename) ?? null; + results.set(relativePath, { relativePath, handle }); + } + return; + } + + const seg = segments[segIdx] ?? ""; + + // Handle '.' — stay in current directory + if (seg === ".") { + walkGlob( + cwd, + segments, + segIdx + 1, + currentAbsPath, + relativePath, + results, + cache, + segmentRegexes, + visitedGlobstar, + depth, + ); + return; + } + + // Handle '..' — go up one directory (but don't escape cwd) + if (seg === "..") { + if (currentAbsPath === cwd || !currentAbsPath.startsWith(cwd + "/")) { + return; + } + + const lastSlash = currentAbsPath.lastIndexOf("/"); + const newAbs = lastSlash > 0 ? currentAbsPath.slice(0, lastSlash) : "/"; + + const relParts = relativePath.split("/").filter(Boolean); + relParts.pop(); + const newRel = relParts.join("/"); + + walkGlob( + cwd, + segments, + segIdx + 1, + newAbs, + newRel, + results, + cache, + segmentRegexes, + visitedGlobstar, + depth, + ); + return; + } + + // Handle '**' — match zero or more directory levels + if (seg === "**") { + const gsKey = `${currentAbsPath}:${String(segIdx)}`; + if (visitedGlobstar === undefined) { + visitedGlobstar = new Set(); + } + if (visitedGlobstar.has(gsKey)) return; + visitedGlobstar.add(gsKey); + + // Zero levels: advance to next segment at current path + walkGlob( + cwd, + segments, + segIdx + 1, + currentAbsPath, + relativePath, + results, + cache, + segmentRegexes, + visitedGlobstar, + depth, + ); + + // One or more levels: enumerate children + const entries = getDirectoryEntries(currentAbsPath, cache); + for (const entry of entries) { + const childAbs = currentAbsPath + "/" + entry.name; + const childRel = relativePath + ? relativePath + "/" + entry.name + : entry.name; + + // Try matching next segment against this child + walkGlob( + cwd, + segments, + segIdx + 1, + childAbs, + childRel, + results, + cache, + segmentRegexes, + visitedGlobstar, + depth + 1, + ); + + // If directory, recurse ** deeper + if (isDirectory(entry)) { + walkGlob( + cwd, + segments, + segIdx, + childAbs, + childRel, + results, + cache, + segmentRegexes, + visitedGlobstar, + depth + 1, + ); + } + } + return; + } + + // Regular segment: match against precompiled regex + const segRegex = segmentRegexes[segIdx] ?? segmentToRegex(seg); + const entries = getDirectoryEntries(currentAbsPath, cache); + + for (const entry of entries) { + if (segRegex.test(entry.name)) { + const childAbs = currentAbsPath + "/" + entry.name; + const childRel = relativePath + ? relativePath + "/" + entry.name + : entry.name; + + if (segIdx + 1 >= segments.length) { + // This is the last segment — record match + if (!results.has(childRel)) { + results.set(childRel, { relativePath: childRel, handle: entry }); + } + } else { + // More segments to match — recurse + walkGlob( + cwd, + segments, + segIdx + 1, + childAbs, + childRel, + results, + cache, + segmentRegexes, + visitedGlobstar, + depth + 1, + ); + } + } + } +} diff --git a/src/compat/fs/glob.spec.ts b/src/compat/fs/glob.spec.ts new file mode 100644 index 00000000..cc584d22 --- /dev/null +++ b/src/compat/fs/glob.spec.ts @@ -0,0 +1,7 @@ +import { test } from "vitest"; +import { runWithTemporaryFiles } from "./writable.js"; +// @ts-expect-error Upstream JavaScript fixture has no declaration file. +import * as cases from "./upstream-glob.fixture.mjs"; +for (const [name, fixture] of Object.entries(cases)) + test(`pinned Workers glob: ${name}`, () => + runWithTemporaryFiles(() => (fixture as { test: () => unknown }).test())); diff --git a/src/compat/fs/glob.ts b/src/compat/fs/glob.ts new file mode 100644 index 00000000..6840dad1 --- /dev/null +++ b/src/compat/fs/glob.ts @@ -0,0 +1,102 @@ +import { currentDirectory } from "./writable.js"; +// Copyright (c) 2026 Cloudflare, Inc. Licensed under Apache-2.0. +// Ported from workerd@5358e3d337336eaf696a48bc1563d808b10feba5 internal_fs_sync.ts. +import { validateString, validateObject } from "../net/validators.js"; +import { Dirent } from "./directory.js"; +import type { DirEntryHandle } from "./directory-backend.js"; +import { + expandBraces, + normalizePattern, + walkGlob, + compileExcludePatterns, + collapseGlobstars, + precompileSegmentRegexes, + type GlobResult, +} from "./glob-internal.js"; +type GlobOptions = { + cwd?: string | URL; + withFileTypes?: boolean; + exclude?: string[] | ((path: any) => boolean); +}; +type GlobOptionsWithFileTypes = GlobOptions; +type GlobOptionsWithoutFileTypes = GlobOptions; +export function globSync( + pattern: string | readonly string[], + options: + | GlobOptions + | GlobOptionsWithFileTypes + | GlobOptionsWithoutFileTypes = {}, +): string[] | Dirent[] { + // Normalize pattern to array + const patterns: string[] = + typeof pattern === "string" ? [pattern] : [...pattern]; + for (const p of patterns) { + validateString(p, "pattern"); + } + + if (typeof options !== "object" || options === null) { + validateObject(options, "options"); + } + + const cwdOption = (options as GlobOptions).cwd; + const cwd: string = + cwdOption instanceof URL + ? cwdOption.pathname + : ((cwdOption as string | undefined) ?? currentDirectory()); + validateString(cwd, "options.cwd"); + + const withFileTypes: boolean = + (options as GlobOptionsWithFileTypes).withFileTypes ?? false; + + // Exclude can be a user function or an array of glob patterns. + // Keep them as separate variables to preserve proper type signatures. + const excludeOption = (options as GlobOptions).exclude; + let excludeUserFn: ((path: string | Dirent) => boolean) | undefined; + let excludePatternFn: ((path: string) => boolean) | undefined; + if (typeof excludeOption === "function") { + excludeUserFn = excludeOption as (path: string | Dirent) => boolean; + } else if (Array.isArray(excludeOption)) { + excludePatternFn = compileExcludePatterns(excludeOption as string[]); + } + + // Pattern-driven directory walk + const results = new Map(); + const dirCache = new Map(); + + for (const p of patterns) { + for (const expanded of expandBraces(p)) { + const normalized = normalizePattern(expanded); + const segments = collapseGlobstars( + normalized.split("/").filter((s) => s !== ""), + ); + if (segments.length === 0) continue; + const segmentRegexes = precompileSegmentRegexes(segments); + walkGlob(cwd, segments, 0, cwd, "", results, dirCache, segmentRegexes); + } + } + + // Build final results, applying exclude filter + const stringResults: string[] = []; + const direntResults: Dirent[] = []; + + for (const [relPath, entry] of results) { + if (!relPath) continue; // skip empty paths + + if (excludePatternFn && excludePatternFn(relPath)) continue; + + if (withFileTypes) { + const parts = relPath.split("/"); + const name = parts.pop() ?? ""; + const parentPath = cwd + (parts.length ? "/" + parts.join("/") : ""); + const type = entry.handle?.type ?? 0; + const dirent = new Dirent(name, type, parentPath); + if (excludeUserFn && excludeUserFn(dirent)) continue; + direntResults.push(dirent); + } else { + if (excludeUserFn && excludeUserFn(relPath)) continue; + stringResults.push(relPath); + } + } + + return withFileTypes ? direntResults : stringResults; +} diff --git a/src/compat/fs/index.ts b/src/compat/fs/index.ts index 73d36704..3cb5df60 100644 --- a/src/compat/fs/index.ts +++ b/src/compat/fs/index.ts @@ -1,3 +1,10 @@ +import { ReadableStream as WorkersReadableStream } from "../stream-web/runtime.js"; +import { installIo } from "./io.js"; +import * as fileStreams from "./streams.js"; +import { registerFileHandle } from "./stream-utils.js"; +import { Dir as WorkersDir, Dirent as WorkersDirent } from "./directory.js"; +import { readDirectory } from "./directory-backend.js"; +import { globSync as workersGlob } from "./glob.js"; import { fileMode } from "./mode.js"; import { validateInteger } from "../crypto/validation.js"; import * as embedded from "./embedded.js"; @@ -86,7 +93,7 @@ for (const name of [ "access", ]) { const old = (embedded.promises as any)[name]; - promises[name] = (...args: any[]) => + promises[name] = async (...args: any[]) => temporaryPath(args[0]) ? temporary.promises[name](...args) : old(...args); } // Keep all writes inside the memory volume, including FileHandle operations. @@ -160,7 +167,9 @@ function installOperation( function temporaryStats(stat: any, readonly = false) { if (stat == null) return stat; const bigint = typeof stat.mode === "bigint"; - const mode = (Number(stat.mode) & ~0o777) | (readonly ? 0o444 : 0o666); + const mode = + (Number(stat.mode) & ~0o777) | + (readonly || stat.isSymbolicLink() ? 0o444 : 0o666); stat.mode = bigint ? BigInt(mode) : mode; for (const name of [ "dev", @@ -173,6 +182,9 @@ function temporaryStats(stat: any, readonly = false) { "atimeMs", ]) stat[name] = bigint ? 0n : 0; + stat.dev = bigint + ? BigInt(stat.isCharacterDevice()) + : Number(stat.isCharacterDevice()); stat.nlink = bigint ? 1n : 1; stat.atime = new Date(0); stat.ctime = stat.mtime; @@ -228,39 +240,69 @@ api.access = (path: unknown, mode: unknown, callback?: any) => { }; promises.access = async (path: unknown, mode?: unknown) => checkAccess(accessArgs(path, mode)); +function statOptions(options: any = {}) { + validateObject(options, "options"); + validateBoolean(options.bigint ?? false, "options.bigint"); + validateBoolean(options.throwIfNoEntry ?? true, "options.throwIfNoEntry"); + return options; +} for (const name of ["stat", "lstat"]) - installOperation(name, (path: any, options?: any) => - temporaryPath(path) + installOperation(name, (path: any, options: any = {}) => { + statOptions(options); + return temporaryPath(path) ? temporaryStats( temporary[name + "Sync"](path, options), readonlyFile(path, name !== "lstat"), ) - : temporaryStats(embeddedStat(path, options), true), - ); + : temporaryStats(embeddedStat(path, options), true); + }); installOperation( "fstat", - (...args: any[]) => - temporaryStats(temporary.fstatSync(...args), readonlyFile(args[0])), + (fd: any, options: any = {}) => { + statOptions(options); + validateInteger(fd, "fd", 0, 2147483647); + return temporaryStats(temporary.fstatSync(fd, options), readonlyFile(fd)); + }, false, ); for (const name of ["chmod", "lchmod", "fchmod", "chown", "lchown", "fchown"]) { - installOperation( - name, - (path: any, value: any, gid: any) => { - if (name.endsWith("chmod")) fileMode(value); - else { - validateInteger(value, "uid", -1, 0xffffffff); - validateInteger(gid, "gid", -1, 0xffffffff); - } - if (name.startsWith("f")) { - validateInteger(path, "fd", 0, 2147483647); - temporary.fstatSync(path); - } else if (name.startsWith("l") && temporaryPath(path)) - temporary.lstatSync(path); - else api.statSync(path); - }, - !name.startsWith("f"), - ); + const count = name.endsWith("chmod") ? 2 : 3; + const prepare = (args: any[]) => { + const [path, value, gid] = args; + if (name.endsWith("chmod")) fileMode(value); + else { + validateInteger(value, "uid", -1, 0xffffffff); + validateInteger(gid, "gid", -1, 0xffffffff); + } + if (typeof path === "number" || name.startsWith("f")) + validateInteger(path, "fd", 0, 2147483647); + else pathOf(path); + return path; + }; + const operation = (path: any) => { + try { + if (typeof path === "number") temporary.fstatSync(path); + else api[name.startsWith("l") ? "lstatSync" : "statSync"](path); + } catch (error: any) { + if (error.code === "ENOENT") error.syscall = name; + throw error; + } + }; + api[name + "Sync"] = (...args: any[]) => operation(prepare(args)); + api[name] = (...args: any[]) => { + const path = prepare(args), + callback = args[count]; + validateFunction(callback, "callback"); + let error: any = null; + try { + operation(path); + } catch (failure) { + error = failure; + } + queueMicrotask(() => callback(error)); + }; + if (!name.startsWith("f")) + promises[name] = async (...args: any[]) => operation(prepare(args)); } installOperation("statfs", (path: any, options: any = {}) => { pathOf(path); @@ -281,15 +323,22 @@ installOperation("statfs", (path: any, options: any = {}) => { // Workers interprets numeric timestamps as milliseconds and does not change // metadata belonging to embedded files. Convert before the memfs boundary. function fileTime(value: any): Date { - if (value instanceof Date) return value; - if (typeof value === "number" || typeof value === "string") - return new Date(value); - if (typeof value === "bigint") return new Date(Number(value)); - throw new ERR_INVALID_ARG_TYPE( - "time", - ["string", "number", "bigint", "Date"], - value, - ); + let date: Date; + if (value instanceof Date) date = value; + else if (typeof value === "number" || typeof value === "string") + date = new Date(value); + else if (typeof value === "bigint") date = new Date(Number(value)); + else + throw new ERR_INVALID_ARG_TYPE( + "time", + ["string", "number", "bigint", "Date"], + value, + ); + if (!Number.isFinite(date.getTime())) + throw new TypeError( + "The value cannot be converted because it is not an integer.", + ); + return date; } for (const name of ["utimes", "lutimes", "futimes"]) { installOperation( @@ -312,6 +361,69 @@ promises.open = async (...args: any[]) => { fileMode(args[2], 0o666); args[2] = 0o666; const handle = await openPromise(...args); + const closeHandle = handle.close.bind(handle); + handle.close = () => { + if (handle.fd === -1) { + handle.emit("close"); + return Promise.resolve(); + } + return closeHandle(); + }; + const publicHandle = new Proxy(handle, { + get(target, key) { + if (key === "fd") return target.fd === -1 ? undefined : target.fd; + const value = Reflect.get(target, key, target); + return typeof value === "function" && + key !== "constructor" && + !Object.hasOwn(target, key) + ? value.bind(target) + : value; + }, + }); + registerFileHandle(handle); + registerFileHandle(publicHandle); + handle.readableWebStream = (options: any = {}) => { + if (handle.fd === -1) + throw Object.assign(new Error("bad file descriptor"), { code: "EBADF" }); + validateObject(options, "options"); + const { autoClose = true } = options; + validateBoolean(autoClose, "options.autoClose"); + let controller: ReadableByteStreamController, + ended = false; + const finish = () => { + if (!ended) { + ended = true; + controller.close(); + } + }; + const stream = new WorkersReadableStream({ + type: "bytes", + autoAllocateChunkSize: 16384, + start(c) { + controller = c; + }, + async pull(c) { + const req = c.byobRequest!; + const view = req.view!; + const { bytesRead } = await handle.read(view, 0, view.byteLength, null); + if (bytesRead === 0) { + finish(); + if (autoClose) await handle.close(); + } + req.respond(bytesRead); + }, + async cancel() { + ended = true; + if (autoClose) await handle.close(); + }, + }); + handle.once("close", finish); + return stream; + }; + handle.createReadStream = (options: any = {}) => + fileStreams.createReadStream("", { ...options, fd: handle }); + handle.createWriteStream = (options: any = {}) => + fileStreams.createWriteStream("", { ...options, fd: handle }); const stat = handle.stat.bind(handle); const isReadonly = readonlyFile(handle.fd); const utimes = handle.utimes.bind(handle); @@ -332,7 +444,7 @@ promises.open = async (...args: any[]) => { validateInteger(gid, "gid", -1, 0xffffffff); await stat(); }; - return handle; + return publicHandle; }; // Copy an embedded bundle file into request-local /tmp without granting any // write access to its source volume. @@ -353,7 +465,68 @@ installOperation("copyFile", (source: any, destination: any, mode = 0) => { throw Object.assign(new Error("File already exists"), { code: "EEXIST" }); temporary.writeFileSync(destination, embedded.readFileSync(pathOf(source))); }); -const copyTree = api.cpSync; +const copyTree = (source: string, destination: string, options: any) => { + const fail = (code: string): never => { + throw Object.assign(new Error(code), { code, syscall: "cp" }); + }; + source = pathOf(source); + destination = pathOf(destination); + if (source === destination) + throw new Error("Source and destination paths must not be the same"); + if (destination.startsWith(source + "/")) fail("ERR_FS_CP_EINVAL"); + const lookup = (path: string, follow: boolean) => { + try { + return api[follow ? "statSync" : "lstatSync"](path); + } catch (error: any) { + if (error.code !== "ENOENT") throw error; + } + }; + const visit = (src: string, dst: string, nested = false) => { + const stat = lookup(src, nested ? false : options.dereference); + if (!stat) fail("ENOENT"); + const target = lookup(dst, options.dereference); + if (stat.isDirectory()) { + if (!options.recursive) fail("ERR_FS_EISDIR"); + if (target && !target.isDirectory()) + fail( + target.isSymbolicLink() + ? nested + ? "EISDIR" + : "ENOTDIR" + : nested + ? "ENOTDIR" + : "ERR_FS_CP_DIR_TO_NON_DIR", + ); + const entries = api.readdirSync(src); + api.mkdirSync(dst, { recursive: true }); + for (const name of entries) + visit(src + "/" + name, dst + "/" + name, true); + return; + } + if (target?.isDirectory()) + fail( + nested + ? "EISDIR" + : stat.isSymbolicLink() + ? "ENOTDIR" + : "ERR_FS_CP_NON_DIR_TO_DIR", + ); + if (target && !options.force) { + if (options.errorOnExist) fail(nested ? "EEXIST" : "ERR_FS_CP_EEXIST"); + return; + } + const data = stat.isSymbolicLink() + ? api.readlinkSync(src) + : api.readFileSync(src); + api.mkdirSync(dst.slice(0, dst.lastIndexOf("/")) || "/", { + recursive: true, + }); + if (lookup(dst, false)) api.unlinkSync(dst); + if (stat.isSymbolicLink()) api.symlinkSync(data, dst); + else api.writeFileSync(dst, data); + }; + visit(source, destination); +}; installOperation("cp", (source: any, destination: any, options: any = {}) => { validateObject(options, "options"); const { @@ -401,8 +574,360 @@ installOperation("cp", (source: any, destination: any, options: any = {}) => { mode: 0, }); }); +api.cp = (source: any, destination: any, options: any, callback?: any) => { + if (typeof options === "function") { + callback = options; + options = {}; + } + validateObject(options ?? {}, "options"); + for (const key of [ + "dereference", + "errorOnExist", + "force", + "preserveTimestamps", + "recursive", + "verbatimSymlinks", + ]) + if (options?.[key] !== undefined) + validateBoolean(options[key], "options." + key); + if (options?.mode !== undefined) validateUint32(options.mode, "options.mode"); + if (options?.filter !== undefined && typeof options.filter !== "function") + throw new ERR_INVALID_ARG_TYPE( + "options.filter", + "function", + options.filter, + ); + if (options?.filter !== undefined) + throw Object.assign(new Error("The requested operation is unsupported"), { + code: "ERR_UNSUPPORTED_OPERATION", + }); + if (options?.mode & 4) + throw new ERR_INVALID_ARG_VALUE( + "options.mode", + "COPYFILE_FICLONE_FORCE is not supported", + ); + pathOf(source); + pathOf(destination); + validateFunction(callback, "callback"); + let error: unknown; + try { + api.cpSync(source, destination, options); + } catch (cause) { + error = cause; + } + queueMicrotask(() => callback(error ?? null)); +}; api.promises = promises; export { promises }; +// Validate public directory arguments before scheduling their callbacks. +function directoryOperation( + name: string, + prepare: (path: any, options: any) => any, + operation: (args: any) => any, +) { + api[name + "Sync"] = (path: any, options: any = {}) => + operation(prepare(path, options)); + api[name] = (path: any, options: any, callback?: any) => { + if (typeof options === "function") { + callback = options; + options = {}; + } + const args = prepare(path, options === undefined ? {} : options); + validateFunction(callback, "callback"); + let value: any, + error: any = null; + try { + value = operation(args); + } catch (failure) { + error = failure; + } + queueMicrotask(() => callback(error, value)); + }; + promises[name] = async (path: any, options: any = {}) => + operation(prepare(path, options)); +} +directoryOperation( + "mkdir", + (path, options) => { + if (typeof options === "number") options = { mode: options }; + validateObject(options, "options"); + const { recursive = false, mode = 0o777 } = options; + validateBoolean(recursive, "options.recursive"); + fileMode(mode); + return { path: pathOf(path), recursive }; + }, + ({ path, recursive }) => { + writablePath(path, "mkdir"); + if (temporary.existsSync(path) && temporary.statSync(path).isDirectory()) + return; + let created = path; + if (recursive) { + let parent = created.slice(0, created.lastIndexOf("/")) || "/"; + while (!temporary.existsSync(parent)) { + created = parent; + parent = created.slice(0, created.lastIndexOf("/")) || "/"; + } + } + temporary.mkdirSync(path, { recursive, mode: 0o777 }); + return recursive ? created : undefined; + }, +); +directoryOperation( + "mkdtemp", + (path, options) => { + if (typeof options === "string" || options == null) + options = { encoding: options }; + validateObject(options, "options"); + const { encoding = "utf8" } = options; + if (encoding !== "buffer" && !Buffer.isEncoding(encoding)) + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + return { path: pathOf(path) }; + }, + ({ path }) => { + writablePath(path, "mkdtemp"); + for (;;) { + const name = + path + globalThis.crypto.getRandomValues(new Uint32Array(1))[0]; + try { + temporary.mkdirSync(name); + return name; + } catch (error: any) { + if (error.code !== "EEXIST") throw error; + } + } + }, +); +for (const name of ["rm", "rmdir"]) + directoryOperation( + name, + (path, options) => { + validateObject(options, "options"); + const { + recursive = false, + force = false, + maxRetries = 0, + retryDelay = 0, + } = options; + if (name === "rm") validateBoolean(force, "options.force"); + validateUint32(maxRetries, "options.maxRetries"); + validateBoolean(recursive, "options.recursive"); + validateUint32(retryDelay, "options.retryDelay"); + return { path: pathOf(path), recursive, force }; + }, + ({ path, recursive, force }) => { + if ( + name === "rm" && + !recursive && + temporary.existsSync(path) && + temporary.lstatSync(path).isDirectory() + ) + return temporary.rmdirSync(path); + return temporary[name + "Sync"](path, { recursive, force }); + }, + ); +const statFilesystem = api.statfsSync; +directoryOperation( + "statfs", + (path, options) => { + pathOf(path); + validateObject(options, "options"); + validateBoolean(options.bigint ?? false, "options.bigint"); + return { path, options }; + }, + ({ path, options }) => statFilesystem(path, options), +); +api.exists = (path: any, callback: any) => { + validateFunction(callback, "callback"); + let exists = false; + try { + exists = api.existsSync(path); + } catch {} + queueMicrotask(() => callback(exists)); +}; +for (const name of ["link", "symlink"]) { + const prepare = (source: any, destination: any, type: any = null) => { + if (name === "symlink" && !["dir", "file", "junction", null].includes(type)) + throw new ERR_INVALID_ARG_VALUE("type", type); + const src = pathOf(source), + dest = pathOf(destination); + const raw = + destination instanceof URL + ? destination.pathname + : Buffer.isBuffer(destination) + ? destination.toString() + : destination; + return { src, dest, type, trailing: raw.endsWith("/") }; + }; + const operation = ({ src, dest, type, trailing }: any) => { + if (trailing) + throw Object.assign(new Error("Invalid path"), { code: "EINVAL" }); + if (name === "link") { + let stat; + try { + stat = api.statSync(src); + } catch (error: any) { + if (error.code === "ENOTDIR") error.code = "ENOENT"; + throw error; + } + if (stat.isDirectory()) + throw Object.assign(new Error("operation not permitted"), { + code: "EPERM", + }); + } + const parent = dest.slice(0, dest.lastIndexOf("/")) || "/"; + try { + const parentStat = api.statSync(parent); + if (!parentStat.isDirectory()) + throw Object.assign(new Error("Invalid path"), { code: "EINVAL" }); + } catch (error: any) { + if (error.code === "ENOTDIR") error.code = "EINVAL"; + throw error; + } + try { + return temporary[name + "Sync"](src, dest, type ?? undefined); + } catch (error: any) { + if (error.code === "ENOTDIR") + error.code = api.existsSync(parent) ? "EINVAL" : "ENOENT"; + throw error; + } + }; + api[name + "Sync"] = (source: any, destination: any, type?: any) => + operation(prepare(source, destination, type)); + api[name] = (source: any, destination: any, type: any, callback?: any) => { + if (typeof type === "function") { + callback = type; + type = null; + } + const args = prepare(source, destination, type); + validateFunction(callback, "callback"); + let error: any = null; + try { + operation(args); + } catch (failure) { + error = failure; + } + queueMicrotask(() => callback(error)); + }; + promises[name] = async (source: any, destination: any, type?: any) => + operation(prepare(source, destination, type)); +} +for (const name of ["readlink", "realpath"]) + directoryOperation( + name, + (path, options) => { + if (typeof options === "string" || options == null) + options = { encoding: options }; + validateObject(options, "options"); + const { encoding = "utf8" } = options; + if (encoding !== "buffer" && !Buffer.isEncoding(encoding)) + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + return { path: pathOf(path), encoding }; + }, + ({ path, encoding }) => { + const stat = api.lstatSync(path); + let target = path; + if (stat.isSymbolicLink()) target = temporary.readlinkSync(path); + else if (name === "readlink") + throw Object.assign(new Error("invalid argument"), { code: "EINVAL" }); + return Buffer.from(target).toString(encoding); + }, + ); +api.realpathSync.native = api.realpathSync; +api.realpath.native = api.realpath; +const unlinkFile = (path: any) => temporary.unlinkSync(path); +api.unlinkSync = (path: any) => { + path = pathOf(path); + if (api.lstatSync(path).isDirectory()) + throw Object.assign(new Error("illegal operation on a directory"), { + code: "EISDIR", + }); + return unlinkFile(path); +}; +api.unlink = (path: any, callback: any) => { + path = pathOf(path); + validateFunction(callback, "callback"); + let error: any = null; + try { + api.unlinkSync(path); + } catch (failure) { + error = failure; + } + queueMicrotask(() => callback(error)); +}; +promises.unlink = async (path: any) => api.unlinkSync(path); +api.ReadStream = api.FileReadStream = fileStreams.ReadStream; +api.WriteStream = api.FileWriteStream = fileStreams.WriteStream; +api.createReadStream = fileStreams.createReadStream; +api.createWriteStream = fileStreams.createWriteStream; +api.openAsBlob = (path: any, options: any = {}) => { + const normalized = pathOf(path); + validateObject(options, "options"); + const { type = "" } = options; + if (typeof type !== "string") + throw new ERR_INVALID_ARG_TYPE("options.type", "string", type); + if (api.statSync(normalized).isDirectory()) + throw Object.assign(new Error("illegal operation on a directory"), { + code: "EISDIR", + }); + const data = api.readFileSync(normalized); + return new Blob([data], { type }); +}; +api.Dir = WorkersDir; +api.Dirent = WorkersDirent; +installOperation("glob", workersGlob, false); +promises.glob = async function* (pattern: any, options?: any) { + yield* workersGlob(pattern, options); +}; +function directoryOptions(options: any, open: boolean) { + if (!open && (typeof options === "string" || options == null)) + options = { encoding: options }; + validateObject(options, "options"); + const { + encoding = "utf8", + recursive = false, + withFileTypes = false, + bufferSize = 32, + } = options; + if (encoding !== "buffer" && !Buffer.isEncoding(encoding)) + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + validateBoolean(recursive, "options.recursive"); + if (open) validateUint32(bufferSize, "options.bufferSize"); + else validateBoolean(withFileTypes, "options.withFileTypes"); + return { encoding, recursive, withFileTypes }; +} +directoryOperation( + "readdir", + (path, options) => ({ + ...directoryOptions(options, false), + path: pathOf(path), + }), + ({ path, encoding, recursive, withFileTypes }) => { + const entries = readDirectory(path, recursive); + return entries.map((entry) => + withFileTypes + ? new WorkersDirent( + encoding === "buffer" ? Buffer.from(entry.name) : entry.name, + entry.type, + entry.parentPath, + ) + : encoding === "buffer" + ? Buffer.from(entry.name) + : Buffer.from(entry.name).toString(encoding), + ); + }, +); +directoryOperation( + "opendir", + (path, options) => { + const opts = directoryOptions(options, true); + pathOf(path); + return { ...opts, path }; + }, + ({ path, encoding, recursive }) => { + return new WorkersDir(readDirectory(path, recursive), path, { encoding }); + }, +); +installIo(api, promises); export const { Dir, Dirent, diff --git a/src/compat/fs/io-validation.ts b/src/compat/fs/io-validation.ts new file mode 100644 index 00000000..ee0e3140 --- /dev/null +++ b/src/compat/fs/io-validation.ts @@ -0,0 +1,186 @@ +// @ts-nocheck +// Adapted from cloudflare/workerd@5358e3d337336eaf696a48bc1563d808b10feba5. +// Copyright Cloudflare, Inc. Licensed under Apache-2.0; see LICENSE-APACHE. +import { Buffer } from "node:buffer"; +import { validateInteger } from "../crypto/validation.js"; +import { validateUint32 } from "../net/validators.js"; +import { + ERR_INVALID_ARG_TYPE, + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, +} from "../net/errors.js"; +const isArrayBufferView = ArrayBuffer.isView; +class ERR_BUFFER_OUT_OF_BOUNDS extends RangeError { + code = "ERR_BUFFER_OUT_OF_BOUNDS"; + constructor(name) { + super(`${name} is outside of buffer bounds`); + } +} +function validateEncoding(value, encoding) { + if (encoding !== undefined && !Buffer.isEncoding(encoding)) + throw new ERR_INVALID_ARG_VALUE("encoding", encoding); +} +export function getValidatedFd(fd) { + validateInteger(fd, "fd", 0, 2147483647); + return fd; +} +export function validatePosition(position, name) { + if (typeof position === "number") validateUint32(position, name); + else if (typeof position !== "bigint" && position !== null) + throw new ERR_INVALID_ARG_TYPE( + name, + ["integer", "bigint", "null"], + position, + ); +} +export function validateWriteArgs( + fd: number, + buffer: NodeJS.ArrayBufferView | string, + offsetOrOptions: WriteSyncOptions | Position | undefined, + length: number | ValidEncoding | undefined, + position: Position | undefined, +): { fd: number; buffer: Buffer[]; position: Position } { + fd = getValidatedFd(fd); + + let offset: number | undefined | null = offsetOrOptions as number; + if (isArrayBufferView(buffer)) { + if (typeof offsetOrOptions === "object" && offsetOrOptions != null) { + ({ + offset = 0, + length, + position = null, + } = offsetOrOptions as WriteSyncOptions); + offset ??= 0; + validateInteger(offset, "offset", 0); + } else { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (offset != null) { + validateInteger(offset, "offset", 0); + } + offset ??= 0; + position ??= null; + } + + validatePosition(position, "position"); + if (offset > buffer.byteLength) { + throw new ERR_BUFFER_OUT_OF_BOUNDS("offset"); + } + length ??= buffer.byteLength - offset; + validateInteger(length, "length", 0); + if (length > buffer.byteLength - offset) { + throw new ERR_BUFFER_OUT_OF_BOUNDS("length"); + } + + return { + fd, + buffer: [Buffer.from(buffer.buffer, buffer.byteOffset + offset, length)], + position, + }; + } + + if (typeof buffer !== "string") { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["string", "Buffer", "TypedArray", "DataView"], + buffer, + ); + } + + // In this case, offsetOrOptions must either be a number, bigint, or null. + validatePosition(offsetOrOptions, "position"); + position = offsetOrOptions; + + // In this instance, buffer is a string and the length arg specifies + // the encoding to use. + validateEncoding(buffer, length as string); + return { + fd, + buffer: [Buffer.from(buffer, length as string /* encoding */)], + position, + }; +} + +export function validateReadArgs( + fd: number, + buffer: NodeJS.ArrayBufferView, + offsetOrOptions: ReadOptions | number | null, + length: number | undefined, + position: Position | undefined, +): { fd: number; buffer: Buffer[]; length: number; position: Position } { + fd = getValidatedFd(fd); + + // Great fun with polymorphism here. We're going to normalize the arguments + // to match the first signature (fd, buffer, offset, length, position). + // + // If the third argument is an object, then we will pull the offset, length, + // and position from it, ignoring the remaining arguments. If the third + // argument is a number, then we will use it as the offset and pull the + // length and position from the fourth and fifth arguments. If the third + // position is any other type, then we will throw an error. + + if (!isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["Buffer", "TypedArray", "DataView"], + buffer, + ); + } + + let actualOffset: number; + let actualLength: number; + let actualPosition = position; + + // Handle the case where the third argument is an options object + if (offsetOrOptions != null && typeof offsetOrOptions === "object") { + const { + offset = 0, + length = buffer.byteLength - offset, + position = null, + } = offsetOrOptions; + actualOffset = offset; + actualLength = length; + actualPosition = position; + } + // Handle the case where the third argument is a number (offset) + else if (typeof offsetOrOptions === "number") { + actualOffset = offsetOrOptions; + actualLength = length ?? buffer.byteLength - offsetOrOptions; + actualPosition = position; + } else { + throw new ERR_INVALID_ARG_TYPE( + "offset", + ["number", "object"], + offsetOrOptions, + ); + } + + validateUint32(actualOffset, "offset"); + validateUint32(actualLength, "length"); + validatePosition(actualPosition, "position"); + + // As in Node, a zero-length read is a no-op regardless of offset. + if (actualLength === 0) { + return { fd, buffer: [], length: 0, position: actualPosition }; + } + + if (actualOffset + actualLength > buffer.byteLength) { + throw new ERR_OUT_OF_RANGE( + "length", + `<= ${buffer.byteLength - actualOffset}`, + actualLength, + ); + } + + return { + fd, + buffer: [ + Buffer.from( + buffer.buffer, + buffer.byteOffset + actualOffset, + actualLength, + ), + ], + length: actualLength, + position: actualPosition, + }; +} diff --git a/src/compat/fs/io.ts b/src/compat/fs/io.ts new file mode 100644 index 00000000..145ce037 --- /dev/null +++ b/src/compat/fs/io.ts @@ -0,0 +1,321 @@ +import { Buffer } from "node:buffer"; +import { pathOf, descriptorFlags, readDescriptor } from "./writable.js"; +import { fileMode } from "./mode.js"; +import { + validateWriteArgs, + validateReadArgs, + getValidatedFd, + validatePosition, +} from "./io-validation.js"; +import { + validateBoolean, + validateObject, + validateFunction, + validateUint32, +} from "../net/validators.js"; +import { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE } from "../net/errors.js"; +export function installIo(api: any, promises: any) { + const raw = { ...api }; + const fail = (code: string, message = code): never => { + throw Object.assign(new Error(message), { code }); + }; + const perform = (fn: () => any) => { + try { + return fn(); + } catch (e: any) { + if (e.code === "EBADF") e.message = "bad file descriptor"; + throw e; + } + }; + const install = ( + name: string, + prepare: (...args: any[]) => any[], + action: (...args: any[]) => any, + extra?: (args: any[]) => any[], + ) => { + api[name + "Sync"] = (...args: any[]) => + perform(() => action(...prepare(...args))); + api[name] = (...args: any[]) => { + const callback = + typeof args.at(-1) === "function" ? args.pop() : undefined; + if (name === "read") validateFunction(callback, "callback"); + const prepared = prepare(...args); + validateFunction(callback, "callback"); + let error: unknown, value: any; + try { + value = perform(() => action(...prepared)); + } catch (cause) { + error = cause; + } + queueMicrotask(() => + callback(error ?? null, value, ...(extra?.(args) ?? [])), + ); + }; + if (Object.hasOwn(promises, name)) + promises[name] = async (...args: any[]) => api[name + "Sync"](...args); + }; + const flags = (flag: any) => { + if (typeof flag === "number") { + validateUint32(flag, "flags"); + return flag; + } + const value: Record = { + r: 0, + "r+": 2, + rs: 1052672, + sr: 1052672, + "rs+": 1052674, + "sr+": 1052674, + w: 577, + wx: 705, + xw: 705, + "w+": 578, + "wx+": 706, + "xw+": 706, + a: 1089, + ax: 1217, + xa: 1217, + "a+": 1090, + "ax+": 1218, + "xa+": 1218, + as: 1053761, + sa: 1053761, + "as+": 1053762, + "sa+": 1053762, + }; + if (!Object.hasOwn(value, flag)) + throw new ERR_INVALID_ARG_VALUE("flags", flag); + return value[flag]; + }; + install( + "close", + (fd: any) => [getValidatedFd(fd)], + (fd) => { + try { + raw.closeSync(fd); + } catch (e: any) { + if (e.code !== "EBADF") throw e; + } + }, + ); + for (const name of ["truncate", "ftruncate"]) + install( + name, + (path: any, length = 0) => { + validateUint32(length, "len"); + return [ + name === "ftruncate" ? getValidatedFd(path) : pathOf(path), + length, + ]; + }, + (...args: any[]) => raw[name + "Sync"](...args), + ); + for (const name of ["write", "read"]) + install( + name, + ( + fd: any, + buffer: any, + offset: any = name === "read" ? 0 : null, + length?: any, + position: any = null, + ) => { + const result = ( + name === "write" ? validateWriteArgs : validateReadArgs + )(fd, buffer, offset, length, position); + return [result.fd, result.buffer, result.position]; + }, + (fd: number, buffers: any[], position: any) => { + if (typeof position === "bigint") { + if (position < 0n || position > 0xffffffffn) fail("EINVAL"); + position = Number(position); + } + if (!buffers.length) return 0; + return perform(() => + raw[name + "Sync"]( + fd, + buffers[0], + 0, + buffers[0].byteLength, + position, + ), + ); + }, + (args) => [args[1]], + ); + const writeCallback = api.write; + api.write = (...args: any[]) => { + if ( + typeof args[1] === "string" && + args[2] !== null && + typeof args[2] === "object" + ) + args[2] = args[2].position ?? null; + return writeCallback(...args); + }; + for (const name of ["readv", "writev"]) + install( + name, + (fd: any, buffers: any, position: any = null) => { + getValidatedFd(fd); + validatePosition(position, "position"); + if ( + !Array.isArray(buffers) || + buffers.some((b) => !ArrayBuffer.isView(b)) + ) + throw new ERR_INVALID_ARG_TYPE( + "buffers", + "ArrayBufferView[]", + buffers, + ); + return [fd, buffers, position]; + }, + (fd: number, buffers: any[], position: any) => { + let count = 0; + for (const buffer of buffers) { + const size = api[name === "readv" ? "readSync" : "writeSync"]( + fd, + buffer, + 0, + buffer.byteLength, + position === null ? null : Number(position) + count, + ); + count += size; + } + return count; + }, + (args) => [args[1]], + ); + const optionsObject = (options: any) => + typeof options === "string" || options == null + ? { encoding: options } + : options; + install( + "readFile", + (path: any, options: any = {}) => { + options = optionsObject(options); + validateObject(options, "options"); + const { encoding = null, flag = "r" } = options; + flags(flag); + if ( + encoding !== null && + encoding !== "buffer" && + !Buffer.isEncoding(encoding) + ) + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + return [ + typeof path === "number" ? getValidatedFd(path) : pathOf(path), + encoding, + ]; + }, + (path: any, encoding: any) => { + const data = + typeof path === "number" + ? readDescriptor(path) + : raw.readFileSync(path); + return typeof encoding === "string" ? data.toString(encoding) : data; + }, + ); + for (const name of ["writeFile", "appendFile"]) + install( + name, + (path: any, data: any, options: any = {}) => { + path = typeof path === "number" ? getValidatedFd(path) : pathOf(path); + options = optionsObject(options); + validateObject(options, "options"); + const { + encoding = "utf8", + mode = 0o666, + flag = name === "appendFile" ? "a" : "w", + flush = false, + } = options; + if (encoding !== "buffer" && !Buffer.isEncoding(encoding)) + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + validateBoolean(flush, "options.flush"); + fileMode(mode); + const value = flags(flag); + if (!(value & 3) && !(value & 1024)) + throw new ERR_INVALID_ARG_VALUE( + "flag", + flag, + "must indicate write or append", + ); + if (typeof data === "string") data = Buffer.from(data, encoding); + if (!ArrayBuffer.isView(data)) + throw new ERR_INVALID_ARG_TYPE( + "data", + ["string", "Buffer", "TypedArray", "DataView"], + data, + ); + return [ + path, + Buffer.from(data.buffer, data.byteOffset, data.byteLength), + Boolean(value & 1024), + Boolean(value & 128), + ]; + }, + (path: any, data: Buffer, append: boolean, exclusive: boolean) => { + if (typeof path !== "number") { + if (exclusive && api.existsSync(path)) fail("EEXIST"); + raw[append ? "appendFileSync" : "writeFileSync"](path, data); + return data.length; + } + append ||= Boolean(descriptorFlags(path) & 1024); + if (!append) api.ftruncateSync(path, 0); + return api.writeSync( + path, + data, + 0, + data.length, + append ? api.fstatSync(path).size : 0, + ); + }, + ); + const open = api.openSync; + api.openSync = (path: any, flag: any = "r", mode?: any) => { + let value = flags(flag); + if (!(value & 3)) value &= ~512; + return open(path, value, mode); + }; + api.constants = Object.assign(Object.create(null), api.constants); + for (const name of [ + "UV_FS_COPYFILE_EXCL", + "UV_FS_COPYFILE_FICLONE", + "UV_FS_COPYFILE_FICLONE_FORCE", + ]) + api.constants[name] = api.constants[name.slice(6)]; + promises.constants = api.constants; + const rename = api.renameSync; + install( + "rename", + (src: any, dst: any) => [pathOf(src), pathOf(dst)], + (src: string, dst: string) => { + if (src === dst) return; + const stat = api.lstatSync(src); + const target = api.lstatSync(dst, { throwIfNoEntry: false }); + if (stat.isDirectory()) { + if (dst.startsWith(src + "/")) fail("EINVAL"); + if (target && !target.isDirectory()) fail("ENOTDIR"); + if (target && api.readdirSync(dst).length) fail("ENOTEMPTY"); + } else if (target?.isDirectory()) fail("EISDIR"); + return rename(src, dst); + }, + ); + const copy = api.copyFileSync; + install( + "copyFile", + (src: any, dst: any, mode: any = 0) => { + src = pathOf(src); + dst = pathOf(dst); + if (![0, 1, 2, 4].includes(mode)) + throw new ERR_INVALID_ARG_VALUE("mode", mode); + if (mode === 4) + fail( + "ERR_UNSUPPORTED_OPERATION", + "The requested operation is unsupported", + ); + return [src, dst, mode]; + }, + copy, + ); +} diff --git a/src/compat/fs/stream-errors.ts b/src/compat/fs/stream-errors.ts new file mode 100644 index 00000000..cb63aa7a --- /dev/null +++ b/src/compat/fs/stream-errors.ts @@ -0,0 +1,15 @@ +export { + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, + ERR_MISSING_ARGS, +} from "../net/errors.js"; +export { + ERR_METHOD_NOT_IMPLEMENTED, + ERR_STREAM_DESTROYED, +} from "../stream/errors.js"; +import { NodeError } from "../net/errors.js"; +export class ERR_SYSTEM_ERROR extends NodeError { + constructor(message: string) { + super("ERR_SYSTEM_ERROR", message); + } +} diff --git a/src/compat/fs/stream-utils.ts b/src/compat/fs/stream-utils.ts new file mode 100644 index 00000000..acf1708c --- /dev/null +++ b/src/compat/fs/stream-utils.ts @@ -0,0 +1,55 @@ +import { pathOf } from "./writable.js"; +import { fileMode } from "./mode.js"; +import { validateInteger } from "../crypto/validation.js"; +import { NodeTypeError } from "../net/errors.js"; +export { + validateAbortSignal, + validateBoolean, + validateFunction, + validateObject, + validateString, + validateUint32, +} from "../net/validators.js"; +export type FilePath = string | import("node:buffer").Buffer | URL; +export type Position = number | bigint | null; +export type WriteSyncOptions = { + offset?: number; + length?: number; + position?: Position; +}; +export type ValidEncoding = BufferEncoding | "buffer" | null; +const handles = new WeakSet(); +export function registerFileHandle(handle: object) { + handles.add(handle); +} +export function isFileHandle(handle: unknown) { + return typeof handle === "object" && handle !== null && handles.has(handle); +} +export function normalizePath(path: unknown) { + return new URL("file://" + pathOf(path)); +} +export function toPathIfFileURL(path: URL) { + return path.pathname; +} +export function getValidatedFd(fd: unknown) { + validateInteger(fd, "fd", 0, 2147483647); + return fd as number; +} +export function parseFileMode( + value: unknown, + _name: string, + fallback?: number, +) { + return fileMode(value, fallback); +} +export function validateThisInternalField( + value: any, + key: symbol, + name: string, +) { + if (!value || !Reflect.has(value, key)) + throw new NodeTypeError( + "ERR_INVALID_THIS", + `Value of "this" must be of type ${name}`, + ); +} diff --git a/src/compat/fs/streams.ts b/src/compat/fs/streams.ts new file mode 100644 index 00000000..610602c9 --- /dev/null +++ b/src/compat/fs/streams.ts @@ -0,0 +1,1187 @@ +// @ts-nocheck +// Ported from workerd@5358e3d337336eaf696a48bc1563d808b10feba5 internal_fs_streams.ts. +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { AsyncLocalStorage } from "../async-hooks/index.js"; +function bindFs(operations) { + const run = AsyncLocalStorage.snapshot(); + return Object.fromEntries( + Object.entries(operations).map(([name, fn]) => [ + name, + (...args) => run(() => Reflect.apply(fn, operations, args)), + ]), + ); +} +import { Readable } from "../stream/readable.js"; +import { Writable } from "../stream/writable.js"; +import { Buffer } from "node:buffer"; +import { type EventEmitter } from "../events/index.js"; +import { + normalizePath, + getValidatedFd, + isFileHandle, + type FilePath, + type Position, + type WriteSyncOptions, + type ValidEncoding, +} from "./stream-utils.js"; +import { toPathIfFileURL } from "./stream-utils.js"; + +import * as fs from "./index.js"; + +import { + parseFileMode, + validateAbortSignal, + validateBoolean, + validateFunction, + validateObject, + validateString, + validateUint32, + validateThisInternalField, +} from "./stream-utils.js"; + +type DoubleArgCallback = (error: any, a?: A, b?: B) => void; +type SingleArgCallback = (error: any, value?: A) => void; +type ErrorOnlyCallback = (error?: any) => void; +type OpenType = typeof import("node:fs").open; +type CloseType = typeof import("node:fs").close; +type FsyncType = typeof import("node:fs").fsync; +type ReadType = typeof import("node:fs").read; +type WriteType = typeof import("node:fs").write; +type WritevType = typeof import("node:fs").writev; +import type { FileHandle } from "node:fs/promises"; + +import { errorOrDestroy } from "../stream/destroy.js"; +import { eos } from "../stream/end-of-stream.js"; + +import { + ERR_INVALID_ARG_VALUE, + ERR_OUT_OF_RANGE, + ERR_MISSING_ARGS, + ERR_METHOD_NOT_IMPLEMENTED, + ERR_STREAM_DESTROYED, + ERR_SYSTEM_ERROR, +} from "./stream-errors.js"; + +import type { ReadAsyncOptions } from "node:fs"; +const reportError = (error: unknown) => { + if (typeof globalThis.reportError === "function") + globalThis.reportError(error); + else + queueMicrotask(() => { + throw error; + }); +}; + +export interface FsOperations { + open?: typeof OpenType | undefined; + close?: typeof CloseType | undefined; + fsync?: typeof FsyncType | undefined; + read?: typeof ReadType | undefined; + write?: typeof WriteType | undefined; + writev?: typeof WritevType | undefined; +} + +export interface RealizedFsOperations { + open: typeof OpenType; + close: typeof CloseType; + fsync: typeof FsyncType; + read: typeof ReadType; + write: typeof WriteType; + writev: typeof WritevType; +} + +// Temporary while developing... +/* eslint-disable */ + +let lazyFs: RealizedFsOperations | undefined; +async function getLazyFs(): Promise { + if (lazyFs == undefined) { + lazyFs = { + open: (...args): void => { + fs.open(...args); + }, + close: (...args): void => { + fs.close(...args); + }, + fsync: (...args): void => { + fs.fsync(...args); + }, + read: (...args): void => { + fs.read(...args); + }, + write: (...args): void => { + fs.write(...args); + }, + writev: (...args): void => { + fs.writev(...args); + }, + }; + } + return lazyFs; +} + +const kDefaultFsOperations: RealizedFsOperations = { + open( + path: FilePath, + flags: string | number | SingleArgCallback = "r", + mode: string | number | SingleArgCallback = 0o666, + cb?: SingleArgCallback, + ): void { + let callback: SingleArgCallback; + if (typeof flags === "function") { + callback = flags; + } else if (typeof mode === "function") { + callback = mode; + } else if (typeof cb === "function") { + callback = cb; + } else { + throw new ERR_MISSING_ARGS("fs.open callback"); + } + validateFunction(callback, "fs.open callback"); + const openFlags = typeof flags === "function" ? "r" : flags; + const openMode = typeof mode === "function" ? 0o666 : mode; + const done = (err: unknown, fd?: number): void => { + try { + if (err) callback(err); + else callback(null, fd); + } catch (e: unknown) { + reportError(e); + } + }; + + getLazyFs().then((fs: RealizedFsOperations) => { + // fs.open validates flags synchronously. + try { + fs.open(path, openFlags, openMode, done); + } catch (err: unknown) { + done(err); + } + }, done); + }, + close(fd: number, cb: ErrorOnlyCallback = () => {}): void { + getLazyFs().then( + (fs: RealizedFsOperations) => { + fs.close(fd, (err: unknown) => { + if (err) { + try { + cb(err); + } catch (e: unknown) { + reportError(e); + } + return; + } + try { + cb(null); + } catch (e: unknown) { + reportError(e); + } + }); + }, + (err: unknown) => { + try { + cb(err); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + fsync(fd: number, cb: ErrorOnlyCallback = () => {}): void { + getLazyFs().then( + (fs: RealizedFsOperations) => { + fs.fsync(fd, (err: unknown) => { + if (err) { + try { + cb(err); + } catch (e: unknown) { + reportError(e); + } + return; + } + try { + cb(null); + } catch (e: unknown) { + reportError(e); + } + }); + }, + (err: unknown) => { + try { + cb(err); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + read( + fd: number, + buffer: T | ReadAsyncOptions | DoubleArgCallback, + offset?: ReadAsyncOptions | number | DoubleArgCallback, + length?: null | number | DoubleArgCallback, + position?: Position, + cb?: DoubleArgCallback, + ): void { + getLazyFs().then( + (fs: RealizedFsOperations) => { + fs.read( + fd, + buffer, + offset, + length, + position, + ( + err: unknown, + bytesRead: number | undefined, + buffer: T | undefined, + ) => { + if (err) { + try { + cb?.(err); + } catch (e: unknown) { + reportError(e); + } + return; + } + try { + cb?.(null, bytesRead, buffer); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + (err: unknown) => { + try { + cb?.(err); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + write( + fd: number, + buffer: T | string, + offset?: WriteSyncOptions | Position | DoubleArgCallback, + length?: number | ValidEncoding | DoubleArgCallback, + position?: Position | DoubleArgCallback, + cb?: DoubleArgCallback, + ): void { + getLazyFs().then( + (fs: RealizedFsOperations) => { + fs.write( + fd, + buffer, + offset, + length, + position, + ( + err: unknown, + bytesWritten: number | undefined, + buffer: T | undefined, + ) => { + if (err) { + try { + cb?.(err); + } catch (e: unknown) { + reportError(e); + } + return; + } + try { + cb?.(null, bytesWritten, buffer); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + (err: unknown) => { + try { + cb?.(err); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + writev( + fd: number, + buffers: T[], + position?: Position | SingleArgCallback, + cb?: DoubleArgCallback, + ): void { + getLazyFs().then( + (fs: RealizedFsOperations) => { + fs.writev( + fd, + buffers, + position, + (err: unknown, bytesWritten?: number, buffers?: T[]) => { + if (err) { + try { + cb?.(err); + } catch (e: unknown) { + reportError(e); + } + return; + } + try { + cb?.(null, bytesWritten, buffers); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, + (err: unknown) => { + try { + cb?.(err); + } catch (e: unknown) { + reportError(e); + } + }, + ); + }, +}; + +export type ReadStreamOptions = { + encoding?: string | undefined; + autoClose?: boolean | undefined; + autoDestroy?: boolean | undefined; + emitClose?: boolean | undefined; + start?: number | undefined; + end?: number | undefined; + highWaterMark?: number | undefined; + signal?: AbortSignal | undefined; + flags?: string | undefined; + fd?: number | FileHandle | undefined; + mode?: number | undefined; + fs?: FsOperations | undefined; +}; + +const kFs = Symbol("kFs"); +const kIsPerformingIO = Symbol("kIsPerformingIO"); +const kIoDone = Symbol("kIoDone"); +const kHandle = Symbol("kHandle"); + +// @ts-expect-error TS2323 Cannot redeclare. +export declare class ReadStream extends Readable { + fd: number | null; + flags: string; + path: string; + mode: number; + start: number; + end: number; + pos: number | undefined; + bytesRead: number; + flush: boolean; + [kFs]: RealizedFsOperations; + [kIsPerformingIO]: boolean; + [kHandle]: FileHandle | undefined; + constructor(path: FilePath | null, options?: ReadStreamOptions); + push(chunk: NodeJS.ArrayBufferView | null): boolean; + close(callback?: ErrorOnlyCallback): void; +} + +function construct( + this: ReadStream | WriteStream, + callback: (err: unknown) => void, +): void { + const stream = this; + if (typeof stream.fd === "number") { + callback(null); + return; + } + + const ee = stream as unknown as EventEmitter; + + if (typeof (stream as any).open === "function") { + // Backwards compat for monkey patching open(). + const orgEmit = ee.emit; + ee.emit = function (...args): boolean { + if (args[0] === "open") { + this.emit = orgEmit; + callback(null); + Reflect.apply(orgEmit, this, args); + } else if (args[0] === "error") { + this.emit = orgEmit; + callback(args[1]); + } else { + Reflect.apply(orgEmit, this, args); + } + return true; + }; + (stream as any).open(); + return; + } + stream[kFs].open( + stream.path, + stream.flags, + stream.mode, + (er: unknown, fd: number | undefined) => { + if (er) { + callback(er); + return; + } + if (fd === undefined) { + callback(new ERR_INVALID_ARG_VALUE("fd", "undefined")); + return; + } + stream.fd = fd; + callback(null); + ee.emit("open", stream.fd); + ee.emit("ready"); + }, + ); +} + +function getValidatedFsOptions(fs: FsOperations): RealizedFsOperations { + validateObject(fs, "options.fs"); + + if (isFileHandle(fs)) { + const handle = fs as unknown as FileHandle; + const open = function (..._args: unknown[]): void { + throw new ERR_METHOD_NOT_IMPLEMENTED("open()"); + }; + const close = function (...args: unknown[]): void { + const cb = args[args.length - 1] as ErrorOnlyCallback; + handle.close().then( + () => cb(null), + (err: unknown) => cb(err), + ); + }; + const fsync = function (...args: unknown[]): void { + const cb = args[args.length - 1] as ErrorOnlyCallback; + handle.sync().then( + () => cb(null), + (err: unknown) => cb(err), + ); + }; + const read = function (...args: unknown[]): void { + const cb = args[args.length - 1] as DoubleArgCallback< + number, + NodeJS.ArrayBufferView + >; + // @ts-expect-error TS2345 + handle.read(...args.slice(1, -1)).then( + (result: { bytesRead: number; buffer: NodeJS.ArrayBufferView }) => { + cb(null, result.bytesRead, result.buffer); + }, + (err: unknown) => cb(err), + ); + }; + const write = function (...args: unknown[]): void { + const cb = args[args.length - 1] as DoubleArgCallback< + number, + NodeJS.ArrayBufferView + >; + // @ts-expect-error TS2556 + handle.write(...args.slice(1, -1)).then( + (result: { bytesWritten: number; buffer: NodeJS.ArrayBufferView }) => { + cb(null, result.bytesWritten, result.buffer); + }, + (err: unknown) => cb(err), + ); + }; + const writev = function (...args: unknown[]): void { + const cb = args[args.length - 1] as DoubleArgCallback< + number, + NodeJS.ArrayBufferView[] + >; + // @ts-expect-error TS2556 + handle.writev(...args.slice(1, -1)).then( + (result: { + bytesWritten: number; + buffers: NodeJS.ArrayBufferView[]; + }) => { + cb(null, result.bytesWritten, result.buffers); + }, + (err: unknown) => cb(err), + ); + }; + return { + open, + close, + fsync, + read, + write, + writev, + }; + } + + let { + open = kDefaultFsOperations.open, + close = kDefaultFsOperations.close, + fsync = kDefaultFsOperations.fsync, + read = kDefaultFsOperations.read, + write = kDefaultFsOperations.write, + writev = kDefaultFsOperations.writev, + } = fs as FsOperations; + + validateFunction(open, "options.fs.open"); + validateFunction(read, "options.fs.read"); + validateFunction(close, "options.fs.close"); + validateFunction(fsync, "options.fs.fsync"); + validateFunction(write, "options.fs.write"); + validateFunction(writev, "options.fs.writev"); + return { open, close, fsync, read, write, writev }; +} + +function readImpl(this: ReadStream, n: number): void { + n = + this.pos !== undefined + ? Math.min(this.end - this.pos + 1, n) + : Math.min(this.end - this.bytesRead + 1, n); + if (n <= 0) { + this.push(null); + return; + } + + const buf = Buffer.allocUnsafeSlow(n); + const ee = this as unknown as EventEmitter; + + this[kIsPerformingIO] = true; + if (this.fd == null) { + this.push(null); + return; + } + this[kFs].read(this.fd, buf, 0, n, this.pos, (er, bytesRead, buf) => { + this[kIsPerformingIO] = false; + + // Tell ._destroy() that it's safe to close the fd now. + if (this.destroyed) { + ee.emit(kIoDone, er); + return; + } + + if (er) { + errorOrDestroy(this, er as Error); + return; + } + + if (buf == null) { + errorOrDestroy(this, new ERR_INVALID_ARG_VALUE("buf", "null")); + return; + } + + if (bytesRead != null && bytesRead > 0) { + if (this.pos !== undefined) { + this.pos += bytesRead; + } + + this.bytesRead += bytesRead; + + if (bytesRead !== buf.byteLength) { + // Slow path. Shrink to fit. + // Copy instead of slice so that we don't retain + // large backing buffer for small reads. + const dst = Buffer.allocUnsafeSlow(bytesRead); + if (Buffer.isBuffer(buf)) { + (buf as Buffer).copy(dst, 0, 0, bytesRead); + } else { + const buffer = Buffer.from(buf.buffer, buf.byteOffset, bytesRead); + buffer.copy(dst, 0, 0, bytesRead); + } + buf = dst; + } + + this.push(buf); + } else { + this.push(null); + } + }); +} + +function actualCloseImpl( + stream: ReadStream | WriteStream, + err: unknown, + cb: ErrorOnlyCallback, +): void { + if (stream.fd == null) return; + stream[kFs].close(stream.fd, (er) => { + cb(er || err); + }); + stream.fd = null; +} + +function closeImpl( + stream: ReadStream | WriteStream, + err: unknown, + cb: ErrorOnlyCallback, +): void { + if (stream.fd == null) { + cb(err); + } else if (stream.flush) { + stream[kFs].fsync(stream.fd, (flushErr) => { + actualCloseImpl(stream, err || flushErr, cb); + }); + } else { + actualCloseImpl(stream, err, cb); + } +} + +function destroyImpl( + this: ReadStream | WriteStream, + err: unknown, + cb: ErrorOnlyCallback, +): void { + // Usually for async IO it is safe to close a file descriptor + // even when there are pending operations. However, due to platform + // differences file IO is implemented using synchronous operations + // running in a thread pool. Therefore, file descriptors are not safe + // to close while used in a pending read or write operation. Wait for + // any pending IO (kIsPerformingIO) to complete (kIoDone). + const ee = this as unknown as EventEmitter; + if (this[kIsPerformingIO]) { + ee.once(kIoDone, (er) => { + closeImpl(this, err || er, cb); + }); + } else { + closeImpl(this, err, cb); + } +} + +// @ts-expect-error TS2323 Cannot redeclare. +export function ReadStream( + this: ReadStream, + path: FilePath, + options: ReadStreamOptions = {}, +): ReadStream { + if (!(this instanceof ReadStream)) { + return new ReadStream(path, options); + } + if (options === null) { + options = {}; + } else if (typeof options === "string") { + options = { encoding: options }; + } + + validateObject(options, "options"); + const { + encoding = null, + autoClose = true, + emitClose = true, + start = 0, + end = Infinity, + highWaterMark = 64 * 1024, + signal = null, + flags = "r", + fd = null, + mode = 0o666, + fs = kDefaultFsOperations, + } = options; + const autoDestroy = autoClose; + + if ( + encoding !== "buffer" && + encoding !== null && + !Buffer.isEncoding(encoding) + ) { + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + } + validateBoolean(autoClose, "options.autoClose"); + validateBoolean(emitClose, "options.emitClose"); + validateUint32(start, "options.start"); + if (end !== Infinity) { + validateUint32(end, "options.end"); + if (start > end) { + throw new ERR_OUT_OF_RANGE("start", `<= "end" (here: ${end})`, start); + } + } + validateUint32(highWaterMark, "options.highWaterMark"); + if (signal != null) { + validateAbortSignal(signal, "options.signal"); + } + + validateString(flags, "options.flags"); + + // We don't actually use the mode in our implementation. Parsing it is + // just to ensure that it is validated. + parseFileMode(mode, "options.mode", 0o666); + + this[kFs] = bindFs(getValidatedFsOptions(fs)); + + if (fd == null) { + this.fd = null; + // Path will be ignored when fd is specified, so it can be falsy + this.path = toPathIfFileURL(normalizePath(path)); + this.flags = options.flags === undefined ? "r" : options.flags; + this.mode = options.mode === undefined ? 0o666 : options.mode; + } else { + if (isFileHandle(fd)) { + if (fs !== kDefaultFsOperations) { + throw new ERR_METHOD_NOT_IMPLEMENTED("FileHandle with fs"); + } + this[kHandle] = fd as FileHandle; + this[kFs] = bindFs(getValidatedFsOptions(fd as unknown as FsOperations)); + const ee = fd as unknown as EventEmitter; + ee.on("close", () => this.close()); + this.fd = (fd as FileHandle).fd || null; + } else { + this.fd = getValidatedFd(fd as number); + } + } + + this.start = start; + this.end = end; + this.pos = start; + this.bytesRead = 0; + this[kIsPerformingIO] = false; + + Reflect.apply(Readable, this, [ + { + highWaterMark, + encoding, + emitClose, + autoDestroy, + signal, + construct: construct.bind(this), + read: readImpl.bind(this), + destroy: destroyImpl.bind(this), + }, + ]); + return this; +} +Object.setPrototypeOf(ReadStream.prototype, Readable.prototype); +Object.setPrototypeOf(ReadStream, Readable); + +Object.defineProperty(ReadStream.prototype, "autoClose", { + get(this: ReadStream): boolean { + validateThisInternalField(this, kFs, "ReadStream"); + return this._readableState?.autoDestroy || false; + }, + set(this: ReadStream, _val: boolean): void { + validateThisInternalField(this, kFs, "ReadStream"); + if (this._readableState !== undefined) { + this._readableState.autoDestroy = _val; + } + }, +}); + +ReadStream.prototype.close = function ( + cb: ErrorOnlyCallback = (_err: unknown): void => {}, +): void { + if (typeof cb === "function") eos(this, cb); + this.destroy(); +}; + +Object.defineProperty(ReadStream.prototype, "pending", { + get(this: ReadStream): boolean { + return this.fd === null; + }, + configurable: true, +}); + +// ====================================================================================== + +export type WriteStreamOptions = { + encoding?: string | undefined; + autoClose?: boolean | undefined; + autoDestroy?: boolean | undefined; + emitClose?: boolean | undefined; + start?: number | undefined; + highWaterMark?: number | undefined; + signal?: AbortSignal | undefined; + flags?: string | undefined; + fd?: number | FileHandle | undefined; + mode?: number | undefined; + fs?: FsOperations | undefined; + flush?: boolean | undefined; +}; + +declare type WriteVChunk = { + chunk: string | NodeJS.ArrayBufferView; + encoding: ValidEncoding; +}; + +// @ts-expect-error TS2323 Cannot redeclare. +export declare class WriteStream extends Writable { + fd: number | null; + path: string; + flags: string; + mode: number; + flush: boolean; + autoClose: boolean; + destroyed: boolean; + start: number; + pos: number; + bytesRead: number; + bytesWritten: number; + [kIsPerformingIO]: boolean; + [kFs]: RealizedFsOperations; + [kHandle]?: FileHandle; + constructor(path: FilePath, options?: WriteStreamOptions); + close(cb?: ErrorOnlyCallback): void; + destroySoon(): void; +} + +function writeAll( + this: WriteStream, + data: string | NodeJS.ArrayBufferView, + size: number, + pos: number, + cb: ErrorOnlyCallback, + retries = 0, +) { + if (this.fd == null) { + return cb(new ERR_INVALID_ARG_VALUE("fd", "null")); + } + + this[kFs].write( + this.fd, + data, + 0, + size, + pos, + ( + er: unknown, + bytesWritten?: number, + buffer?: string | NodeJS.ArrayBufferView, + ) => { + // No data currently available and operation should be retried later. + if ((er as any)?.code === "EAGAIN") { + er = null; + bytesWritten = 0; + } + + if (this.destroyed || er) { + return cb(er || new ERR_STREAM_DESTROYED("write")); + } + + // The value should be set but let's suppress the possible + // typescript error here. + bytesWritten = bytesWritten ?? 0; + + this.bytesWritten += bytesWritten; + + retries = bytesWritten ? 0 : retries + 1; + size -= bytesWritten; + pos += bytesWritten; + + // Try writing non-zero number of bytes up to 5 times. + if (retries > 5) { + cb(new ERR_SYSTEM_ERROR("write failed")); + } else if (size) { + if (buffer == null) { + cb(null); + return; + } + const buf = + typeof buffer === "string" + ? Buffer.from(buffer as string) + : Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength); + writeAll.call(this, buf.slice(bytesWritten), size, pos, cb, retries); + } else { + cb(null); + } + }, + ); +} + +function writevAll( + this: WriteStream, + chunks: NodeJS.ArrayBufferView[], + size: number, + pos: number, + cb: ErrorOnlyCallback, + retries = 0, +) { + if (this.fd == null) { + return cb(new ERR_INVALID_ARG_VALUE("fd", "null")); + } + this[kFs].writev( + this.fd, + chunks, + this.pos, + ( + er: unknown, + bytesWritten?: number, + buffers?: NodeJS.ArrayBufferView[], + ) => { + // No data currently available and operation should be retried later. + if ((er as any)?.code === "EAGAIN") { + er = null; + bytesWritten = 0; + } + + if (this.destroyed || er) { + return cb(er || new ERR_STREAM_DESTROYED("writev")); + } + + bytesWritten = bytesWritten ?? 0; + + this.bytesWritten += bytesWritten; + + retries = bytesWritten ? 0 : retries + 1; + size -= bytesWritten; + pos += bytesWritten; + + // Try writing non-zero number of bytes up to 5 times. + if (retries > 5) { + cb(new ERR_SYSTEM_ERROR("writev failed")); + } else if (size) { + buffers ??= []; + const bufs = buffers.map((b): Buffer => { + return Buffer.from(b.buffer, b.byteOffset, b.byteLength); + }); + if (buffers.length === 0) { + cb(null); + return; + } + writevAll.call( + this, + [Buffer.concat(bufs).slice(bytesWritten)], + size, + pos, + cb, + retries, + ); + } else { + cb(null); + } + }, + ); +} + +function writeImpl( + this: WriteStream, + data: string | NodeJS.ArrayBufferView, + encodingOrCallback: ValidEncoding | ErrorOnlyCallback | undefined, + cb?: ErrorOnlyCallback, +): void { + let callback: ErrorOnlyCallback; + let encoding: ValidEncoding = null; + if (typeof encodingOrCallback === "function") { + callback = encodingOrCallback; + } else if (encodingOrCallback != null) { + encoding = encodingOrCallback; + if (cb !== undefined) callback = cb; + } + // @ts-expect-error TS2345 + validateFunction(callback, "write callback"); + + if (typeof data === "string") { + data = Buffer.from(data, encoding as any); + } + + this[kIsPerformingIO] = true; + const ee = this as unknown as EventEmitter; + writeAll.call(this, data, data.byteLength, this.pos, (er: unknown) => { + this[kIsPerformingIO] = false; + if (this.destroyed) { + // Tell ._destroy() that it's safe to close the fd now. + callback(er); + ee.emit(kIoDone, er); + return; + } + + callback(er); + }); + + if (this.pos !== undefined) this.pos += data.byteLength; +} + +function writevImpl( + this: WriteStream, + data: WriteVChunk[], + callback: ErrorOnlyCallback, +) { + let size = 0; + const chunks = data.map((d) => { + const chunk = (d as any).chunk; + size += chunk.length; + return chunk; + }); + + this[kIsPerformingIO] = true; + writevAll.call(this, chunks, size, this.pos, (er: unknown) => { + this[kIsPerformingIO] = false; + const ee = this as unknown as EventEmitter; + if (this.destroyed) { + // Tell ._destroy() that it's safe to close the fd now. + callback(er); + ee.emit(kIoDone, er); + return; + } + + callback(er); + }); + + if (this.pos !== undefined) this.pos += size; +} + +// @ts-expect-error TS2323 Cannot redeclare. +export function WriteStream( + this: WriteStream, + path: FilePath, + options: WriteStreamOptions = {}, +): WriteStream { + if (!(this instanceof WriteStream)) { + return new WriteStream(path, options); + } + + if (options === null) { + options = {}; + } else if (typeof options === "string") { + options = { encoding: options }; + } + + validateObject(options, "options"); + const { + encoding = null, + autoClose = true, + emitClose = true, + start = 0, + highWaterMark = 64 * 1024, + signal = null, + flags = "w", + fd = null, + mode = 0o666, + fs = kDefaultFsOperations, + } = options; + let { flush = false } = options; + const autoDestroy = autoClose; + + if ( + encoding !== "buffer" && + encoding !== null && + !Buffer.isEncoding(encoding) + ) { + throw new ERR_INVALID_ARG_VALUE("options.encoding", encoding); + } + validateBoolean(autoClose, "options.autoClose"); + validateBoolean(emitClose, "options.emitClose"); + if (flush === null) flush = false; + validateBoolean(flush, "options.flush"); + validateUint32(start, "options.start"); + validateUint32(highWaterMark, "options.highWaterMark"); + if (signal != null) { + validateAbortSignal(signal, "options.signal"); + } + + validateString(flags, "options.flags"); + + // We don't actually use the mode in our implementation. Parsing it is + // just to ensure that it is validated. + parseFileMode(mode, "options.mode", 0o666); + + this[kFs] = bindFs(getValidatedFsOptions(fs)); + this.flush = flush; + + if (fd == null) { + this.fd = null; + // Path will be ignored when fd is specified, so it can be falsy + this.path = toPathIfFileURL(normalizePath(path)); + this.flags = options.flags === undefined ? "w" : options.flags; + this.mode = options.mode === undefined ? 0o666 : options.mode; + } else { + if (isFileHandle(fd)) { + if (fs !== kDefaultFsOperations) { + throw new ERR_METHOD_NOT_IMPLEMENTED("FileHandle with fs"); + } + this[kHandle] = fd as FileHandle; + this[kFs] = bindFs(getValidatedFsOptions(fd as unknown as FsOperations)); + const ee = fd as unknown as EventEmitter; + ee.on("close", () => this.close()); + this.fd = (fd as FileHandle).fd || null; + } else { + this.fd = getValidatedFd(fd as number); + } + } + + this.start = start; + this.pos = start; + this.bytesRead = 0; + this.bytesWritten = 0; + this[kIsPerformingIO] = false; + + Reflect.apply(Writable, this, [ + { + highWaterMark, + encoding, + emitClose, + autoDestroy, + signal, + construct: construct.bind(this), + write: writeImpl.bind(this), + writev: writevImpl.bind(this), + destroy: destroyImpl.bind(this), + }, + ]); + + if (encoding != null && encoding !== "buffer") { + (this as unknown as Writable).setDefaultEncoding( + encoding as BufferEncoding, + ); + } + + return this; +} +Object.setPrototypeOf(WriteStream.prototype, Writable.prototype); +Object.setPrototypeOf(WriteStream, Writable); + +Object.defineProperty(WriteStream.prototype, "autoClose", { + get(this: WriteStream): boolean { + validateThisInternalField(this, kFs, "WriteStream"); + return this._writableState?.autoDestroy || false; + }, + set(this: WriteStream, val: boolean): void { + validateThisInternalField(this, kFs, "WriteStream"); + if (this._writableState !== undefined) { + this._writableState.autoDestroy = val; + } + }, +}); + +WriteStream.prototype.close = function ( + this: WriteStream, + cb: ErrorOnlyCallback = (_err: unknown): void => {}, +): void { + const writable = this as unknown as Writable; + const ee = this as unknown as EventEmitter; + if (cb) { + if (writable.closed) { + queueMicrotask(() => cb(null)); + return; + } + ee.on("close", cb); + } + + // If we are not autoClosing, we should call + // destroy on 'finish'. + if (!this.autoClose) { + ee.on("finish", () => writable.destroy()); + } + + // We use end() instead of destroy() because of + // https://github.com/nodejs/node/issues/2006 + writable.end(); +}; + +// There is no shutdown() for files. +WriteStream.prototype.destroySoon = WriteStream.prototype.end; + +Object.defineProperty(WriteStream.prototype, "pending", { + get(this: WriteStream): boolean { + return this.fd === null; + }, + configurable: true, +}); + +export function createReadStream( + path: FilePath, + options: ReadStreamOptions = {}, +): ReadStream { + return new ReadStream(path, options); +} +export function createWriteStream( + path: FilePath, + options: WriteStreamOptions = {}, +): WriteStream { + return new WriteStream(path, options); +} diff --git a/src/compat/fs/upstream-access.fixture.mjs b/src/compat/fs/upstream-access.fixture.mjs new file mode 100644 index 00000000..5295c514 --- /dev/null +++ b/src/compat/fs/upstream-access.fixture.mjs @@ -0,0 +1,354 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { ok, rejects, strictEqual, throws } from "node:assert"; + +// Tests node:fs access and exists APIs +// These tests are not a straight up port of the Node.js tests but have +// been adapted and verified to match the behavior of the Node.js APIs +// as closely as possible. + +import { + accessSync, + existsSync, + access, + exists, + promises, + constants, + fstatSync, + closeSync, + openSync, + writeSync, + writeFileSync, + readSync, +} from "./index.js"; + +const { F_OK, R_OK, W_OK, X_OK } = constants; + +strictEqual(typeof accessSync, "function"); +strictEqual(typeof access, "function"); +strictEqual(typeof existsSync, "function"); +strictEqual(typeof exists, "function"); +strictEqual(typeof promises, "object"); +strictEqual(typeof promises.access, "function"); + +strictEqual(typeof F_OK, "number"); +strictEqual(typeof R_OK, "number"); +strictEqual(typeof W_OK, "number"); +strictEqual(typeof X_OK, "number"); +strictEqual(F_OK, 0); +strictEqual(X_OK, 1); +strictEqual(W_OK, 2); +strictEqual(R_OK, 4); + +const kKnownPaths = [ + { path: "/", writable: false }, + { path: "/bundle", writable: false }, + { path: "/bundle/worker", writable: false }, + { path: "/dev", writable: false }, + { path: "/dev/null", writable: true }, + { path: "/dev/zero", writable: true }, + { path: "/dev/full", writable: true }, + { path: "/dev/random", writable: true }, + { path: "/tmp", writable: true }, +]; + +const UV_ENOENT = -2; + +const kNoEntError = { + code: "ENOENT", + errno: UV_ENOENT, + syscall: "access", +}; + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; +const kOutOfRangeError = { code: "ERR_OUT_OF_RANGE" }; + +function mustNotCall() { + throw new Error("This function should not be called"); +} + +export const accessInputValidationTest = { + async test() { + // For both sync and async callback versions, input validation errors + // are thrown synchronously. + [ + 123n, + { + [Symbol.toPrimitive]() { + return R_OK; + }, + }, + "/", + ].forEach((i) => { + throws(() => accessSync("/", i), kInvalidArgTypeError); + throws(() => access("/", i, mustNotCall), kInvalidArgTypeError); + throws(() => access("/", F_OK, i), kInvalidArgTypeError); + }); + throws(() => accessSync(123), kInvalidArgTypeError); + throws(() => access(123, "", mustNotCall), kInvalidArgTypeError); + + // Mode is out of range + throws(() => accessSync("/", -1), kOutOfRangeError); + throws( + () => accessSync("/", (F_OK | R_OK | W_OK | X_OK) + 1), + kOutOfRangeError, + ); + + // For the promise version, input validation errors cause rejected promises. + await rejects(promises.access("/", 123n), kInvalidArgTypeError); + await rejects(promises.access(123), kInvalidArgTypeError); + await rejects(promises.access("/", -1), kOutOfRangeError); + await rejects( + promises.access("/", (F_OK | R_OK | W_OK | X_OK) + 1), + kOutOfRangeError, + ); + }, +}; + +export const accessSyncTest = { + test() { + kKnownPaths.forEach(({ path, writable }) => { + const bufferPath = Buffer.from(path); + const urlPath = new URL(path, "file:///"); + accessSync(path); + accessSync(bufferPath); + accessSync(urlPath); + accessSync(path, F_OK); + accessSync(bufferPath, F_OK); + accessSync(urlPath, F_OK); + accessSync(path, R_OK); + accessSync(bufferPath, R_OK); + accessSync(urlPath, R_OK); + + // Some paths are writable (W_OK) + if (writable) { + accessSync(path, W_OK); + accessSync(bufferPath, W_OK); + accessSync(urlPath, W_OK); + } else { + throws(() => accessSync(path, W_OK), kNoEntError); + throws(() => accessSync(bufferPath, W_OK), kNoEntError); + throws(() => accessSync(urlPath, W_OK), kNoEntError); + } + + // No path is executable (X_OK) + throws(() => accessSync(path, X_OK), kNoEntError); + throws(() => accessSync(bufferPath, X_OK), kNoEntError); + throws(() => accessSync(urlPath, X_OK), kNoEntError); + }); + + // Paths that don't exist... + throws(() => accessSync("/does/not/exist"), kNoEntError); + throws(() => accessSync("/does/not/exist", F_OK), kNoEntError); + throws(() => accessSync("/does/not/exist", R_OK), kNoEntError); + throws(() => accessSync("/does/not/exist", W_OK), kNoEntError); + throws(() => accessSync("/does/not/exist", X_OK), kNoEntError); + }, +}; + +export const accessCallbackTest = { + async test() { + const doTest = (path, mode) => { + const { promise, resolve, reject } = Promise.withResolvers(); + access(path, mode, (err) => { + if (err) reject(err); + else resolve(); + }); + return promise; + }; + + const cases = []; + kKnownPaths.forEach(({ path, writable }) => { + const bufferPath = Buffer.from(path); + const urlPath = new URL(path, "file:///"); + cases.push( + doTest(path, F_OK), + doTest(bufferPath, F_OK), + doTest(urlPath, F_OK), + doTest(path, R_OK), + doTest(bufferPath, R_OK), + doTest(urlPath, R_OK), + ); + if (writable) { + cases.push( + doTest(path, W_OK), + doTest(bufferPath, W_OK), + doTest(urlPath, W_OK), + ); + } else { + cases.push( + rejects(doTest(path, W_OK), kNoEntError), + rejects(doTest(bufferPath, W_OK), kNoEntError), + rejects(doTest(urlPath, W_OK), kNoEntError), + ); + } + // No paths are executable (X_OK) + cases.push( + rejects(doTest(path, X_OK), kNoEntError), + rejects(doTest(bufferPath, X_OK), kNoEntError), + rejects(doTest(urlPath, X_OK), kNoEntError), + ); + }); + // Paths that don't exist have no permissions. + cases.push( + rejects(doTest("/does/not/exist", F_OK), kNoEntError), + rejects(doTest("/does/not/exist", R_OK), kNoEntError), + rejects(doTest("/does/not/exist", W_OK), kNoEntError), + rejects(doTest("/does/not/exist", X_OK), kNoEntError), + ); + strictEqual(cases.length, kKnownPaths.length * 12 + 4); + await Promise.all(cases); + }, +}; + +export const accessPromiseTest = { + async test() { + const cases = []; + kKnownPaths.forEach(({ path, writable }) => { + const bufferPath = Buffer.from(path); + const urlPath = new URL(path, "file:///"); + cases.push( + promises.access(path, F_OK), + promises.access(bufferPath, F_OK), + promises.access(urlPath, F_OK), + promises.access(path, R_OK), + promises.access(bufferPath, R_OK), + promises.access(urlPath, R_OK), + ); + if (writable) { + cases.push( + promises.access(path, W_OK), + promises.access(bufferPath, W_OK), + promises.access(urlPath, W_OK), + ); + } else { + cases.push( + rejects(promises.access(path, W_OK), kNoEntError), + rejects(promises.access(bufferPath, W_OK), kNoEntError), + rejects(promises.access(urlPath, W_OK), kNoEntError), + ); + } + // No paths are executable (X_OK) + cases.push( + rejects(promises.access(path, X_OK), kNoEntError), + rejects(promises.access(bufferPath, X_OK), kNoEntError), + rejects(promises.access(urlPath, X_OK), kNoEntError), + ); + }); + // Paths that don't exist have no permissions. + cases.push( + rejects(promises.access("/does/not/exist", F_OK), kNoEntError), + rejects(promises.access("/does/not/exist", R_OK), kNoEntError), + rejects(promises.access("/does/not/exist", W_OK), kNoEntError), + rejects(promises.access("/does/not/exist", X_OK), kNoEntError), + ); + strictEqual(cases.length, kKnownPaths.length * 12 + 4); + await Promise.all(cases); + }, +}; + +export const existsSyncTest = { + test() { + kKnownPaths.forEach(({ path }) => { + const bufferPath = Buffer.from(path); + const urlPath = new URL(path, "file:///"); + ok(existsSync(path)); + ok(existsSync(bufferPath)); + ok(existsSync(urlPath)); + }); + + // Paths that don't exist + ok(!existsSync("/does/not/exist")); + // Incorrect inputs types results in false returned + ok(!existsSync(123)); + }, +}; + +export const existsCallbackTest = { + async test() { + const doTest = (path, expected) => { + const { promise, resolve, reject } = Promise.withResolvers(); + exists(path, (exists) => { + if (exists !== expected) + reject(new Error(`Expected ${path} to be ${expected}`)); + else resolve(); + }); + return promise; + }; + + const cases = []; + kKnownPaths.forEach(({ path }) => { + const bufferPath = Buffer.from(path); + const urlPath = new URL(path, "file:///"); + cases.push( + doTest(path, true), + doTest(bufferPath, true), + doTest(urlPath, true), + ); + }); + // Paths that don't exist + cases.push(doTest("/does/not/exist", false)); + // Incorrect inputs types results in false returned + cases.push(doTest(123, false)); + + strictEqual(cases.length, kKnownPaths.length * 3 + 2); + await Promise.all(cases); + }, +}; + +export const stdioFds = { + test() { + let stat1 = fstatSync(0); + let stat2 = fstatSync(1); + let stat3 = fstatSync(2); + ok(stat1.isFile()); + ok(stat2.isFile()); + ok(stat3.isFile()); + closeSync(0); + closeSync(1); + closeSync(2); + // After closing the stdio file descriptors, they can still be used. + // That is, we don't actually close them. + stat1 = fstatSync(0); + stat2 = fstatSync(1); + stat3 = fstatSync(2); + ok(stat1.isFile()); + ok(stat2.isFile()); + ok(stat3.isFile()); + + // Writing to the stdio file descriptors is permitted, always in append mode. + // Position is ignored. + writeSync(0, "Hello, stdin!\n", 1000); + writeSync(0, "test", 500); + writeSync(1, "Hello, stdin!\n", 1000); + writeSync(1, "test", 500); + writeSync(2, "Hello, stdin!\n", 1000); + writeSync(2, "test", 500); + + stat1 = fstatSync(0); + strictEqual(stat1.size, 0); + + const buf = Buffer.alloc(20); + const _bytesRead = readSync(0, buf, { position: 1000 }); + + const fd = openSync("/tmp/test.txt", "w"); + strictEqual(fd, 3); + }, +}; +// There is no promise version of exists in Node.js, so no test for it here. + +export const fsPercentEncodingTest = { + test() { + // The ? and # in the string path will be percent-encoded as part of the + // normalization, and the empty // in the path will be replaced as /. + // the %FF is left as is. + const path = "/tmp//?#a%FF"; + ok(!existsSync("/tmp/%3F%23a%FF")); + writeFileSync(path, "test"); + + // The casing of the percent-encoded characters is not significant. + ok(existsSync("/tmp/%3f%23a%FF")); + ok(existsSync("/tmp/%3F%23a%ff")); + }, +}; diff --git a/src/compat/fs/upstream-chown-chmod.fixture.mjs b/src/compat/fs/upstream-chown-chmod.fixture.mjs new file mode 100644 index 00000000..a1972715 --- /dev/null +++ b/src/compat/fs/upstream-chown-chmod.fixture.mjs @@ -0,0 +1,594 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { rejects, strictEqual, throws } from "node:assert"; + +// Tests node:fs chmod and chown APIS +// These tests are not a straight up port of the Node.js tests but have +// been adapted and verified to match the behavior of the Node.js APIs +// as closely as possible. + +// Our implementation of these APIs is mostly a non-op. The most we do +// is validate the input arguments and verify that the file exists and +// is accessible. The actual chown and chmod operations are not performed +// since we do not have a concept of users, groups, or posix-style permissions. + +import { + openSync, + closeSync, + statSync, + fstatSync, + lstatSync, + symlinkSync, + chmod, + lchmod, + fchmod, + chmodSync, + lchmodSync, + fchmodSync, + chown, + lchown, + fchown, + chownSync, + lchownSync, + fchownSync, + promises, +} from "./index.js"; + +strictEqual(typeof openSync, "function"); +strictEqual(typeof closeSync, "function"); +strictEqual(typeof statSync, "function"); +strictEqual(typeof fstatSync, "function"); +strictEqual(typeof lstatSync, "function"); +strictEqual(typeof symlinkSync, "function"); +strictEqual(typeof chmod, "function"); +strictEqual(typeof lchmod, "function"); +strictEqual(typeof fchmod, "function"); +strictEqual(typeof chmodSync, "function"); +strictEqual(typeof lchmodSync, "function"); +strictEqual(typeof fchmodSync, "function"); +strictEqual(typeof chown, "function"); +strictEqual(typeof lchown, "function"); +strictEqual(typeof fchown, "function"); +strictEqual(typeof chownSync, "function"); +strictEqual(typeof lchownSync, "function"); +strictEqual(typeof fchownSync, "function"); +strictEqual(typeof promises.chown, "function"); +strictEqual(typeof promises.lchown, "function"); + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; +const kOutOfRangeError = { code: "ERR_OUT_OF_RANGE" }; + +function checkStat(path) { + const { uid, gid } = statSync(path); + strictEqual(uid, 0); + strictEqual(gid, 0); +} + +function checkfStat(fd) { + const { uid, gid } = fstatSync(fd); + strictEqual(uid, 0); + strictEqual(gid, 0); +} + +const path = "/tmp"; +const bufferPath = Buffer.from(path); +const urlPath = new URL(path, "file:///"); + +export const chownSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => chownSync(123), kInvalidArgTypeError); + throws(() => chownSync("/", {}), kInvalidArgTypeError); + throws(() => chownSync("/", 0, {}), kInvalidArgTypeError); + throws(() => chownSync(path, -1000, 0), kOutOfRangeError); + throws(() => chownSync(path, 0, -1000), kOutOfRangeError); + + // We stat the file before and after to verify the impact + // of the chown operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkStat(path); + chownSync(path, 1000, 1000); + checkStat(path); + + chownSync(bufferPath, 1000, 1000); + checkStat(bufferPath); + + chownSync(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + throws(() => chownSync("/non-existent-path", 1000, 1000), { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "chown", + }); + }, +}; + +export const chownCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => chown(123), kInvalidArgTypeError); + throws(() => chown("/", {}), kInvalidArgTypeError); + throws(() => chown("/", 0, {}), kInvalidArgTypeError); + throws(() => chownSync(path, -1000, 0), kOutOfRangeError); + throws(() => chownSync(path, 0, -1000), kOutOfRangeError); + + async function callChown(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + chown(path, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkStat(path); + await callChown(path); + checkStat(path); + + await callChown(bufferPath); + checkStat(bufferPath); + + await callChown(urlPath); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + const { promise, resolve, reject } = Promise.withResolvers(); + chown("/non-existent-path", 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "chown", + }); + }, +}; + +export const chownPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.chown(123), kInvalidArgTypeError); + await rejects(promises.chown("/", {}), kInvalidArgTypeError); + await rejects(promises.chown("/", 0, {}), kInvalidArgTypeError); + await rejects(promises.chown(path, -1000, 0), kOutOfRangeError); + await rejects(promises.chown(path, 0, -1000), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.chown(path, 1000, 1000); + checkStat(path); + + await promises.chown(bufferPath, 1000, 1000); + checkStat(bufferPath); + + await promises.chown(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + await rejects(promises.chown("/non-existent-path", 1000, 1000), { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "chown", + }); + }, +}; + +export const lchownSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => lchownSync(123), kInvalidArgTypeError); + throws(() => lchownSync("/", {}), kInvalidArgTypeError); + throws(() => lchownSync("/", 0, {}), kInvalidArgTypeError); + throws(() => lchownSync(path, -1000, 0), kOutOfRangeError); + throws(() => lchownSync(path, 0, -1000), kOutOfRangeError); + + // We stat the file before and after to verify the impact + // of the chown operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkStat(path); + lchownSync(path, 1000, 1000); + checkStat(path); + + lchownSync(bufferPath, 1000, 1000); + checkStat(bufferPath); + + lchownSync(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + throws(() => lchownSync("/non-existent-path", 1000, 1000), { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "lchown", + }); + }, +}; + +export const lchownCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => lchown(123), kInvalidArgTypeError); + throws(() => lchown("/", {}), kInvalidArgTypeError); + throws(() => lchown("/", 0, {}), kInvalidArgTypeError); + throws(() => lchownSync(path, -1000, 0), kOutOfRangeError); + throws(() => lchownSync(path, 0, -1000), kOutOfRangeError); + + async function callChown(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + lchown(path, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkStat(path); + await callChown(path); + checkStat(path); + + await callChown(bufferPath); + checkStat(bufferPath); + + await callChown(urlPath); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + const { promise, resolve, reject } = Promise.withResolvers(); + lchown("/non-existent-path", 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "lchown", + }); + }, +}; + +export const lchownPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.lchown(123), kInvalidArgTypeError); + await rejects(promises.lchown("/", {}), kInvalidArgTypeError); + await rejects(promises.lchown("/", 0, {}), kInvalidArgTypeError); + await rejects(promises.lchown(path, -1000, 0), kOutOfRangeError); + await rejects(promises.lchown(path, 0, -1000), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.lchown(path, 1000, 1000); + checkStat(path); + + await promises.lchown(bufferPath, 1000, 1000); + checkStat(bufferPath); + + await promises.lchown(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + await rejects(promises.lchown("/non-existent-path", 1000, 1000), { + code: "ENOENT", + syscall: "lchown", + }); + }, +}; + +export const fchownSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => fchownSync({}), kInvalidArgTypeError); + throws(() => fchownSync(123), kInvalidArgTypeError); + throws(() => fchownSync(123, {}), kInvalidArgTypeError); + throws(() => fchownSync(123, 0, {}), kInvalidArgTypeError); + throws(() => fchownSync(123, -1000, 0), kOutOfRangeError); + throws(() => fchownSync(123, 0, -1000), kOutOfRangeError); + + const fd = openSync("/tmp"); + + // We stat the file before and after to verify the impact + // of the chown operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkfStat(fd); + fchownSync(fd, 1000, 1000); + checkfStat(fd); + + throws(() => fchownSync(999, 1000, 1000), { + code: "EBADF", + syscall: "fstat", + }); + + closeSync(fd); + }, +}; + +export const fchownCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => fchown({}), kInvalidArgTypeError); + throws(() => fchown(123), kInvalidArgTypeError); + throws(() => fchown(123, {}), kInvalidArgTypeError); + throws(() => fchown(123, 0, {}), kInvalidArgTypeError); + throws(() => fchown(123, -1000, 0), kOutOfRangeError); + throws(() => fchown(123, 0, -1000), kOutOfRangeError); + + const fd = openSync("/tmp"); + + async function callChown() { + const { promise, resolve, reject } = Promise.withResolvers(); + fchown(fd, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkfStat(fd); + await callChown(); + checkfStat(fd); + + const { promise, resolve, reject } = Promise.withResolvers(); + fchown(999, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: "EBADF", + syscall: "fstat", + }); + + closeSync(fd); + }, +}; + +// =========================================================================== + +export const chmodSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => chmodSync(123), kInvalidArgTypeError); + throws(() => chmodSync("/", {}), kInvalidArgTypeError); + throws(() => chmodSync("/tmp", -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + chmodSync(path, 0o777); + checkStat(path); + + chmodSync(bufferPath, 0o777); + checkStat(bufferPath); + + chmodSync(urlPath, 0o777); + checkStat(urlPath); + + throws(() => chmodSync("/non-existent-path", 0o777), { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "chmod", + }); + }, +}; + +export const chmodCallbackTest = { + async test() { + // Incorrect input types should throw. + throws(() => chmod(123), kInvalidArgTypeError); + throws(() => chmod("/", {}), kInvalidArgTypeError); + throws(() => chmod("/tmp", -1), kOutOfRangeError); + + async function callChmod(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + chmod(path, 0o000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + checkStat(path); + await callChmod(path); + checkStat(path); + + await callChmod(bufferPath); + checkStat(bufferPath); + + await callChmod(urlPath); + checkStat(bufferPath); + + const { promise, resolve, reject } = Promise.withResolvers(); + chmod("/non-existent-path", 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "chmod", + }); + }, +}; + +export const chmodPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.chmod(123), kInvalidArgTypeError); + await rejects(promises.chmod("/", {}), kInvalidArgTypeError); + await rejects(promises.chmod("/tmp", -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.chmod(path, 0o777); + checkStat(path); + + await promises.chmod(bufferPath, 0o777); + checkStat(bufferPath); + + await promises.chmod(urlPath, 0o777); + checkStat(urlPath); + + await rejects(promises.chmod("/non-existent-path", 0o777), { + code: "ENOENT", + syscall: "chmod", + }); + }, +}; + +export const lchmodSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => lchmodSync(123), kInvalidArgTypeError); + throws(() => lchmodSync("/", {}), kInvalidArgTypeError); + throws(() => lchmodSync("/tmp", -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + lchmodSync(path, 0o777); + checkStat(path); + + lchmodSync(bufferPath, 0o777); + checkStat(bufferPath); + + lchmodSync(urlPath, 0o777); + checkStat(urlPath); + + throws(() => lchmodSync("/non-existent-path", 0o777), { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "lchmod", + }); + }, +}; + +export const lchmodCallbackTest = { + async test() { + // Incorrect input types should throw. + throws(() => lchmod(123), kInvalidArgTypeError); + throws(() => lchmod("/", {}), kInvalidArgTypeError); + throws(() => lchmod("/tmp", -1), kOutOfRangeError); + + async function callChmod(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + lchmod(path, 0o000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + checkStat(path); + await callChmod(path); + checkStat(path); + + await callChmod(bufferPath); + checkStat(bufferPath); + + await callChmod(urlPath); + checkStat(bufferPath); + + const { promise, resolve, reject } = Promise.withResolvers(); + lchmod("/non-existent-path", 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: "ENOENT", + // Access because it is an access check under the covers. + syscall: "lchmod", + }); + }, +}; + +export const lchmodPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.lchmod(123), kInvalidArgTypeError); + await rejects(promises.lchmod("/", {}), kInvalidArgTypeError); + await rejects(promises.lchmod("/tmp", -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.lchmod(path, 0o777); + checkStat(path); + + await promises.lchmod(bufferPath, 0o777); + checkStat(bufferPath); + + await promises.lchmod(urlPath, 0o777); + checkStat(urlPath); + + await rejects(promises.lchmod("/non-existent-path", 0o777), { + code: "ENOENT", + syscall: "lchmod", + }); + }, +}; + +export const fchmodSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => fchmodSync({}), kInvalidArgTypeError); + throws(() => fchmodSync(123), kInvalidArgTypeError); + throws(() => fchmodSync(123, {}), kInvalidArgTypeError); + throws(() => fchmodSync(123, -1000), kOutOfRangeError); + + const fd = openSync("/tmp"); + + // We stat the file before and after to verify the impact + // of the chown operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkfStat(fd); + fchmodSync(fd, 0o777); + checkfStat(fd); + + throws(() => fchmodSync(999, 0o777), { + code: "EBADF", + syscall: "fstat", + }); + + closeSync(fd); + }, +}; + +export const fchmodCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => fchmod({}), kInvalidArgTypeError); + throws(() => fchmod(123), kInvalidArgTypeError); + throws(() => fchmod(123, {}), kInvalidArgTypeError); + + const fd = openSync("/tmp"); + + async function callChmod() { + const { promise, resolve, reject } = Promise.withResolvers(); + fchmod(fd, 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkfStat(fd); + await callChmod(); + checkfStat(fd); + + const { promise, resolve, reject } = Promise.withResolvers(); + fchmod(999, 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: "EBADF", + syscall: "fstat", + }); + + closeSync(fd); + }, +}; diff --git a/src/compat/fs/upstream-cp-into-subdirectory.fixture.mjs b/src/compat/fs/upstream-cp-into-subdirectory.fixture.mjs new file mode 100644 index 00000000..3b491c0c --- /dev/null +++ b/src/compat/fs/upstream-cp-into-subdirectory.fixture.mjs @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { + cpSync, + mkdirSync, + readFileSync, + readdirSync, + symlinkSync, + writeFileSync, +} from "./index.js"; +import { strictEqual, throws } from "node:assert"; + +export const FsCpIntoSubdirectory = { + test() { + mkdirSync(new URL("file:///tmp/src"), { recursive: true }); + writeFileSync(new URL("file:///tmp/src/file.txt"), "test"); + + throws( + () => + cpSync(new URL("file:///tmp/src"), new URL("file:///tmp/src/dest"), { + recursive: true, + }), + { + code: "ERR_FS_CP_EINVAL", + }, + ); + }, +}; + +export const FsCpDestinationSymlinkToSource = { + test() { + const src = new URL("file:///tmp/src"); + const dest = new URL("file:///tmp/dest"); + + mkdirSync(new URL("file:///tmp/src/child"), { recursive: true }); + writeFileSync(new URL("file:///tmp/src/child/payload.txt"), "payload"); + writeFileSync(new URL("file:///tmp/src/a.txt"), "a"); + strictEqual(readdirSync(src).length, 2); + + mkdirSync(dest); + // /tmp/dest/child --> /tmp/src + symlinkSync(src, new URL("file:///tmp/dest/child")); + + cpSync(src, dest, { recursive: true, dereference: true }); + strictEqual(readFileSync(new URL("file:///tmp/dest/a.txt"), "utf8"), "a"); + strictEqual( + readFileSync(new URL("file:///tmp/src/payload.txt"), "utf8"), + "payload", + ); + strictEqual( + readFileSync(new URL("file:///tmp/src/child/payload.txt"), "utf8"), + "payload", + ); + }, +}; diff --git a/src/compat/fs/upstream-cp.fixture.mjs b/src/compat/fs/upstream-cp.fixture.mjs new file mode 100644 index 00000000..1a1c1015 --- /dev/null +++ b/src/compat/fs/upstream-cp.fixture.mjs @@ -0,0 +1,2149 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { + writeFileSync, + mkdirSync, + symlinkSync, + cpSync, + cp, + existsSync, + readFileSync, + lstatSync, + statSync, + promises as fsPromises, +} from "./index.js"; + +import { ok, deepStrictEqual, strictEqual, throws, rejects } from "node:assert"; + +const pathA = new URL("file:///tmp/a"); // File +const pathB = new URL("file:///tmp/b"); // Directory +const pathBE = new URL("file:///tmp/b/e"); // File in directory +const pathC = new URL("file:///tmp/c"); // Symlink to file +const pathD = new URL("file:///tmp/d"); // Symlink to directory +const pathE = new URL("file:///tmp/e"); // Directory +const pathF = new URL("file:///tmp/f"); // Path to copy to + +function setupPaths() { + writeFileSync(pathA, "foo"); + mkdirSync(pathB); + symlinkSync(pathA, pathC); + symlinkSync(pathB, pathD); + mkdirSync(pathE); + writeFileSync(pathBE, "bar"); +} + +export const simpleFileCopy = { + test() { + setupPaths(); + ok(existsSync(pathA)); + deepStrictEqual(readFileSync(pathA, "utf8"), "foo"); + ok(!existsSync(pathF)); + cpSync(pathA, pathF); + ok(existsSync(pathF)); + deepStrictEqual(readFileSync(pathF, "utf8"), "foo"); + writeFileSync(pathA, "baz"); + deepStrictEqual(readFileSync(pathA, "utf8"), "baz"); + deepStrictEqual(readFileSync(pathF, "utf8"), "foo"); + cpSync(pathA, pathF); + cpSync(pathA, pathF, { errorOnExist: true }); // force is true by default, weird but true. + + // Copying a file when the destination already exists and force is false and + // errorOnExist is true, should throw an error. + throws(() => cpSync(pathA, pathF, { force: false, errorOnExist: true }), { + code: "ERR_FS_CP_EEXIST", + }); + + // Copying a file to a directory should throw an error. + throws(() => cpSync(pathA, pathB), { + code: "ERR_FS_CP_NON_DIR_TO_DIR", + }); + + // Copy a file to a symlink to a directory should throw by default + throws(() => cpSync(pathA, pathD, { dereference: true }), { + code: "ERR_FS_CP_NON_DIR_TO_DIR", + }); + + ok(statSync(pathD).isDirectory()); + ok(lstatSync(pathD).isSymbolicLink()); + cpSync(pathA, pathD); + ok(lstatSync(pathD).isFile()); + }, +}; + +// Copy file to non-existent directory (should fail) +export const copyFileToNonExistentDirectory = { + test() { + setupPaths(); + // The directory will be created by cpSync if it doesn't exist. + const pathG = new URL("file:///tmp/nonexistent"); + const pathH = new URL("file:///tmp/nonexistent/g"); + ok(!existsSync(pathG)); + ok(!existsSync(pathH)); + cpSync(pathA, pathH); + ok(existsSync(pathG)); + ok(existsSync(pathH)); + }, +}; + +// Copy file to existing file with force +export const copyFileToExistingFileWithForce = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + cpSync(pathA, pathG, { force: true }); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Copy file to existing file without force +export const copyFileToExistingFileWithoutForce = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + // When force is false, it should not overwrite the existing file. + cpSync(pathA, pathG, { force: false }); + deepStrictEqual(readFileSync(pathG, "utf8"), "original"); + }, +}; + +// Copy file to existing directory +export const copyFileToExistingDirectory = { + test() { + setupPaths(); + throws(() => cpSync(pathA, pathB), { + code: "ERR_FS_CP_NON_DIR_TO_DIR", + }); + }, +}; + +// Copy file to existing symlink to directory +export const copyFileToExistingSymlinkToDirectory = { + test() { + setupPaths(); + // With dereference: true, should throw EISDIR + throws(() => cpSync(pathA, pathD, { dereference: true }), { + code: "ERR_FS_CP_NON_DIR_TO_DIR", + }); + + // Without dereference (default), should replace the symlink + cpSync(pathA, pathD); + ok(lstatSync(pathD).isFile()); + deepStrictEqual(readFileSync(pathD, "utf8"), "foo"); + }, +}; + +// Copy directory to non-existent directory +export const copyDirectoryToNonExistentDirectory = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + cpSync(pathB, pathG, { recursive: true }); + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + ok(existsSync(new URL("file:///tmp/g/e"))); + deepStrictEqual(readFileSync(new URL("file:///tmp/g/e"), "utf8"), "bar"); + }, +}; + +// Copy directory to existing directory +export const copyDirectoryToExistingDirectory = { + test() { + setupPaths(); + cpSync(pathB, pathE, { recursive: true }); + ok(existsSync(new URL("file:///tmp/e/e"))); + deepStrictEqual(readFileSync(new URL("file:///tmp/e/e"), "utf8"), "bar"); + }, +}; + +// Copy directory to existing file +export const copyDirectoryToExistingFile = { + test() { + setupPaths(); + throws(() => cpSync(pathB, pathA, { recursive: true }), { + code: "ERR_FS_CP_DIR_TO_NON_DIR", + }); + }, +}; + +// Copy directory to existing symlink to file +export const copyDirectoryToExistingSymlinkToFile = { + test() { + setupPaths(); + throws(() => cpSync(pathB, pathC, { recursive: true, dereference: true }), { + code: "ERR_FS_CP_DIR_TO_NON_DIR", + }); + }, +}; + +// Copy symlink to file +export const copySymlinkToFile = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // With dereference: false (default), should copy the symlink + cpSync(pathC, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + + // With dereference: true, should copy the target file + const pathH = new URL("file:///tmp/h"); + cpSync(pathC, pathH, { dereference: true }); + ok(lstatSync(pathH).isFile()); + deepStrictEqual(readFileSync(pathH, "utf8"), "foo"); + }, +}; + +// Copy symlink to directory +export const copySymlinkToDirectory = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // With dereference: false (default), should copy the symlink + cpSync(pathD, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + ok(statSync(pathG).isDirectory()); + + // With dereference: true, should copy the target directory + const pathH = new URL("file:///tmp/h"); + cpSync(pathD, pathH, { recursive: true, dereference: true }); + ok(lstatSync(pathH).isDirectory()); + ok(existsSync(new URL("file:///tmp/h/e"))); + }, +}; + +// Copy symlink to existing file +export const copySymlinkToExistingFile = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + // Should replace the file with symlink + cpSync(pathC, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Copy symlink to existing directory +export const copySymlinkToExistingDirectory = { + test() { + setupPaths(); + throws(() => cpSync(pathC, pathE), { + code: "ENOTDIR", + }); + }, +}; + +// Copy symlink to existing symlink to directory +export const copySymlinkToExistingSymlinkToDirectory = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + mkdirSync(pathH); + symlinkSync(pathH, pathG); + + // Should replace the symlink + cpSync(pathD, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + ok(statSync(pathG).isDirectory()); + }, +}; + +// Copy symlink operations with various option combinations +export const copySymlinkWithDereferenceOptions = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + const pathI = new URL("file:///tmp/i"); + + // Test with force: false, errorOnExist: true + symlinkSync(pathA, pathG); + throws(() => cpSync(pathC, pathG, { force: false, errorOnExist: true }), { + code: "ERR_FS_CP_EEXIST", + }); + + // Test with force: false, errorOnExist: false + cpSync(pathC, pathG, { force: false, errorOnExist: false }); + ok(lstatSync(pathG).isSymbolicLink()); + + // Test with dereference and errorOnExist combinations + writeFileSync(pathH, "existing"); + throws( + () => + cpSync(pathC, pathH, { + dereference: true, + force: false, + errorOnExist: true, + }), + { + code: "ERR_FS_CP_EEXIST", + }, + ); + + // Test copying to non-existent path + cpSync(pathC, pathI); + ok(lstatSync(pathI).isSymbolicLink()); + deepStrictEqual(readFileSync(pathI, "utf8"), "foo"); + }, +}; + +// Option validation tests +export const optionValidationTests = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // Test invalid option types + throws(() => cpSync(pathA, pathG, { dereference: "invalid" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cpSync(pathA, pathG, { errorOnExist: "invalid" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cpSync(pathA, pathG, { force: "invalid" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cpSync(pathA, pathG, { recursive: "invalid" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cpSync(pathA, pathG, { preserveTimestamps: "invalid" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cpSync(pathA, pathG, { verbatimSymlinks: "invalid" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + // Test invalid filter type + throws(() => cpSync(pathA, pathG, { filter: "not-a-function" }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + // Test filter option throws unsupported operation + throws(() => cpSync(pathA, pathG, { filter: () => true }), { + code: "ERR_UNSUPPORTED_OPERATION", + }); + + // Test COPYFILE_FICLONE_FORCE mode + throws(() => cpSync(pathA, pathG, { mode: 4 }), { + // COPYFILE_FICLONE_FORCE = 4 + code: "ERR_INVALID_ARG_VALUE", + }); + }, +}; + +// Recursive option tests +export const recursiveOptionTests = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + + // Test copying directory without recursive option should fail + throws(() => cpSync(pathB, pathG), { + code: "ERR_FS_EISDIR", + }); + + // Test with recursive: false explicitly + throws(() => cpSync(pathB, pathG, { recursive: false }), { + code: "ERR_FS_EISDIR", + }); + + // Test copying directory with recursive: true should work + cpSync(pathB, pathG, { recursive: true }); + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + ok(existsSync(new URL("file:///tmp/g/e"))); + + // Test deep directory structure + mkdirSync(new URL("file:///tmp/deep")); + mkdirSync(new URL("file:///tmp/deep/nested")); + mkdirSync(new URL("file:///tmp/deep/nested/very")); + writeFileSync(new URL("file:///tmp/deep/nested/very/deep.txt"), "content"); + + cpSync(new URL("file:///tmp/deep"), pathH, { recursive: true }); + ok(existsSync(new URL("file:///tmp/h/nested/very/deep.txt"))); + deepStrictEqual( + readFileSync(new URL("file:///tmp/h/nested/very/deep.txt"), "utf8"), + "content", + ); + }, +}; + +// PreserveTimestamps option tests +export const preserveTimestampsTests = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + + // Get original timestamps + const originalStat = lstatSync(pathA); + const originalMtime = originalStat.mtime; + + // Copy without preserveTimestamps (default behavior) + cpSync(pathA, pathG); + const _copiedStat = lstatSync(pathG); + + // Copy with preserveTimestamps: true + cpSync(pathA, pathH, { preserveTimestamps: true }); + const preservedStat = lstatSync(pathH); + + // The preserved timestamps should match the original + // Note: This test depends on the implementation actually preserving timestamps + // Since the implementation is noted as having this option, we test it + ok( + Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000, + ); + }, +}; + +// Error handling edge cases +export const errorHandlingTests = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const nonExistent = new URL("file:///tmp/nonexistent"); + + // Test copying from non-existent source + throws(() => cpSync(nonExistent, pathG), { + code: "ENOENT", + }); + }, +}; + +// Symlink edge cases +export const symlinkEdgeCases = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + const pathI = new URL("file:///tmp/i"); + const pathJ = new URL("file:///tmp/j"); + + // Create a broken symlink + const brokenTarget = new URL("file:///tmp/broken-target"); + symlinkSync(brokenTarget, pathG); + + // Test copying broken symlink without dereference + cpSync(pathG, pathH); + ok(lstatSync(pathH).isSymbolicLink()); + + // Test copying broken symlink with dereference should fail + throws(() => cpSync(pathG, pathI, { dereference: true }), { + code: "ENOENT", + }); + + // Create symlink chain: J -> I -> A + symlinkSync(pathA, pathI); + symlinkSync(pathI, pathJ); + + // Test copying symlink chain + const pathK = new URL("file:///tmp/k"); + cpSync(pathJ, pathK); + ok(lstatSync(pathK).isSymbolicLink()); + deepStrictEqual(readFileSync(pathK, "utf8"), "foo"); + }, +}; + +// Combined options tests +export const combinedOptionsTests = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // Test multiple options together + cpSync(pathB, pathG, { + recursive: true, + dereference: true, + force: true, + preserveTimestamps: true, + errorOnExist: false, + }); + + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + }, +}; + +// Path type tests +export const pathTypeTests = { + test() { + setupPaths(); + + // Test with Buffer paths + const pathG = new URL("file:///tmp/g"); + const bufferSrc = Buffer.from("/tmp/a"); + const bufferDest = Buffer.from("/tmp/g"); + + // Note: The implementation uses normalizePath which should handle Buffer inputs + // This tests the path normalization behavior + cpSync(bufferSrc, bufferDest); + ok(existsSync(pathG)); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// ===== CALLBACK-BASED API TESTS ===== + +// Simple file copy - callback version +export const simpleFileCopyCallback = { + async test() { + setupPaths(); + ok(existsSync(pathA)); + deepStrictEqual(readFileSync(pathA, "utf8"), "foo"); + ok(!existsSync(pathF)); + + await new Promise((resolve, reject) => { + cp(pathA, pathF, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(pathF)); + deepStrictEqual(readFileSync(pathF, "utf8"), "foo"); + writeFileSync(pathA, "baz"); + deepStrictEqual(readFileSync(pathA, "utf8"), "baz"); + deepStrictEqual(readFileSync(pathF, "utf8"), "foo"); + + // Test with errorOnExist: true + await new Promise((resolve, reject) => { + cp(pathA, pathF, { errorOnExist: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + // Test error case: force: false, errorOnExist: true + await new Promise((resolve) => { + cp(pathA, pathF, { force: false, errorOnExist: true }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_EEXIST"); + resolve(); + }); + }); + + // Test error case: copying file to directory + await new Promise((resolve) => { + cp(pathA, pathB, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_NON_DIR_TO_DIR"); + resolve(); + }); + }); + + // Test copying to symlink to directory + await new Promise((resolve) => { + cp(pathA, pathD, { dereference: true }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_NON_DIR_TO_DIR"); + resolve(); + }); + }); + + // Test copying to symlink without dereference + await new Promise((resolve, reject) => { + cp(pathA, pathD, (err) => { + if (err) reject(err); + else { + ok(lstatSync(pathD).isFile()); + resolve(); + } + }); + }); + }, +}; + +// Copy file to non-existent directory - callback version +export const copyFileToNonExistentDirectoryCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/nonexistent"); + const pathH = new URL("file:///tmp/nonexistent/g"); + ok(!existsSync(pathG)); + ok(!existsSync(pathH)); + + await new Promise((resolve, reject) => { + cp(pathA, pathH, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(pathG)); + ok(existsSync(pathH)); + }, +}; + +// Copy file to existing file with force - callback version +export const copyFileToExistingFileWithForceCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + await new Promise((resolve, reject) => { + cp(pathA, pathG, { force: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Copy file to existing file without force - callback version +export const copyFileToExistingFileWithoutForceCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + await new Promise((resolve, reject) => { + cp(pathA, pathG, { force: false }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + deepStrictEqual(readFileSync(pathG, "utf8"), "original"); + }, +}; + +// Copy file to existing directory - callback version +export const copyFileToExistingDirectoryCallback = { + async test() { + setupPaths(); + + await new Promise((resolve) => { + cp(pathA, pathB, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_NON_DIR_TO_DIR"); + resolve(); + }); + }); + }, +}; + +// Copy file to existing symlink to directory - callback version +export const copyFileToExistingSymlinkToDirectoryCallback = { + async test() { + setupPaths(); + + // With dereference: true, should throw EISDIR + await new Promise((resolve) => { + cp(pathA, pathD, { dereference: true }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_NON_DIR_TO_DIR"); + resolve(); + }); + }); + + // Without dereference (default), should replace the symlink + await new Promise((resolve, reject) => { + cp(pathA, pathD, (err) => { + if (err) reject(err); + else { + ok(lstatSync(pathD).isFile()); + deepStrictEqual(readFileSync(pathD, "utf8"), "foo"); + resolve(); + } + }); + }); + }, +}; + +// Copy directory to non-existent directory - callback version +export const copyDirectoryToNonExistentDirectoryCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + await new Promise((resolve, reject) => { + cp(pathB, pathG, { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + ok(existsSync(new URL("file:///tmp/g/e"))); + deepStrictEqual(readFileSync(new URL("file:///tmp/g/e"), "utf8"), "bar"); + }, +}; + +// Copy directory to existing directory - callback version +export const copyDirectoryToExistingDirectoryCallback = { + async test() { + setupPaths(); + + await new Promise((resolve, reject) => { + cp(pathB, pathE, { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(new URL("file:///tmp/e/e"))); + deepStrictEqual(readFileSync(new URL("file:///tmp/e/e"), "utf8"), "bar"); + }, +}; + +// Copy directory to existing file - callback version +export const copyDirectoryToExistingFileCallback = { + async test() { + setupPaths(); + + await new Promise((resolve) => { + cp(pathB, pathA, { recursive: true }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_DIR_TO_NON_DIR"); + resolve(); + }); + }); + }, +}; + +// Copy directory to existing symlink to file - callback version +export const copyDirectoryToExistingSymlinkToFileCallback = { + async test() { + setupPaths(); + + await new Promise((resolve) => { + cp(pathB, pathC, { recursive: true, dereference: true }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_DIR_TO_NON_DIR"); + resolve(); + }); + }); + }, +}; + +// Copy symlink to file - callback version +export const copySymlinkToFileCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // With dereference: false (default), should copy the symlink + await new Promise((resolve, reject) => { + cp(pathC, pathG, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathG).isSymbolicLink()); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + + // With dereference: true, should copy the target file + const pathH = new URL("file:///tmp/h"); + await new Promise((resolve, reject) => { + cp(pathC, pathH, { dereference: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathH).isFile()); + deepStrictEqual(readFileSync(pathH, "utf8"), "foo"); + }, +}; + +// Copy symlink to directory - callback version +export const copySymlinkToDirectoryCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // With dereference: false (default), should copy the symlink + await new Promise((resolve, reject) => { + cp(pathD, pathG, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathG).isSymbolicLink()); + ok(statSync(pathG).isDirectory()); + + // With dereference: true, should copy the target directory + const pathH = new URL("file:///tmp/h"); + await new Promise((resolve, reject) => { + cp(pathD, pathH, { recursive: true, dereference: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathH).isDirectory()); + ok(existsSync(new URL("file:///tmp/h/e"))); + }, +}; + +// Copy symlink to existing file - callback version +export const copySymlinkToExistingFileCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + // Should replace the file with symlink + await new Promise((resolve, reject) => { + cp(pathC, pathG, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathG).isSymbolicLink()); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Copy symlink to existing directory - callback version +export const copySymlinkToExistingDirectoryCallback = { + async test() { + setupPaths(); + + await new Promise((resolve) => { + cp(pathC, pathE, (err) => { + ok(err); + strictEqual(err.code, "ENOTDIR"); + resolve(); + }); + }); + }, +}; + +// Copy symlink to existing symlink to directory - callback version +export const copySymlinkToExistingSymlinkToDirectoryCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + mkdirSync(pathH); + symlinkSync(pathH, pathG); + + // Should replace the symlink + await new Promise((resolve, reject) => { + cp(pathD, pathG, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathG).isSymbolicLink()); + ok(statSync(pathG).isDirectory()); + }, +}; + +// Copy symlink operations with various option combinations - callback version +export const copySymlinkWithDereferenceOptionsCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + const pathI = new URL("file:///tmp/i"); + + // Test with force: false, errorOnExist: true + symlinkSync(pathA, pathG); + await new Promise((resolve) => { + cp(pathC, pathG, { force: false, errorOnExist: true }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_EEXIST"); + resolve(); + }); + }); + + // Test with force: false, errorOnExist: false + await new Promise((resolve, reject) => { + cp(pathC, pathG, { force: false, errorOnExist: false }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(lstatSync(pathG).isSymbolicLink()); + + // Test with dereference and errorOnExist combinations + writeFileSync(pathH, "existing"); + await new Promise((resolve) => { + cp( + pathC, + pathH, + { dereference: true, force: false, errorOnExist: true }, + (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_CP_EEXIST"); + resolve(); + }, + ); + }); + + // Test copying to non-existent path + await new Promise((resolve, reject) => { + cp(pathC, pathI, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(lstatSync(pathI).isSymbolicLink()); + deepStrictEqual(readFileSync(pathI, "utf8"), "foo"); + }, +}; + +// Option validation tests - callback version +export const optionValidationTestsCallback = { + test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // Test invalid option types - these throw synchronously + throws(() => cp(pathA, pathG, { dereference: "invalid" }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cp(pathA, pathG, { errorOnExist: "invalid" }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cp(pathA, pathG, { force: "invalid" }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => cp(pathA, pathG, { recursive: "invalid" }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws( + () => cp(pathA, pathG, { preserveTimestamps: "invalid" }, () => {}), + { + code: "ERR_INVALID_ARG_TYPE", + }, + ); + + throws(() => cp(pathA, pathG, { verbatimSymlinks: "invalid" }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + // Test invalid filter type - throws synchronously + throws(() => cp(pathA, pathG, { filter: "not-a-function" }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + // Test filter option throws unsupported operation - throws synchronously + throws(() => cp(pathA, pathG, { filter: () => true }, () => {}), { + code: "ERR_UNSUPPORTED_OPERATION", + }); + + // Test COPYFILE_FICLONE_FORCE mode - throws synchronously + throws(() => cp(pathA, pathG, { mode: 4 }, () => {}), { + code: "ERR_INVALID_ARG_VALUE", + }); + }, +}; + +// Recursive option tests - callback version +export const recursiveOptionTestsCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + + // Test copying directory without recursive option should fail + await new Promise((resolve) => { + cp(pathB, pathG, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_EISDIR"); + resolve(); + }); + }); + + // Test with recursive: false explicitly + await new Promise((resolve) => { + cp(pathB, pathG, { recursive: false }, (err) => { + ok(err); + strictEqual(err.code, "ERR_FS_EISDIR"); + resolve(); + }); + }); + + // Test copying directory with recursive: true should work + await new Promise((resolve, reject) => { + cp(pathB, pathG, { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + ok(existsSync(new URL("file:///tmp/g/e"))); + + // Test deep directory structure + mkdirSync(new URL("file:///tmp/deep")); + mkdirSync(new URL("file:///tmp/deep/nested")); + mkdirSync(new URL("file:///tmp/deep/nested/very")); + writeFileSync(new URL("file:///tmp/deep/nested/very/deep.txt"), "content"); + + await new Promise((resolve, reject) => { + cp(new URL("file:///tmp/deep"), pathH, { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(new URL("file:///tmp/h/nested/very/deep.txt"))); + deepStrictEqual( + readFileSync(new URL("file:///tmp/h/nested/very/deep.txt"), "utf8"), + "content", + ); + }, +}; + +// PreserveTimestamps option tests - callback version +export const preserveTimestampsTestsCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + + // Get original timestamps + const originalStat = lstatSync(pathA); + const originalMtime = originalStat.mtime; + + // Copy without preserveTimestamps (default behavior) + await new Promise((resolve, reject) => { + cp(pathA, pathG, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + const _copiedStat = lstatSync(pathG); + + // Copy with preserveTimestamps: true + await new Promise((resolve, reject) => { + cp(pathA, pathH, { preserveTimestamps: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + const preservedStat = lstatSync(pathH); + + ok( + Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000, + ); + }, +}; + +// Error handling edge cases - callback version +export const errorHandlingTestsCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const nonExistent = new URL("file:///tmp/nonexistent"); + + // Test copying from non-existent source + await new Promise((resolve) => { + cp(nonExistent, pathG, (err) => { + ok(err); + strictEqual(err.code, "ENOENT"); + resolve(); + }); + }); + }, +}; + +// Symlink edge cases - callback version +export const symlinkEdgeCasesCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + const pathI = new URL("file:///tmp/i"); + const pathJ = new URL("file:///tmp/j"); + + // Create a broken symlink + const brokenTarget = new URL("file:///tmp/broken-target"); + symlinkSync(brokenTarget, pathG); + + // Test copying broken symlink without dereference + await new Promise((resolve, reject) => { + cp(pathG, pathH, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(lstatSync(pathH).isSymbolicLink()); + + // Test copying broken symlink with dereference should fail + await new Promise((resolve) => { + cp(pathG, pathI, { dereference: true }, (err) => { + ok(err); + strictEqual(err.code, "ENOENT"); + resolve(); + }); + }); + + // Create symlink chain: J -> I -> A + symlinkSync(pathA, pathI); + symlinkSync(pathI, pathJ); + + // Test copying symlink chain + const pathK = new URL("file:///tmp/k"); + await new Promise((resolve, reject) => { + cp(pathJ, pathK, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(lstatSync(pathK).isSymbolicLink()); + deepStrictEqual(readFileSync(pathK, "utf8"), "foo"); + }, +}; + +// Combined options tests - callback version +export const combinedOptionsTestsCallback = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // Test multiple options together + await new Promise((resolve, reject) => { + cp( + pathB, + pathG, + { + recursive: true, + dereference: true, + force: true, + preserveTimestamps: true, + errorOnExist: false, + }, + (err) => { + if (err) reject(err); + else resolve(); + }, + ); + }); + + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + }, +}; + +// Path type tests - callback version +export const pathTypeTestsCallback = { + async test() { + setupPaths(); + + // Test with Buffer paths + const pathG = new URL("file:///tmp/g"); + const bufferSrc = Buffer.from("/tmp/a"); + const bufferDest = Buffer.from("/tmp/g"); + + await new Promise((resolve, reject) => { + cp(bufferSrc, bufferDest, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + ok(existsSync(pathG)); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// ===== PROMISES-BASED API TESTS ===== + +// Simple file copy - promises version +export const simpleFileCopyPromises = { + async test() { + setupPaths(); + ok(existsSync(pathA)); + deepStrictEqual(readFileSync(pathA, "utf8"), "foo"); + ok(!existsSync(pathF)); + + await fsPromises.cp(pathA, pathF); + ok(existsSync(pathF)); + deepStrictEqual(readFileSync(pathF, "utf8"), "foo"); + writeFileSync(pathA, "baz"); + deepStrictEqual(readFileSync(pathA, "utf8"), "baz"); + deepStrictEqual(readFileSync(pathF, "utf8"), "foo"); + + // Test with errorOnExist: true + await fsPromises.cp(pathA, pathF, { errorOnExist: true }); + + // Test error case: force: false, errorOnExist: true + await rejects( + async () => { + await fsPromises.cp(pathA, pathF, { force: false, errorOnExist: true }); + }, + { code: "ERR_FS_CP_EEXIST" }, + ); + + // Test error case: copying file to directory + await rejects( + async () => { + await fsPromises.cp(pathA, pathB); + }, + { code: "ERR_FS_CP_NON_DIR_TO_DIR" }, + ); + + // Test copying to symlink to directory + await rejects( + async () => { + await fsPromises.cp(pathA, pathD, { dereference: true }); + }, + { code: "ERR_FS_CP_NON_DIR_TO_DIR" }, + ); + + // Test copying to symlink without dereference + await fsPromises.cp(pathA, pathD); + ok(lstatSync(pathD).isFile()); + }, +}; + +// Copy file to non-existent directory - promises version +export const copyFileToNonExistentDirectoryPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/nonexistent"); + const pathH = new URL("file:///tmp/nonexistent/g"); + ok(!existsSync(pathG)); + ok(!existsSync(pathH)); + + await fsPromises.cp(pathA, pathH); + ok(existsSync(pathG)); + ok(existsSync(pathH)); + }, +}; + +// Copy file to existing file with force - promises version +export const copyFileToExistingFileWithForcePromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + await fsPromises.cp(pathA, pathG, { force: true }); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Copy file to existing file without force - promises version +export const copyFileToExistingFileWithoutForcePromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + await fsPromises.cp(pathA, pathG, { force: false }); + deepStrictEqual(readFileSync(pathG, "utf8"), "original"); + }, +}; + +// Copy file to existing directory - promises version +export const copyFileToExistingDirectoryPromises = { + async test() { + setupPaths(); + + await rejects( + async () => { + await fsPromises.cp(pathA, pathB); + }, + { code: "ERR_FS_CP_NON_DIR_TO_DIR" }, + ); + }, +}; + +// Copy file to existing symlink to directory - promises version +export const copyFileToExistingSymlinkToDirectoryPromises = { + async test() { + setupPaths(); + + // With dereference: true, should throw EISDIR + await rejects( + async () => { + await fsPromises.cp(pathA, pathD, { dereference: true }); + }, + { code: "ERR_FS_CP_NON_DIR_TO_DIR" }, + ); + + // Without dereference (default), should replace the symlink + await fsPromises.cp(pathA, pathD); + ok(lstatSync(pathD).isFile()); + deepStrictEqual(readFileSync(pathD, "utf8"), "foo"); + }, +}; + +// Copy directory to non-existent directory - promises version +export const copyDirectoryToNonExistentDirectoryPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + await fsPromises.cp(pathB, pathG, { recursive: true }); + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + ok(existsSync(new URL("file:///tmp/g/e"))); + deepStrictEqual(readFileSync(new URL("file:///tmp/g/e"), "utf8"), "bar"); + }, +}; + +// Copy directory to existing directory - promises version +export const copyDirectoryToExistingDirectoryPromises = { + async test() { + setupPaths(); + + await fsPromises.cp(pathB, pathE, { recursive: true }); + ok(existsSync(new URL("file:///tmp/e/e"))); + deepStrictEqual(readFileSync(new URL("file:///tmp/e/e"), "utf8"), "bar"); + }, +}; + +// Copy directory to existing file - promises version +export const copyDirectoryToExistingFilePromises = { + async test() { + setupPaths(); + + await rejects( + async () => { + await fsPromises.cp(pathB, pathA, { recursive: true }); + }, + { code: "ERR_FS_CP_DIR_TO_NON_DIR" }, + ); + }, +}; + +// Copy directory to existing symlink to file - promises version +export const copyDirectoryToExistingSymlinkToFilePromises = { + async test() { + setupPaths(); + + await rejects( + async () => { + await fsPromises.cp(pathB, pathC, { + recursive: true, + dereference: true, + }); + }, + { code: "ERR_FS_CP_DIR_TO_NON_DIR" }, + ); + }, +}; + +// Copy symlink to file - promises version +export const copySymlinkToFilePromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // With dereference: false (default), should copy the symlink + await fsPromises.cp(pathC, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + + // With dereference: true, should copy the target file + const pathH = new URL("file:///tmp/h"); + await fsPromises.cp(pathC, pathH, { dereference: true }); + ok(lstatSync(pathH).isFile()); + deepStrictEqual(readFileSync(pathH, "utf8"), "foo"); + }, +}; + +// Copy symlink to directory - promises version +export const copySymlinkToDirectoryPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // With dereference: false (default), should copy the symlink + await fsPromises.cp(pathD, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + ok(statSync(pathG).isDirectory()); + + // With dereference: true, should copy the target directory + const pathH = new URL("file:///tmp/h"); + await fsPromises.cp(pathD, pathH, { recursive: true, dereference: true }); + ok(lstatSync(pathH).isDirectory()); + ok(existsSync(new URL("file:///tmp/h/e"))); + }, +}; + +// Copy symlink to existing file - promises version +export const copySymlinkToExistingFilePromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + writeFileSync(pathG, "original"); + + // Should replace the file with symlink + await fsPromises.cp(pathC, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Copy symlink to existing directory - promises version +export const copySymlinkToExistingDirectoryPromises = { + async test() { + setupPaths(); + + await rejects( + async () => { + await fsPromises.cp(pathC, pathE); + }, + { code: "ENOTDIR" }, + ); + }, +}; + +// Copy symlink to existing symlink to directory - promises version +export const copySymlinkToExistingSymlinkToDirectoryPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + mkdirSync(pathH); + symlinkSync(pathH, pathG); + + // Should replace the symlink + await fsPromises.cp(pathD, pathG); + ok(lstatSync(pathG).isSymbolicLink()); + ok(statSync(pathG).isDirectory()); + }, +}; + +// Copy symlink operations with various option combinations - promises version +export const copySymlinkWithDereferenceOptionsPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + const pathI = new URL("file:///tmp/i"); + + // Test with force: false, errorOnExist: true + symlinkSync(pathA, pathG); + await rejects( + async () => { + await fsPromises.cp(pathC, pathG, { force: false, errorOnExist: true }); + }, + { code: "ERR_FS_CP_EEXIST" }, + ); + + // Test with force: false, errorOnExist: false + await fsPromises.cp(pathC, pathG, { force: false, errorOnExist: false }); + ok(lstatSync(pathG).isSymbolicLink()); + + // Test with dereference and errorOnExist combinations + writeFileSync(pathH, "existing"); + await rejects( + async () => { + await fsPromises.cp(pathC, pathH, { + dereference: true, + force: false, + errorOnExist: true, + }); + }, + { code: "ERR_FS_CP_EEXIST" }, + ); + + // Test copying to non-existent path + await fsPromises.cp(pathC, pathI); + ok(lstatSync(pathI).isSymbolicLink()); + deepStrictEqual(readFileSync(pathI, "utf8"), "foo"); + }, +}; + +// Option validation tests - promises version +export const optionValidationTestsPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // Test invalid option types + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { dereference: "invalid" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { errorOnExist: "invalid" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { force: "invalid" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { recursive: "invalid" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { preserveTimestamps: "invalid" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { verbatimSymlinks: "invalid" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + // Test invalid filter type + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { filter: "not-a-function" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + // Test filter option throws unsupported operation + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { filter: () => true }); + }, + { code: "ERR_UNSUPPORTED_OPERATION" }, + ); + + // Test COPYFILE_FICLONE_FORCE mode + await rejects( + async () => { + await fsPromises.cp(pathA, pathG, { mode: 4 }); + }, + { code: "ERR_INVALID_ARG_VALUE" }, + ); + }, +}; + +// Recursive option tests - promises version +export const recursiveOptionTestsPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + + // Test copying directory without recursive option should fail + await rejects( + async () => { + await fsPromises.cp(pathB, pathG); + }, + { code: "ERR_FS_EISDIR" }, + ); + + // Test with recursive: false explicitly + await rejects( + async () => { + await fsPromises.cp(pathB, pathG, { recursive: false }); + }, + { code: "ERR_FS_EISDIR" }, + ); + + // Test copying directory with recursive: true should work + await fsPromises.cp(pathB, pathG, { recursive: true }); + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + ok(existsSync(new URL("file:///tmp/g/e"))); + + // Test deep directory structure + mkdirSync(new URL("file:///tmp/deep")); + mkdirSync(new URL("file:///tmp/deep/nested")); + mkdirSync(new URL("file:///tmp/deep/nested/very")); + writeFileSync(new URL("file:///tmp/deep/nested/very/deep.txt"), "content"); + + await fsPromises.cp(new URL("file:///tmp/deep"), pathH, { + recursive: true, + }); + ok(existsSync(new URL("file:///tmp/h/nested/very/deep.txt"))); + deepStrictEqual( + readFileSync(new URL("file:///tmp/h/nested/very/deep.txt"), "utf8"), + "content", + ); + }, +}; + +// PreserveTimestamps option tests - promises version +export const preserveTimestampsTestsPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + + // Get original timestamps + const originalStat = lstatSync(pathA); + const originalMtime = originalStat.mtime; + + // Copy without preserveTimestamps (default behavior) + await fsPromises.cp(pathA, pathG); + const _copiedStat = lstatSync(pathG); + + // Copy with preserveTimestamps: true + await fsPromises.cp(pathA, pathH, { preserveTimestamps: true }); + const preservedStat = lstatSync(pathH); + + ok( + Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000, + ); + }, +}; + +// Error handling edge cases - promises version +export const errorHandlingTestsPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const nonExistent = new URL("file:///tmp/nonexistent"); + + // Test copying from non-existent source + await rejects( + async () => { + await fsPromises.cp(nonExistent, pathG); + }, + { code: "ENOENT" }, + ); + }, +}; + +// Symlink edge cases - promises version +export const symlinkEdgeCasesPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + const pathH = new URL("file:///tmp/h"); + const pathI = new URL("file:///tmp/i"); + const pathJ = new URL("file:///tmp/j"); + + // Create a broken symlink + const brokenTarget = new URL("file:///tmp/broken-target"); + symlinkSync(brokenTarget, pathG); + + // Test copying broken symlink without dereference + await fsPromises.cp(pathG, pathH); + ok(lstatSync(pathH).isSymbolicLink()); + + // Test copying broken symlink with dereference should fail + await rejects( + async () => { + await fsPromises.cp(pathG, pathI, { dereference: true }); + }, + { code: "ENOENT" }, + ); + + // Create symlink chain: J -> I -> A + symlinkSync(pathA, pathI); + symlinkSync(pathI, pathJ); + + // Test copying symlink chain + const pathK = new URL("file:///tmp/k"); + await fsPromises.cp(pathJ, pathK); + + ok(lstatSync(pathK).isSymbolicLink()); + deepStrictEqual(readFileSync(pathK, "utf8"), "foo"); + }, +}; + +// Combined options tests - promises version +export const combinedOptionsTestsPromises = { + async test() { + setupPaths(); + const pathG = new URL("file:///tmp/g"); + + // Test multiple options together + await fsPromises.cp(pathB, pathG, { + recursive: true, + dereference: true, + force: true, + preserveTimestamps: true, + errorOnExist: false, + }); + + ok(existsSync(pathG)); + ok(lstatSync(pathG).isDirectory()); + }, +}; + +// Path type tests - promises version +export const pathTypeTestsPromises = { + async test() { + setupPaths(); + + // Test with Buffer paths + const pathG = new URL("file:///tmp/g"); + const bufferSrc = Buffer.from("/tmp/a"); + const bufferDest = Buffer.from("/tmp/g"); + + await fsPromises.cp(bufferSrc, bufferDest); + ok(existsSync(pathG)); + deepStrictEqual(readFileSync(pathG, "utf8"), "foo"); + }, +}; + +// Deep directory structure tests - sync version +export const deepDirectoryStructureTestsSync = { + test() { + const sourceRoot = "/tmp/deep_source"; + const destRoot = "/tmp/deep_dest"; + + // Create source directory structure + mkdirSync(sourceRoot, { recursive: true }); + mkdirSync(`${sourceRoot}/level1`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, { + recursive: true, + }); + + // Create files in various levels + writeFileSync(`${sourceRoot}/root_file.txt`, "root content"); + writeFileSync(`${sourceRoot}/level1/level1_file.txt`, "level1 content"); + writeFileSync( + `${sourceRoot}/level1/level2/level2_file.txt`, + "level2 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level3_file.txt`, + "level3 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`, + "level4 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + "level5 content", + ); + + // Create symlinks (will not be followed) + writeFileSync(`${sourceRoot}/link_target.txt`, "link target content"); + symlinkSync( + `${sourceRoot}/link_target.txt`, + `${sourceRoot}/level1/level2/symlink_to_file`, + ); + symlinkSync( + `${sourceRoot}/level1`, + `${sourceRoot}/level1/level2/level3/symlink_to_dir`, + ); + + // Create destination directory structure with some existing files (duplicates) + mkdirSync(destRoot, { recursive: true }); + mkdirSync(`${destRoot}/level1`, { recursive: true }); + mkdirSync(`${destRoot}/level1/level2`, { recursive: true }); + mkdirSync(`${destRoot}/existing_dir`, { recursive: true }); + + // Create files that will be duplicates (should be ignored) + writeFileSync(`${destRoot}/duplicate_file.txt`, "existing content"); + writeFileSync( + `${destRoot}/level1/duplicate_file.txt`, + "existing level1 content", + ); + + // Add some files to source that match existing files (duplicates) + writeFileSync( + `${sourceRoot}/duplicate_file.txt`, + "source duplicate content", + ); + writeFileSync( + `${sourceRoot}/level1/duplicate_file.txt`, + "source level1 duplicate content", + ); + + // Perform recursive copy (should ignore duplicates and not follow symlinks) + cpSync(sourceRoot, destRoot, { recursive: true, force: false }); + + // Verify destination structure + ok(existsSync(`${destRoot}/root_file.txt`)); + ok(existsSync(`${destRoot}/level1/level1_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`)); + ok( + existsSync( + `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + ), + ); + + // Verify file contents + deepStrictEqual( + readFileSync(`${destRoot}/root_file.txt`, "utf8"), + "root content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level1_file.txt`, "utf8"), + "level1 content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level2/level2_file.txt`, "utf8"), + "level2 content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, "utf8"), + "level3 content", + ); + deepStrictEqual( + readFileSync( + `${destRoot}/level1/level2/level3/level4/level4_file.txt`, + "utf8", + ), + "level4 content", + ); + deepStrictEqual( + readFileSync( + `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + "utf8", + ), + "level5 content", + ); + + // Verify symlinks are copied as symlinks (not followed) + ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink()); + ok( + lstatSync( + `${destRoot}/level1/level2/level3/symlink_to_dir`, + ).isSymbolicLink(), + ); + + // Verify duplicate files were ignored (original content preserved) + deepStrictEqual( + readFileSync(`${destRoot}/duplicate_file.txt`, "utf8"), + "existing content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/duplicate_file.txt`, "utf8"), + "existing level1 content", + ); + + // Verify existing directory structure remains + ok(existsSync(`${destRoot}/existing_dir`)); + }, +}; + +// Deep directory structure tests - callback version +export const deepDirectoryStructureTestsCallback = { + async test() { + const sourceRoot = "/tmp/deep_source_cb"; + const destRoot = "/tmp/deep_dest_cb"; + + // Create source directory structure + mkdirSync(sourceRoot, { recursive: true }); + mkdirSync(`${sourceRoot}/level1`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, { + recursive: true, + }); + + // Create files in various levels + writeFileSync(`${sourceRoot}/root_file.txt`, "root content"); + writeFileSync(`${sourceRoot}/level1/level1_file.txt`, "level1 content"); + writeFileSync( + `${sourceRoot}/level1/level2/level2_file.txt`, + "level2 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level3_file.txt`, + "level3 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`, + "level4 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + "level5 content", + ); + + // Create symlinks (will not be followed) + writeFileSync(`${sourceRoot}/link_target.txt`, "link target content"); + symlinkSync( + `${sourceRoot}/link_target.txt`, + `${sourceRoot}/level1/level2/symlink_to_file`, + ); + symlinkSync( + `${sourceRoot}/level1`, + `${sourceRoot}/level1/level2/level3/symlink_to_dir`, + ); + + // Create destination directory structure with some existing files (duplicates) + mkdirSync(destRoot, { recursive: true }); + mkdirSync(`${destRoot}/level1`, { recursive: true }); + mkdirSync(`${destRoot}/level1/level2`, { recursive: true }); + mkdirSync(`${destRoot}/existing_dir`, { recursive: true }); + + // Create files that will be duplicates (should be ignored) + writeFileSync(`${destRoot}/duplicate_file.txt`, "existing content"); + writeFileSync( + `${destRoot}/level1/duplicate_file.txt`, + "existing level1 content", + ); + + // Add some files to source that match existing files (duplicates) + writeFileSync( + `${sourceRoot}/duplicate_file.txt`, + "source duplicate content", + ); + writeFileSync( + `${sourceRoot}/level1/duplicate_file.txt`, + "source level1 duplicate content", + ); + // Perform recursive copy (should ignore duplicates and not follow symlinks) + await new Promise((resolve, reject) => { + cp(sourceRoot, destRoot, { recursive: true, force: false }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + // Verify destination structure + ok(existsSync(`${destRoot}/root_file.txt`)); + ok(existsSync(`${destRoot}/level1/level1_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`)); + ok( + existsSync( + `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + ), + ); + + // Verify file contents + deepStrictEqual( + readFileSync(`${destRoot}/root_file.txt`, "utf8"), + "root content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level1_file.txt`, "utf8"), + "level1 content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level2/level2_file.txt`, "utf8"), + "level2 content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, "utf8"), + "level3 content", + ); + deepStrictEqual( + readFileSync( + `${destRoot}/level1/level2/level3/level4/level4_file.txt`, + "utf8", + ), + "level4 content", + ); + deepStrictEqual( + readFileSync( + `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + "utf8", + ), + "level5 content", + ); + + // Verify symlinks are copied as symlinks (not followed) + ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink()); + ok( + lstatSync( + `${destRoot}/level1/level2/level3/symlink_to_dir`, + ).isSymbolicLink(), + ); + + // Verify duplicate files were ignored (original content preserved) + deepStrictEqual( + readFileSync(`${destRoot}/duplicate_file.txt`, "utf8"), + "existing content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/duplicate_file.txt`, "utf8"), + "existing level1 content", + ); + + // Verify existing directory structure remains + ok(existsSync(`${destRoot}/existing_dir`)); + }, +}; + +// Deep directory structure tests - promises version +export const deepDirectoryStructureTestsPromises = { + async test() { + const sourceRoot = "/tmp/deep_source_pr"; + const destRoot = "/tmp/deep_dest_pr"; + + // Create source directory structure + mkdirSync(sourceRoot, { recursive: true }); + mkdirSync(`${sourceRoot}/level1`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true }); + mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, { + recursive: true, + }); + + // Create files in various levels + writeFileSync(`${sourceRoot}/root_file.txt`, "root content"); + writeFileSync(`${sourceRoot}/level1/level1_file.txt`, "level1 content"); + writeFileSync( + `${sourceRoot}/level1/level2/level2_file.txt`, + "level2 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level3_file.txt`, + "level3 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`, + "level4 content", + ); + writeFileSync( + `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + "level5 content", + ); + + // Create symlinks (will not be followed) + writeFileSync(`${sourceRoot}/link_target.txt`, "link target content"); + symlinkSync( + `${sourceRoot}/link_target.txt`, + `${sourceRoot}/level1/level2/symlink_to_file`, + ); + symlinkSync( + `${sourceRoot}/level1`, + `${sourceRoot}/level1/level2/level3/symlink_to_dir`, + ); + + // Create destination directory structure with some existing files (duplicates) + mkdirSync(destRoot, { recursive: true }); + mkdirSync(`${destRoot}/level1`, { recursive: true }); + mkdirSync(`${destRoot}/level1/level2`, { recursive: true }); + mkdirSync(`${destRoot}/existing_dir`, { recursive: true }); + + // Create files that will be duplicates (should be ignored) + writeFileSync(`${destRoot}/duplicate_file.txt`, "existing content"); + writeFileSync( + `${destRoot}/level1/duplicate_file.txt`, + "existing level1 content", + ); + + // Add some files to source that match existing files (duplicates) + writeFileSync( + `${sourceRoot}/duplicate_file.txt`, + "source duplicate content", + ); + writeFileSync( + `${sourceRoot}/level1/duplicate_file.txt`, + "source level1 duplicate content", + ); + + // Perform recursive copy (should ignore duplicates and not follow symlinks) + await fsPromises.cp(sourceRoot, destRoot, { + recursive: true, + force: false, + }); + + // Verify destination structure + ok(existsSync(`${destRoot}/root_file.txt`)); + ok(existsSync(`${destRoot}/level1/level1_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`)); + ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`)); + ok( + existsSync( + `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + ), + ); + + // Verify file contents + deepStrictEqual( + readFileSync(`${destRoot}/root_file.txt`, "utf8"), + "root content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level1_file.txt`, "utf8"), + "level1 content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level2/level2_file.txt`, "utf8"), + "level2 content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, "utf8"), + "level3 content", + ); + deepStrictEqual( + readFileSync( + `${destRoot}/level1/level2/level3/level4/level4_file.txt`, + "utf8", + ), + "level4 content", + ); + deepStrictEqual( + readFileSync( + `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`, + "utf8", + ), + "level5 content", + ); + + // Verify symlinks are copied as symlinks (not followed) + ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink()); + ok( + lstatSync( + `${destRoot}/level1/level2/level3/symlink_to_dir`, + ).isSymbolicLink(), + ); + + // Verify duplicate files were ignored (original content preserved) + deepStrictEqual( + readFileSync(`${destRoot}/duplicate_file.txt`, "utf8"), + "existing content", + ); + deepStrictEqual( + readFileSync(`${destRoot}/level1/duplicate_file.txt`, "utf8"), + "existing level1 content", + ); + + // Verify existing directory structure remains + ok(existsSync(`${destRoot}/existing_dir`)); + }, +}; + +export const copyTwiceTest = { + test() { + cpSync("/bundle/worker", "/tmp/a"); + cpSync("/tmp/a", "/tmp/b"); + const a = readFileSync("/bundle/worker"); + const b = readFileSync("/tmp/a"); + const c = readFileSync("/tmp/b"); + deepStrictEqual(a, b); + deepStrictEqual(b, c); + }, +}; diff --git a/src/compat/fs/upstream-directory.fixture.mjs b/src/compat/fs/upstream-directory.fixture.mjs new file mode 100644 index 00000000..bfd5239f --- /dev/null +++ b/src/compat/fs/upstream-directory.fixture.mjs @@ -0,0 +1,920 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { + deepStrictEqual, + ok, + match, + rejects, + strictEqual, + throws, +} from "node:assert"; + +import { + existsSync, + writeFileSync, + mkdirSync, + mkdtempSync, + rmSync, + rmdirSync, + readdirSync, + mkdtemp, + mkdir, + rm, + rmdir, + readdir, + promises, + opendirSync, + opendir, +} from "./index.js"; + +strictEqual(typeof existsSync, "function"); +strictEqual(typeof writeFileSync, "function"); +strictEqual(typeof mkdirSync, "function"); +strictEqual(typeof mkdtempSync, "function"); +strictEqual(typeof rmSync, "function"); +strictEqual(typeof rmdirSync, "function"); +strictEqual(typeof readdirSync, "function"); +strictEqual(typeof mkdtemp, "function"); +strictEqual(typeof mkdir, "function"); +strictEqual(typeof rm, "function"); +strictEqual(typeof rmdir, "function"); +strictEqual(typeof readdir, "function"); +strictEqual(typeof opendirSync, "function"); +strictEqual(typeof opendir, "function"); +strictEqual(typeof promises.mkdir, "function"); +strictEqual(typeof promises.mkdtemp, "function"); +strictEqual(typeof promises.rm, "function"); +strictEqual(typeof promises.rmdir, "function"); +strictEqual(typeof promises.readdir, "function"); +strictEqual(typeof promises.opendir, "function"); + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; +const kInvalidArgValueError = { code: "ERR_INVALID_ARG_VALUE" }; +const kEPermError = { code: "EPERM" }; +const kENoEntError = { code: "ENOENT" }; +const kEExistError = { code: "EEXIST" }; +const kENotDirError = { code: "ENOTDIR" }; +const kENotEmptyError = { code: "ENOTEMPTY" }; + +export const mkdirSyncTest = { + test() { + throws(() => mkdirSync(), kInvalidArgTypeError); + throws(() => mkdirSync(123), kInvalidArgTypeError); + throws(() => mkdirSync("/tmp/testdir", "hello"), kInvalidArgTypeError); + throws( + () => mkdirSync("/tmp/testdir", { recursive: 123 }), + kInvalidArgTypeError, + ); + + // Make a directory. + ok(!existsSync("/tmp/testdir")); + strictEqual(mkdirSync("/tmp/testdir"), undefined); + ok(existsSync("/tmp/testdir")); + + // Making a subdirectory in a non-existing path fails by default + ok(!existsSync("/tmp/testdir/a/b/c")); + throws(() => mkdirSync("/tmp/testdir/a/b/c"), kENoEntError); + + // But passing the recursive option allows the entire path to be created. + ok(!existsSync("/tmp/testdir/a/b/c")); + strictEqual( + mkdirSync("/tmp/testdir/a/b/c", { recursive: true }), + "/tmp/testdir/a", + ); + ok(existsSync("/tmp/testdir/a/b/c")); + + // Cannot make a directory in a read-only location + throws(() => mkdirSync("/bundle/a"), kEPermError); + + // Making a directory that already exists is a non-op + mkdirSync("/tmp/testdir"); + + // Attempting to create a directory that already exists as a file throws + writeFileSync("/tmp/abc", "Hello World"); + throws(() => mkdirSync("/tmp/abc"), kEExistError); + + // Attempting to create a directory recursively when a parent is a file + // throws + throws(() => mkdirSync("/tmp/abc/foo", { recursive: true }), kENotDirError); + }, +}; + +export const mkdirAsyncCallbackTest = { + async test() { + throws(() => mkdir(), kInvalidArgTypeError); + throws(() => mkdir(123), kInvalidArgTypeError); + throws(() => mkdir("/tmp/testdir", "hello"), kInvalidArgTypeError); + throws( + () => mkdir("/tmp/testdir", { recursive: 123 }), + kInvalidArgTypeError, + ); + + // Make a directory. + ok(!existsSync("/tmp/testdir")); + await new Promise((resolve, reject) => { + mkdir("/tmp/testdir", (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(existsSync("/tmp/testdir")); + + // Making a subdirectory in a non-existing path fails by default + ok(!existsSync("/tmp/testdir/a/b/c")); + await new Promise((resolve, reject) => { + mkdir("/tmp/testdir/a/b/c", (err) => { + if (err && err.code === kENoEntError.code) resolve(); + else reject(err); + }); + }); + + // But passing the recursive option allows the entire path to be created. + ok(!existsSync("/tmp/testdir/a/b/c")); + await new Promise((resolve, reject) => { + mkdir("/tmp/testdir/a/b/c", { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(existsSync("/tmp/testdir/a/b/c")); + + // Cannot make a directory in a read-only location + await new Promise((resolve, reject) => { + mkdir("/bundle/a", (err) => { + if (err && err.code === kEPermError.code) resolve(); + else reject(err); + }); + }); + + // Making a directory that already exists is a non-op + await new Promise((resolve, reject) => { + mkdir("/tmp/testdir", (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + // Attempting to create a directory that already exists as a file throws + writeFileSync("/tmp/abc", "Hello World"); + await new Promise((resolve, reject) => { + mkdir("/tmp/abc", (err) => { + if (err && err.code === kEExistError.code) resolve(); + else reject(err); + }); + }); + + // Attempting to create a directory recursively when a parent is a file + // throws + await new Promise((resolve, reject) => { + mkdir("/tmp/abc/foo", { recursive: true }, (err) => { + if (err && err.code === kENotDirError.code) resolve(); + else reject(err); + }); + }); + }, +}; + +export const mkdirAsyncPromiseTest = { + async test() { + await rejects(promises.mkdir(), kInvalidArgTypeError); + await rejects(promises.mkdir(123), kInvalidArgTypeError); + await rejects( + promises.mkdir("/tmp/testdir", "hello"), + kInvalidArgTypeError, + ); + await rejects( + promises.mkdir("/tmp/testdir", { recursive: 123 }), + kInvalidArgTypeError, + ); + + // Make a directory. + ok(!existsSync("/tmp/testdir")); + await promises.mkdir("/tmp/testdir"); + ok(existsSync("/tmp/testdir")); + + // Making a subdirectory in a non-existing path fails by default + ok(!existsSync("/tmp/testdir/a/b/c")); + await rejects(promises.mkdir("/tmp/testdir/a/b/c"), kENoEntError); + + // But passing the recursive option allows the entire path to be created. + ok(!existsSync("/tmp/testdir/a/b/c")); + await promises.mkdir("/tmp/testdir/a/b/c", { recursive: true }); + ok(existsSync("/tmp/testdir/a/b/c")); + + // Cannot make a directory in a read-only location + await rejects(promises.mkdir("/bundle/a"), kEPermError); + + // Making a directory that already exists is a non-op + await promises.mkdir("/tmp/testdir"); + + // Attempting to create a directory that already exists as a file throws + writeFileSync("/tmp/abc", "Hello World"); + await rejects(promises.mkdir("/tmp/abc"), kEExistError); + + // Attempting to create a directory recursively when a parent is a file + // throws + await rejects( + promises.mkdir("/tmp/abc/foo", { recursive: true }), + kENotDirError, + ); + }, +}; + +export const mkdtempSyncTest = { + test() { + throws(() => mkdtempSync(), kInvalidArgTypeError); + const ret1 = mkdtempSync("/tmp/testdir-"); + const ret2 = mkdtempSync("/tmp/testdir-"); + match(ret1, /\/tmp\/testdir-\d+/); + match(ret2, /\/tmp\/testdir-\d+/); + ok(existsSync(ret1)); + ok(existsSync(ret2)); + throws(() => mkdtempSync("/bundle/testdir-"), kEPermError); + }, +}; + +export const mkdtempAsyncCallbackTest = { + async test() { + throws(() => mkdtemp(), kInvalidArgTypeError); + const ret1 = await new Promise((resolve, reject) => { + mkdtemp("/tmp/testdir-", (err, dir) => { + if (err) reject(err); + else resolve(dir); + }); + }); + const ret2 = await new Promise((resolve, reject) => { + mkdtemp("/tmp/testdir-", (err, dir) => { + if (err) reject(err); + else resolve(dir); + }); + }); + match(ret1, /\/tmp\/testdir-\d+/); + match(ret2, /\/tmp\/testdir-\d+/); + ok(existsSync(ret1)); + ok(existsSync(ret2)); + await new Promise((resolve, reject) => { + mkdtemp("/bundle/testdir-", (err) => { + if (err && err.code === kEPermError.code) resolve(); + else reject(err); + }); + }); + }, +}; + +export const mkdtempAsyncPromiseTest = { + async test() { + await rejects(promises.mkdtemp(), kInvalidArgTypeError); + const ret1 = await promises.mkdtemp("/tmp/testdir-"); + const ret2 = await promises.mkdtemp("/tmp/testdir-"); + match(ret1, /\/tmp\/testdir-\d+/); + match(ret2, /\/tmp\/testdir-\d+/); + ok(existsSync(ret1)); + ok(existsSync(ret2)); + await rejects(promises.mkdtemp("/bundle/testdir-"), kEPermError); + }, +}; + +export const rmSyncTest = { + test() { + // Passing incorrect types for options throws + throws( + () => rmSync("/tmp/testdir", { recursive: "yes" }), + kInvalidArgTypeError, + ); + throws(() => rmSync("/tmp/testdir", "abc"), kInvalidArgTypeError); + throws( + () => rmSync("/tmp/testdir", { force: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmSync("/tmp/testdir", { maxRetries: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmSync("/tmp/testdir", { retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmSync("/tmp/testdir", { maxRetries: 1, retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmSync("/tmp/testdir", { maxRetries: "yes", retryDelay: 1 }), + kInvalidArgTypeError, + ); + throws( + () => + rmSync("/tmp/testdir", { maxRetries: 1, retryDelay: 1, force: "yes" }), + kInvalidArgTypeError, + ); + + throws( + () => rmdirSync("/tmp/testdir", { recursive: "yes" }), + kInvalidArgTypeError, + ); + throws(() => rmdirSync("/tmp/testdir", "abc"), kInvalidArgTypeError); + throws( + () => rmdirSync("/tmp/testdir", { maxRetries: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmdirSync("/tmp/testdir", { retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmdirSync("/tmp/testdir", { maxRetries: 1, retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmdirSync("/tmp/testdir", { maxRetries: "yes", retryDelay: 1 }), + kInvalidArgTypeError, + ); + + ok(!existsSync("/tmp/testdir")); + mkdirSync("/tmp/testdir"); + writeFileSync("/tmp/testdir/a.txt", "Hello World"); + + // When the recusive option is not set, then removing a directory + // with children throws... + throws(() => rmdirSync("/tmp/testdir"), kENotEmptyError); + ok(existsSync("/tmp/testdir")); + + // But works when the recursive option is set + rmdirSync("/tmp/testdir", { recursive: true }); + ok(!existsSync("/tmp/testdir")); + + mkdirSync("/tmp/testdir"); + writeFileSync("/tmp/testdir/a.txt", "Hello World"); + writeFileSync("/tmp/testdir/b.txt", "Hello World"); + ok(existsSync("/tmp/testdir/a.txt")); + + // trying to remove a file with rmdir throws + throws(() => rmdirSync("/tmp/testdir/a.txt"), kENotDirError); + + // removing a file with rm works + rmSync("/tmp/testdir/a.txt"); + ok(!existsSync("/tmp/testdir/a.txt")); + + // Calling rmSync when the directory is not empty throws + throws(() => rmSync("/tmp/testdir"), kENotEmptyError); + ok(existsSync("/tmp/testdir")); + + // But works when the recursive option is set + throws(() => rmSync("/tmp/testdir")); + rmSync("/tmp/testdir", { recursive: true }); + ok(!existsSync("/tmp/testdir")); + }, +}; + +export const rmAsyncCallbackTest = { + async test() { + // Passing incorrect types for options throws + throws( + () => rm("/tmp/testdir", { recursive: "yes" }), + kInvalidArgTypeError, + ); + throws(() => rm("/tmp/testdir", "abc"), kInvalidArgTypeError); + throws(() => rm("/tmp/testdir", { force: "yes" }), kInvalidArgTypeError); + throws( + () => rm("/tmp/testdir", { maxRetries: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rm("/tmp/testdir", { retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rm("/tmp/testdir", { maxRetries: 1, retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rm("/tmp/testdir", { maxRetries: "yes", retryDelay: 1 }), + kInvalidArgTypeError, + ); + throws( + () => rm("/tmp/testdir", { maxRetries: 1, retryDelay: 1, force: "yes" }), + kInvalidArgTypeError, + ); + + throws( + () => rmdir("/tmp/testdir", { recursive: "yes" }), + kInvalidArgTypeError, + ); + throws(() => rmdir("/tmp/testdir", "abc"), kInvalidArgTypeError); + throws( + () => rmdir("/tmp/testdir", { maxRetries: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmdir("/tmp/testdir", { retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmdir("/tmp/testdir", { maxRetries: 1, retryDelay: "yes" }), + kInvalidArgTypeError, + ); + throws( + () => rmdir("/tmp/testdir", { maxRetries: "yes", retryDelay: 1 }), + kInvalidArgTypeError, + ); + + ok(!existsSync("/tmp/testdir")); + mkdirSync("/tmp/testdir"); + writeFileSync("/tmp/testdir/a.txt", "Hello World"); + + // When the recusive option is not set, then removing a directory + // with children throws... + await new Promise((resolve, reject) => { + rmdir("/tmp/testdir", (err) => { + if (err && err.code === kENotEmptyError.code) resolve(); + else reject(err); + }); + }); + + ok(existsSync("/tmp/testdir")); + // But works when the recursive option is set + await new Promise((resolve, reject) => { + rmdir("/tmp/testdir", { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(!existsSync("/tmp/testdir")); + mkdirSync("/tmp/testdir"); + writeFileSync("/tmp/testdir/a.txt", "Hello World"); + writeFileSync("/tmp/testdir/b.txt", "Hello World"); + + ok(existsSync("/tmp/testdir/a.txt")); + // trying to remove a file with rmdir throws + await new Promise((resolve, reject) => { + rmdir("/tmp/testdir/a.txt", (err) => { + if (err && err.code === kENotDirError.code) resolve(); + else reject(err); + }); + }); + // removing a file with rm works + await new Promise((resolve, reject) => { + rm("/tmp/testdir/a.txt", (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(!existsSync("/tmp/testdir/a.txt")); + // Calling rm when the directory is not empty throws + await new Promise((resolve, reject) => { + rm("/tmp/testdir", (err) => { + if (err && err.code === kENotEmptyError.code) resolve(); + else reject(err); + }); + }); + ok(existsSync("/tmp/testdir")); + // But works when the recursive option is set + await new Promise((resolve, reject) => { + rm("/tmp/testdir", { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(!existsSync("/tmp/testdir")); + }, +}; + +export const rmAsyncPromiseTest = { + async test() { + // Passing incorrect types for options throws + await rejects( + promises.rm("/tmp/testdir", { recursive: "yes" }), + kInvalidArgTypeError, + ); + await rejects(promises.rm("/tmp/testdir", "abc"), kInvalidArgTypeError); + await rejects( + promises.rm("/tmp/testdir", { force: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rm("/tmp/testdir", { maxRetries: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rm("/tmp/testdir", { retryDelay: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rm("/tmp/testdir", { maxRetries: 1, retryDelay: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rm("/tmp/testdir", { maxRetries: "yes", retryDelay: 1 }), + kInvalidArgTypeError, + ); + await rejects( + promises.rm("/tmp/testdir", { + maxRetries: 1, + retryDelay: 1, + force: "yes", + }), + kInvalidArgTypeError, + ); + + await rejects( + promises.rmdir("/tmp/testdir", { recursive: "yes" }), + kInvalidArgTypeError, + ); + await rejects(promises.rmdir("/tmp/testdir", "abc"), kInvalidArgTypeError); + await rejects( + promises.rmdir("/tmp/testdir", { maxRetries: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rmdir("/tmp/testdir", { retryDelay: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rmdir("/tmp/testdir", { maxRetries: 1, retryDelay: "yes" }), + kInvalidArgTypeError, + ); + await rejects( + promises.rmdir("/tmp/testdir", { maxRetries: "yes", retryDelay: 1 }), + kInvalidArgTypeError, + ); + + ok(!existsSync("/tmp/testdir")); + mkdirSync("/tmp/testdir"); + writeFileSync("/tmp/testdir/a.txt", "Hello World"); + + // When the recusive option is not set, then removing a directory + // with children throws... + await rejects(promises.rmdir("/tmp/testdir"), kENotEmptyError); + + ok(existsSync("/tmp/testdir")); + // But works when the recursive option is set + await promises.rmdir("/tmp/testdir", { recursive: true }); + ok(!existsSync("/tmp/testdir")); + mkdirSync("/tmp/testdir"); + writeFileSync("/tmp/testdir/a.txt", "Hello World"); + writeFileSync("/tmp/testdir/b.txt", "Hello World"); + ok(existsSync("/tmp/testdir/a.txt")); + // trying to remove a file with rmdir throws + await rejects(promises.rmdir("/tmp/testdir/a.txt"), kENotDirError); + // removing a file with rm works + await promises.rm("/tmp/testdir/a.txt"); + ok(!existsSync("/tmp/testdir/a.txt")); + // Calling rm when the directory is not empty throws + await rejects(promises.rm("/tmp/testdir"), kENotEmptyError); + ok(existsSync("/tmp/testdir")); + // But works when the recursive option is set + await promises.rm("/tmp/testdir", { recursive: true }); + ok(!existsSync("/tmp/testdir")); + }, +}; + +export const readdirSyncTest = { + test() { + throws(() => readdirSync(), kInvalidArgTypeError); + throws(() => readdirSync(123), kInvalidArgTypeError); + throws( + () => readdirSync("/tmp/testdir", { withFileTypes: 123 }), + kInvalidArgTypeError, + ); + throws( + () => readdirSync("/tmp/testdir", { recursive: 123 }), + kInvalidArgTypeError, + ); + throws( + () => + readdirSync("/tmp/testdir", { withFileTypes: true, recursive: 123 }), + kInvalidArgTypeError, + ); + + deepStrictEqual(readdirSync("/"), ["bundle", "tmp", "dev"]); + + deepStrictEqual(readdirSync("/", "buffer"), [ + Buffer.from("bundle"), + Buffer.from("tmp"), + Buffer.from("dev"), + ]); + + { + const ents = readdirSync("/", { withFileTypes: true }); + strictEqual(ents.length, 3); + + strictEqual(ents[0].name, "bundle"); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + } + + { + const ents = readdirSync("/", { + withFileTypes: true, + encoding: "buffer", + }); + strictEqual(ents.length, 3); + + deepStrictEqual(ents[0].name, Buffer.from("bundle")); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + } + + { + const ents = readdirSync("/", { withFileTypes: true, recursive: true }); + strictEqual(ents.length, 8); + + strictEqual(ents[0].name, "bundle"); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + + strictEqual(ents[1].name, "bundle/worker"); + strictEqual(ents[1].isDirectory(), false); + strictEqual(ents[1].isFile(), true); + strictEqual(ents[1].isBlockDevice(), false); + strictEqual(ents[1].isCharacterDevice(), false); + strictEqual(ents[1].isFIFO(), false); + strictEqual(ents[1].isSocket(), false); + strictEqual(ents[1].isSymbolicLink(), false); + strictEqual(ents[1].parentPath, "/bundle"); + + strictEqual(ents[4].name, "dev/null"); + strictEqual(ents[4].isDirectory(), false); + strictEqual(ents[4].isFile(), false); + strictEqual(ents[4].isBlockDevice(), false); + strictEqual(ents[4].isCharacterDevice(), true); + strictEqual(ents[4].isFIFO(), false); + strictEqual(ents[4].isSocket(), false); + strictEqual(ents[4].isSymbolicLink(), false); + strictEqual(ents[4].parentPath, "/dev"); + } + }, +}; + +export const readdirAsyncCallbackTest = { + async test() { + deepStrictEqual( + await new Promise((resolve, reject) => { + readdir("/", (err, files) => { + if (err) reject(err); + else resolve(files); + }); + }), + ["bundle", "tmp", "dev"], + ); + + { + const ents = await new Promise((resolve, reject) => { + readdir("/", { withFileTypes: true }, (err, files) => { + if (err) reject(err); + else resolve(files); + }); + }); + strictEqual(ents.length, 3); + + strictEqual(ents[0].name, "bundle"); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + } + + { + const ents = await new Promise((resolve, reject) => { + readdir( + "/", + { withFileTypes: true, encoding: "buffer" }, + (err, files) => { + if (err) reject(err); + else resolve(files); + }, + ); + }); + strictEqual(ents.length, 3); + + deepStrictEqual(ents[0].name, Buffer.from("bundle")); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + } + + { + const ents = await new Promise((resolve, reject) => { + readdir("/", { withFileTypes: true, recursive: true }, (err, files) => { + if (err) reject(err); + else resolve(files); + }); + }); + strictEqual(ents.length, 8); + + strictEqual(ents[0].name, "bundle"); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + + strictEqual(ents[1].name, "bundle/worker"); + strictEqual(ents[1].isDirectory(), false); + strictEqual(ents[1].isFile(), true); + strictEqual(ents[1].isBlockDevice(), false); + strictEqual(ents[1].isCharacterDevice(), false); + strictEqual(ents[1].isFIFO(), false); + strictEqual(ents[1].isSocket(), false); + strictEqual(ents[1].isSymbolicLink(), false); + strictEqual(ents[1].parentPath, "/bundle"); + + strictEqual(ents[4].name, "dev/null"); + strictEqual(ents[4].isDirectory(), false); + strictEqual(ents[4].isFile(), false); + strictEqual(ents[4].isBlockDevice(), false); + strictEqual(ents[4].isCharacterDevice(), true); + strictEqual(ents[4].isFIFO(), false); + strictEqual(ents[4].isSocket(), false); + strictEqual(ents[4].isSymbolicLink(), false); + strictEqual(ents[4].parentPath, "/dev"); + } + }, +}; + +export const readdirAsyncPromiseTest = { + async test() { + deepStrictEqual(await promises.readdir("/"), ["bundle", "tmp", "dev"]); + + { + const ents = await promises.readdir("/", { withFileTypes: true }); + strictEqual(ents.length, 3); + + strictEqual(ents[0].name, "bundle"); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + } + + { + const ents = await promises.readdir("/", { + withFileTypes: true, + encoding: "buffer", + }); + strictEqual(ents.length, 3); + + deepStrictEqual(ents[0].name, Buffer.from("bundle")); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + } + + { + const ents = await promises.readdir("/", { + withFileTypes: true, + recursive: true, + }); + strictEqual(ents.length, 8); + + strictEqual(ents[0].name, "bundle"); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, "/"); + + strictEqual(ents[1].name, "bundle/worker"); + strictEqual(ents[1].isDirectory(), false); + strictEqual(ents[1].isFile(), true); + strictEqual(ents[1].isBlockDevice(), false); + strictEqual(ents[1].isCharacterDevice(), false); + strictEqual(ents[1].isFIFO(), false); + strictEqual(ents[1].isSocket(), false); + strictEqual(ents[1].isSymbolicLink(), false); + strictEqual(ents[1].parentPath, "/bundle"); + strictEqual(ents[4].name, "dev/null"); + strictEqual(ents[4].isDirectory(), false); + strictEqual(ents[4].isFile(), false); + strictEqual(ents[4].isBlockDevice(), false); + strictEqual(ents[4].isCharacterDevice(), true); + strictEqual(ents[4].isFIFO(), false); + strictEqual(ents[4].isSocket(), false); + strictEqual(ents[4].isSymbolicLink(), false); + strictEqual(ents[4].parentPath, "/dev"); + } + }, +}; + +export const opendirSyncTest = { + test() { + throws(() => opendirSync(), kInvalidArgTypeError); + throws(() => opendirSync(123), kInvalidArgTypeError); + throws(() => opendirSync("/tmp", { encoding: 123 }), kInvalidArgValueError); + + const dir = opendirSync("/", { recursive: true }); + strictEqual(dir.path, "/"); + strictEqual(dir.readSync().name, "bundle"); + strictEqual(dir.readSync().name, "bundle/worker"); + strictEqual(dir.readSync().name, "tmp"); + strictEqual(dir.readSync().name, "dev"); + strictEqual(dir.readSync().name, "dev/null"); + strictEqual(dir.readSync().name, "dev/zero"); + strictEqual(dir.readSync().name, "dev/full"); + strictEqual(dir.readSync().name, "dev/random"); + strictEqual(dir.readSync(), null); // All done. + dir.closeSync(); + + // Closing again throws + throws(() => dir.closeSync(), { code: "ERR_DIR_CLOSED" }); + // Reading again throws + throws(() => dir.readSync(), { code: "ERR_DIR_CLOSED" }); + }, +}; + +export const opendirSyncAndAsyncTest = { + async test() { + throws(() => opendir(), kInvalidArgTypeError); + throws(() => opendir(123), kInvalidArgTypeError); + throws(() => opendir("/tmp", { encoding: 123 }), kInvalidArgValueError); + + const { promise, resolve, reject } = Promise.withResolvers(); + opendir("/", { recursive: true }, (err, dir) => { + if (err) reject(err); + else resolve(dir); + }); + + await using dir = await promise; + + strictEqual((await dir.read()).name, "bundle"); + strictEqual((await dir.read()).name, "bundle/worker"); + strictEqual((await dir.read()).name, "tmp"); + strictEqual((await dir.read()).name, "dev"); + strictEqual((await dir.read()).name, "dev/null"); + strictEqual((await dir.read()).name, "dev/zero"); + strictEqual((await dir.read()).name, "dev/full"); + strictEqual((await dir.read()).name, "dev/random"); + strictEqual(await dir.read(), null); // All done. + }, +}; + +export const opendirSyncAndAsyncTest2 = { + async test() { + throws(() => opendir(), kInvalidArgTypeError); + throws(() => opendir(123), kInvalidArgTypeError); + throws(() => opendir("/tmp", { encoding: 123 }), kInvalidArgValueError); + + const { promise, resolve, reject } = Promise.withResolvers(); + opendir("/", { recursive: true }, (err, dir) => { + if (err) reject(err); + else resolve(dir); + }); + + await using dir = await promise; + + const entries = await Array.fromAsync(dir); + + strictEqual(entries.length, 8); + strictEqual(entries[0].name, "bundle"); + strictEqual(entries[1].name, "bundle/worker"); + strictEqual(entries[2].name, "tmp"); + strictEqual(entries[3].name, "dev"); + strictEqual(entries[4].name, "dev/null"); + strictEqual(entries[5].name, "dev/zero"); + strictEqual(entries[6].name, "dev/full"); + strictEqual(entries[7].name, "dev/random"); + }, +}; diff --git a/src/compat/fs/upstream-filehandle.fixture.mjs b/src/compat/fs/upstream-filehandle.fixture.mjs new file mode 100644 index 00000000..3fc916fa --- /dev/null +++ b/src/compat/fs/upstream-filehandle.fixture.mjs @@ -0,0 +1,170 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { ok, notStrictEqual, strictEqual, rejects } from "node:assert"; + +// sync and datasync are generally non-ops. The most they do is +// verify that the file descriptor is valid. + +import { promises } from "./index.js"; + +export const openCloseTest = { + async test() { + const fileHandle = await promises.open("/tmp/test.txt", "w+"); + strictEqual(fileHandle.constructor.name, "FileHandle"); + + // If the FileHandle is opened successfully, stat should work. + const stat = await fileHandle.stat(); + ok(stat.isFile()); + + // Close the file handle + await fileHandle.close(); + + // Verify that the file handle is closed + await rejects(fileHandle.stat(), { + code: "EBADF", + }); + }, +}; + +export const chmodChownTest = { + async test() { + await using fileHandle = await promises.open("/tmp/test.txt", "w+"); + + const stat = await fileHandle.stat(); + ok(stat.isFile()); + + // Change the file mode + await fileHandle.chmod(0o644); + + // Verify the mode has not changed since chmod is a no-op + const newStat = await fileHandle.stat(); + strictEqual(newStat.mode, stat.mode); + + // Change the file ownership + await fileHandle.chown(1000, 1000); + // Verify the ownership has not changed since chown is a no-op + const newStat2 = await fileHandle.stat(); + strictEqual(newStat2.uid, stat.uid); + strictEqual(newStat2.gid, stat.gid); + }, +}; + +export const syncTest = { + async test() { + await using fileHandle = await promises.open("/tmp/test.txt", "w+"); + strictEqual(fileHandle.constructor.name, "FileHandle"); + + // If the FileHandle is opened successfully, stat should work. + const stat = await fileHandle.stat(); + ok(stat.isFile()); + + // These are non-ops with no testable side effect. Just make + // sure they don't reject. + await fileHandle.sync(); + await fileHandle.datasync(); + }, +}; + +export const writeAppendReadFileTest = { + async test() { + await using fileHandle = await promises.open("/tmp/test.txt", "a+"); + strictEqual(fileHandle.constructor.name, "FileHandle"); + + await fileHandle.writeFile("Hello, World"); + + // Append some data to the file + await fileHandle.appendFile("!!!!\n"); + + // Read the file back + const data = await fileHandle.readFile("utf8"); + strictEqual(data, "Hello, World!!!!\n"); + }, +}; + +export const writeReadTest = { + async test() { + // Note that we're opening the file in append mode... + await using fileHandle = await promises.open("/tmp/test.txt", "a+"); + strictEqual(fileHandle.constructor.name, "FileHandle"); + + // Write some data to the file + await fileHandle.write("Hello, World"); + + // Append some data to the file + await fileHandle.write("!!!!\n"); + + // Read the file back + const buffer = Buffer.alloc(100); + const { bytesRead } = await fileHandle.read(buffer, 0, buffer.length, 0); + strictEqual(bytesRead, 17); + strictEqual(buffer.toString("utf8", 0, bytesRead), "Hello, World!!!!\n"); + + // Use readv + const buffer2 = Buffer.alloc(10); + const buffer3 = Buffer.alloc(10); + const { bytesRead: bytesRead2 } = await fileHandle.readv( + [buffer2, buffer3], + 0, + ); + strictEqual(bytesRead2, 17); + const buffer4 = Buffer.concat([buffer2, buffer3]); + strictEqual(buffer4.toString("utf8", 0, bytesRead2), "Hello, World!!!!\n"); + + await fileHandle.writev( + [Buffer.from("More data"), Buffer.from(" to write\n")], + 0, + ); + // Read the file back again + const buffer5 = Buffer.alloc(100); + const { bytesRead: bytesRead3 } = await fileHandle.read( + buffer5, + 0, + buffer5.length, + 0, + ); + strictEqual(bytesRead3, 36); + strictEqual( + buffer5.toString("utf8", 0, bytesRead3), + "Hello, World!!!!\nMore data to write\n", + ); + }, +}; + +export const truncateTest = { + async test() { + await using fileHandle = await promises.open("/tmp/test.txt", "w+"); + strictEqual(fileHandle.constructor.name, "FileHandle"); + + // Write some data to the file + await fileHandle.writeFile("Hello, World"); + + // Truncate the file to 5 bytes + await fileHandle.truncate(5); + + // Read the file back + const data = await fileHandle.readFile("utf8"); + strictEqual(data, "Hello"); + }, +}; + +export const utimesTest = { + async test() { + await using fileHandle = await promises.open("/tmp/test.txt", "w+"); + strictEqual(fileHandle.constructor.name, "FileHandle"); + + // If the FileHandle is opened successfully, stat should work. + const stat = await fileHandle.stat(); + ok(stat.isFile()); + + // Update the access and modification times + const now = new Date(); + await fileHandle.utimes(now, now); + + const newStat = await fileHandle.stat(); + + // atime should not be changed, mtime should. + strictEqual(newStat.atime.getTime(), stat.atime.getTime()); + notStrictEqual(newStat.mtime.getTime(), stat.mtime.getTime()); + }, +}; diff --git a/src/compat/fs/upstream-glob.fixture.mjs b/src/compat/fs/upstream-glob.fixture.mjs new file mode 100644 index 00000000..5baca2c3 --- /dev/null +++ b/src/compat/fs/upstream-glob.fixture.mjs @@ -0,0 +1,357 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { deepStrictEqual, ok, strictEqual } from "node:assert"; +import { glob, globSync, mkdirSync, writeFileSync, promises } from "./index.js"; + +// Node.js test fixture structure (minus symlinks — not supported in workerd VFS tests) +function setupFixtures() { + const base = "/tmp/gt"; + mkdirSync(base + "/a/.abcdef/x/y/z", { recursive: true }); + mkdirSync(base + "/a/abcdef/g", { recursive: true }); + mkdirSync(base + "/a/abcfed/g", { recursive: true }); + mkdirSync(base + "/a/b/c", { recursive: true }); + mkdirSync(base + "/a/bc/e", { recursive: true }); + mkdirSync(base + "/a/c/d/c", { recursive: true }); + mkdirSync(base + "/a/cb/e", { recursive: true }); + mkdirSync(base + "/a/x/.y", { recursive: true }); + mkdirSync(base + "/a/z/.y", { recursive: true }); + + writeFileSync(base + "/a/.abcdef/x/y/z/a", "x"); + writeFileSync(base + "/a/abcdef/g/h", "x"); + writeFileSync(base + "/a/abcfed/g/h", "x"); + writeFileSync(base + "/a/b/c/d", "x"); + writeFileSync(base + "/a/bc/e/f", "x"); + writeFileSync(base + "/a/c/d/c/b", "x"); + writeFileSync(base + "/a/cb/e/f", "x"); + writeFileSync(base + "/a/x/.y/b", "x"); + writeFileSync(base + "/a/z/.y/b", "x"); + writeFileSync(base + "/a/.b", "x"); + writeFileSync(base + "/a/b/.b", "x"); + + return base; +} + +const cwd = "/tmp/gt"; + +// ============================================================================ +// Pattern Matching Tests (ported from Node.js) +// ============================================================================ + +export const singleWildcardInSubdir = { + test() { + setupFixtures(); + const result = globSync("a/c/d/*/b", { cwd }); + deepStrictEqual(result.sort(), ["a/c/d/c/b"]); + }, +}; + +export const doubleSlashNormalization = { + test() { + setupFixtures(); + // Double slashes should be normalized + const result = globSync("a//c//d//*//b", { cwd }); + deepStrictEqual(result.sort(), ["a/c/d/c/b"]); + }, +}; + +export const multipleWildcards = { + test() { + setupFixtures(); + const result = globSync("a/*/d/*/b", { cwd }); + deepStrictEqual(result.sort(), ["a/c/d/c/b"]); + }, +}; + +export const extglobPlus = { + test() { + setupFixtures(); + // +(c|g) should match one or more occurrences of c or g + // a/*/+(c|g)/./d → matches a/b/c/./d which resolves to a/b/c/d + const result = globSync("a/*/+(c|g)/./d", { cwd }); + deepStrictEqual(result.sort(), ["a/b/c/d"]); + }, +}; + +export const braceExpansion = { + test() { + setupFixtures(); + const result = globSync("a/abc{fed,def}/g/h", { cwd }); + deepStrictEqual(result.sort(), ["a/abcdef/g/h", "a/abcfed/g/h"]); + }, +}; + +export const braceExpansionNoMatch = { + test() { + setupFixtures(); + // None of b,c,d,e,f directories have a **/g path + const result = globSync("a/{b,c,d,e,f}/**/g", { cwd }); + deepStrictEqual(result, []); + }, +}; + +export const globstarMatchesAll = { + test() { + setupFixtures(); + // a/b/** should match a/b itself and everything under it + const result = globSync("a/b/**", { cwd }); + deepStrictEqual(result.sort(), ["a/b", "a/b/.b", "a/b/c", "a/b/c/d"]); + }, +}; + +export const dotSlashGlobstar = { + test() { + setupFixtures(); + // ./**/g should find all 'g' entries + const result = globSync("./**/g", { cwd }); + deepStrictEqual(result.sort(), ["a/abcdef/g", "a/abcfed/g"]); + }, +}; + +export const globstarA = { + test() { + setupFixtures(); + // **/a — find all entries named 'a' + const result = globSync("**/a", { cwd }); + ok(result.includes("a")); + ok(result.includes("a/.abcdef/x/y/z/a")); + }, +}; + +export const threeWildcardLevelsF = { + test() { + setupFixtures(); + const result = globSync("*/*/*/f", { cwd }); + deepStrictEqual(result.sort(), ["a/bc/e/f", "a/cb/e/f"]); + }, +}; + +export const dotSlashGlobstarF = { + test() { + setupFixtures(); + const result = globSync("./**/f", { cwd }); + deepStrictEqual(result.sort(), ["a/bc/e/f", "a/cb/e/f"]); + }, +}; + +export const dotFileGlobstar = { + test() { + setupFixtures(); + // **/.b should match dot files named .b + const result = globSync("**/.b", { cwd }); + deepStrictEqual(result.sort(), ["a/.b", "a/b/.b"]); + }, +}; + +export const globstarCharClass = { + test() { + setupFixtures(); + // a/**/[cg] — match entries named 'c' or 'g' at any depth under a/ + const result = globSync("a/**/[cg]", { cwd }); + ok(result.includes("a/abcdef/g")); + ok(result.includes("a/abcfed/g")); + ok(result.includes("a/b/c")); + ok(result.includes("a/c")); + ok(result.includes("a/c/d/c")); + }, +}; + +export const parentTraversal = { + test() { + setupFixtures(); + // a/**/[cg]/../[cg] — go into [cg] dir, go up, match [cg] again + const result = globSync("a/**/[cg]/../[cg]", { cwd }); + ok(result.length > 0); + // Should find paths where a directory matching [cg] has a sibling matching [cg] + ok(result.includes("a/c")); + }, +}; + +export const extglobNegation = { + test() { + setupFixtures(); + // a/!(doesnotexist)/** — match all under a/ except entries named 'doesnotexist' + const result = globSync("a/!(doesnotexist)/**", { cwd }); + ok(result.length > 0); + ok(result.includes("a/b")); + ok(result.includes("a/b/c")); + ok(result.includes("a/b/c/d")); + }, +}; + +// ============================================================================ +// Exclude Tests +// ============================================================================ + +export const excludeFunction = { + test() { + setupFixtures(); + // Exclude everything — should return empty + const result = globSync("a/**", { cwd, exclude: () => true }); + deepStrictEqual(result, []); + }, +}; + +export const excludeFunctionSelective = { + test() { + setupFixtures(); + // Exclude paths containing 'abcdef' + const result = globSync("a/abc*/g/h", { + cwd, + exclude: (p) => p.includes("abcdef"), + }); + deepStrictEqual(result, ["a/abcfed/g/h"]); + }, +}; + +export const excludeArrayPattern = { + test() { + setupFixtures(); + // Exclude using array of glob patterns + const result = globSync("a/abc*/g/h", { + cwd, + exclude: ["**/abcdef/**"], + }); + deepStrictEqual(result, ["a/abcfed/g/h"]); + }, +}; + +export const excludeArrayMatchesAll = { + test() { + setupFixtures(); + // Exclude all with wildcard + const result = globSync("a/**", { cwd, exclude: ["**"] }); + deepStrictEqual(result, []); + }, +}; + +// ============================================================================ +// withFileTypes Tests +// ============================================================================ + +export const withFileTypesTest = { + test() { + setupFixtures(); + const result = globSync("a/b/c/d", { cwd, withFileTypes: true }); + strictEqual(result.length, 1); + const dirent = result[0]; + strictEqual(dirent.name, "d"); + ok(dirent.isFile()); + ok(!dirent.isDirectory()); + }, +}; + +export const withFileTypesDirTest = { + test() { + setupFixtures(); + const result = globSync("a/b/c", { cwd, withFileTypes: true }); + strictEqual(result.length, 1); + const dirent = result[0]; + strictEqual(dirent.name, "c"); + ok(dirent.isDirectory()); + ok(!dirent.isFile()); + }, +}; + +// ============================================================================ +// Callback API Tests +// ============================================================================ + +export const callbackGlobTest = { + async test() { + setupFixtures(); + const result = await new Promise((resolve, reject) => { + glob("a/abc{fed,def}/g/h", { cwd }, (err, matches) => { + if (err) reject(err); + else resolve(matches); + }); + }); + deepStrictEqual([...result].sort(), ["a/abcdef/g/h", "a/abcfed/g/h"]); + }, +}; + +export const callbackGlobNoOptions = { + async test() { + const result = await new Promise((resolve, reject) => { + glob("*.nonexistent", (err, matches) => { + if (err) reject(err); + else resolve(matches); + }); + }); + deepStrictEqual(result, []); + }, +}; + +// ============================================================================ +// Promises API Tests +// ============================================================================ + +export const promisesGlobTest = { + async test() { + setupFixtures(); + const results = []; + for await (const entry of promises.glob("a/abc{fed,def}/g/h", { cwd })) { + results.push(entry); + } + deepStrictEqual(results.sort(), ["a/abcdef/g/h", "a/abcfed/g/h"]); + }, +}; + +export const promisesGlobstarTest = { + async test() { + setupFixtures(); + const results = []; + for await (const entry of promises.glob("**/.b", { cwd })) { + results.push(entry); + } + deepStrictEqual(results.sort(), ["a/.b", "a/b/.b"]); + }, +}; + +// ============================================================================ +// Edge Cases +// ============================================================================ + +export const noMatchTest = { + test() { + setupFixtures(); + const result = globSync("nonexistent/**", { cwd }); + deepStrictEqual(result, []); + }, +}; + +export const exactPathTest = { + test() { + setupFixtures(); + // Exact path match (no wildcards) + const result = globSync("a/b/c/d", { cwd }); + deepStrictEqual(result, ["a/b/c/d"]); + }, +}; + +export const defaultCwdTest = { + test() { + // Without cwd option, should use process.cwd() and not throw + const result = globSync("*.nonexistent"); + deepStrictEqual(result, []); + }, +}; + +export const multiplePatternsTest = { + test() { + setupFixtures(); + const result = globSync(["a/b/c/d", "a/bc/e/f"], { cwd }); + deepStrictEqual(result.sort(), ["a/b/c/d", "a/bc/e/f"]); + }, +}; + +export const braceWithGlobstar = { + test() { + setupFixtures(); + // a/{b/**,b/c} should match a/b and everything under it, plus a/b/c + const result = globSync("a/{b/**,b/c}", { cwd }); + ok(result.includes("a/b")); + ok(result.includes("a/b/c")); + ok(result.includes("a/b/c/d")); + }, +}; diff --git a/src/compat/fs/upstream-link.fixture.mjs b/src/compat/fs/upstream-link.fixture.mjs new file mode 100644 index 00000000..228f3a75 --- /dev/null +++ b/src/compat/fs/upstream-link.fixture.mjs @@ -0,0 +1,468 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert"; + +import { + existsSync, + statSync, + linkSync, + lstatSync, + symlinkSync, + readlinkSync, + realpathSync, + unlinkSync, + link, + symlink, + readlink, + realpath, + unlink, + promises, +} from "./index.js"; + +strictEqual(typeof existsSync, "function"); +strictEqual(typeof statSync, "function"); +strictEqual(typeof linkSync, "function"); +strictEqual(typeof lstatSync, "function"); +strictEqual(typeof symlinkSync, "function"); +strictEqual(typeof readlinkSync, "function"); +strictEqual(typeof realpathSync, "function"); +strictEqual(typeof unlinkSync, "function"); +strictEqual(typeof link, "function"); +strictEqual(typeof symlink, "function"); +strictEqual(typeof readlink, "function"); +strictEqual(typeof realpath, "function"); +strictEqual(typeof unlink, "function"); +strictEqual(typeof promises.link, "function"); +strictEqual(typeof promises.symlink, "function"); +strictEqual(typeof promises.readlink, "function"); +strictEqual(typeof promises.realpath, "function"); +strictEqual(typeof promises.unlink, "function"); + +const src = "/dev/null"; +const dst = "/tmp/test-link"; + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; +const kEInvalError = { code: "EINVAL" }; +const kENoentError = { code: "ENOENT" }; +const kEExistError = { code: "EEXIST" }; +const kEPermError = { code: "EPERM" }; +const kEIsDir = { code: "EISDIR" }; + +export const linkAndUnlinkSyncTest = { + test() { + throws(() => linkSync(), kInvalidArgTypeError); + throws(() => linkSync(""), kInvalidArgTypeError); + throws(() => realpathSync(), kInvalidArgTypeError); + throws(() => readlinkSync(), kInvalidArgTypeError); + throws(() => unlinkSync(), kInvalidArgTypeError); + + ok(existsSync(src)); + ok(!existsSync(dst)); + linkSync(src, dst); + ok(existsSync(dst)); + + // These are the same file. + deepStrictEqual(statSync(dst), statSync(src)); + deepStrictEqual(lstatSync(dst), statSync(src)); + + // Because this is a hard link, the realpath is /tmp/a + strictEqual(realpathSync(dst), dst); + + // And readlinkSync throws + throws(() => readlinkSync(dst), kEInvalError); + + // Linking from a non-existent file throws. + throws(() => linkSync("/does/not/exist", "/tmp/abc"), kENoentError); + + // Make sure the destination does not exist after the failed link. + ok(!existsSync("/tmp/abc")); + throws(() => linkSync("/dev/foo", "/tmp/abc"), kENoentError); + ok(!existsSync("/tmp/abc")); + + // Creating a link in a non-existent directory throws. + throws(() => linkSync("/dev/null", "/tmp/abc/xyz"), kENoentError); + + // Linking when the path already exists throws. + throws(() => linkSync(src, dst), kEExistError); + + // Linking with an empty file name throws. + throws(() => linkSync(src, "/foo/"), kEInvalError); + + // Invalid paths throw + throws(() => linkSync(src, "/dev/null/test"), kEInvalError); + throws(() => linkSync("/dev/null/test", "/tmp/null/test"), kENoentError); + + // Attempting to link directories throws + throws(() => linkSync("/tmp", "/tmp/abc"), kEPermError); + + // The destination can be unlinked + unlinkSync(dst); + ok(!existsSync(dst)); + + // But unlinking does not remove the source. + ok(existsSync(src)); + + // Cannot unlink read-only files + throws(() => unlinkSync("/bundle/worker"), kEPermError); + throws(() => unlinkSync("/bundle"), kEIsDir); + + // Make sure that the failed unlink had no effect. + ok(existsSync("/bundle/worker")); + ok(existsSync("/bundle")); + }, +}; + +export const linkAndUnlinkAsyncCallbackTest = { + async test() { + const doLinkTest = async (src, dst) => { + const { promise, resolve, reject } = Promise.withResolvers(); + link(src, dst, (err) => { + try { + if (err) return reject(err); + ok(existsSync(dst)); + deepStrictEqual(statSync(dst), statSync(src)); + deepStrictEqual(lstatSync(dst), statSync(src)); + strictEqual(realpathSync(dst), dst); + throws(() => readlinkSync(dst), kEInvalError); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + const doFailedLinkTest = async (src, dst, expectedError) => { + const { promise, resolve, reject } = Promise.withResolvers(); + link(src, dst, (err) => { + try { + strictEqual(err.code, expectedError.code); + ok(!existsSync(dst)); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + const doUnlinkTest = async (dst) => { + const { promise, resolve, reject } = Promise.withResolvers(); + unlink(dst, (err) => { + try { + if (err) return reject(err); + ok(!existsSync(dst)); + ok(existsSync(src)); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + const doFailedUnlinkTest = async (dst, expectedError) => { + const { promise, resolve, reject } = Promise.withResolvers(); + unlink(dst, (err) => { + try { + strictEqual(err.code, expectedError.code); + ok(existsSync(dst)); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + throws(() => link(), kInvalidArgTypeError); + throws(() => link(""), kInvalidArgTypeError); + throws(() => link("", ""), kInvalidArgTypeError); + throws(() => realpath(), kInvalidArgTypeError); + throws(() => realpath(""), kInvalidArgTypeError); + throws(() => readlink(), kInvalidArgTypeError); + throws(() => readlink(""), kInvalidArgTypeError); + throws(() => unlink(), kInvalidArgTypeError); + throws(() => unlink(""), kInvalidArgTypeError); + + ok(existsSync(src)); + ok(!existsSync(dst)); + await doLinkTest(src, dst); + + await doFailedLinkTest("/does/not/exist", "/tmp/abc", kENoentError); + ok(!existsSync("/tmp/abc")); + + await doFailedLinkTest("/dev/foo", "/tmp/abc", kENoentError); + ok(!existsSync("/tmp/abc")); + + // Creating a link in a non-existent directory throws. + await doFailedLinkTest("/dev/null", "/tmp/abc/xyz", kENoentError); + + // Invalid paths throw + await doFailedLinkTest(src, "/dev/null/test", kEInvalError); + await doFailedLinkTest("/dev/null/test", "/tmp/null/test", kENoentError); + + // Attempting to link directories throws + await doFailedLinkTest("/tmp", "/tmp/abc", kEPermError); + + await doUnlinkTest(dst); + + await doFailedUnlinkTest("/bundle/worker", kEPermError); + await doFailedUnlinkTest("/bundle", kEIsDir); + }, +}; + +export const linkAndLinkPromisesTest = { + async test() { + await rejects(promises.link(), kInvalidArgTypeError); + await rejects(promises.link(""), kInvalidArgTypeError); + await rejects(promises.realpath(), kInvalidArgTypeError); + await rejects(promises.readlink(), kInvalidArgTypeError); + await rejects(promises.unlink(), kInvalidArgTypeError); + + ok(existsSync(src)); + ok(!existsSync(dst)); + await promises.link(src, dst); + + // These are the same file. + deepStrictEqual(statSync(dst), statSync(src)); + deepStrictEqual(lstatSync(dst), statSync(src)); + + // Because this is a hard link, the realpath is /tmp/a + strictEqual(await promises.realpath(dst), dst); + + // And readlinkSync throws + await rejects(promises.readlink(dst), kEInvalError); + + await rejects(promises.link("/does/not/exist", "/tmp/abc"), kENoentError); + ok(!existsSync("/tmp/abc")); + + await rejects(promises.link("/dev/null", "/tmp/abc/xyz"), kENoentError); + + await rejects(promises.link(src, dst), kEExistError); + + await rejects(promises.link(src, "/foo/"), kEInvalError); + + await rejects(promises.link(src, "/dev/null/test"), kEInvalError); + + await rejects( + promises.link("/dev/null/test", "/tmp/null/test"), + kENoentError, + ); + + await rejects(promises.link("/tmp", "/tmp/abc"), kEPermError); + + await promises.unlink(dst); + ok(!existsSync(dst)); + // But unlinking does not remove the source. + ok(existsSync(src)); + + await rejects(promises.unlink("/bundle/worker"), kEPermError); + await rejects(promises.unlink("/bundle"), kEIsDir); + }, +}; + +export const symlinkAndUnlinkSyncTest = { + test() { + throws(() => symlinkSync(), kInvalidArgTypeError); + throws(() => symlinkSync(""), kInvalidArgTypeError); + + ok(existsSync(src)); + ok(!existsSync(dst)); + symlinkSync(src, dst); + ok(existsSync(dst)); + + deepStrictEqual(statSync(dst), statSync(src)); + + // The symlink points to the source. + strictEqual(readlinkSync(dst), src); + + // The realpath is the destination + strictEqual(realpathSync(dst), src); + + // And readlinkSync does not throw + strictEqual(readlinkSync(dst), src); + + // Linking from a non-existent file works + symlinkSync("/does/not/exist", "/tmp/abc"); + + // Exists sync follows the symlink, and show return false. + ok(!existsSync("/tmp/abc")); + + // But the symlink does exist. + const linkStat = lstatSync("/tmp/abc"); + ok(linkStat.isSymbolicLink()); + + // Creating a link in a non-existent directory throws. + throws(() => symlinkSync("/dev/null", "/tmp/abc/xyz"), kENoentError); + + // Linking when the path already exists throws. + throws(() => symlinkSync(src, dst), kEExistError); + + // Linking with an empty file name throws. + throws(() => symlinkSync(src, "/foo/"), kEInvalError); + + // Invalid paths throw + throws(() => symlinkSync(src, "/dev/null/test"), kEInvalError); + throws(() => symlinkSync("/dev/null/test", "/tmp/null/test"), kENoentError); + + // Symlinking directories works. + symlinkSync("/tmp", "/tmp/linked-dir"); + ok(existsSync("/tmp/linked-dir")); + + // The destination can be unlinked + unlinkSync(dst); + ok(!existsSync(dst)); + + // But unlinking does not remove the source. + ok(existsSync(src)); + }, +}; + +export const symlinkAndUnlinkAsyncCallbackTest = { + async test() { + throws(() => symlink(), kInvalidArgTypeError); + throws(() => symlink(""), kInvalidArgTypeError); + throws(() => symlink("", ""), kInvalidArgTypeError); + throws(() => realpath(), kInvalidArgTypeError); + throws(() => realpath(""), kInvalidArgTypeError); + throws(() => readlink(), kInvalidArgTypeError); + throws(() => readlink(""), kInvalidArgTypeError); + throws(() => unlink(), kInvalidArgTypeError); + throws(() => unlink(""), kInvalidArgTypeError); + + const doSymlinkTest = async (src, dst) => { + const { promise, resolve, reject } = Promise.withResolvers(); + symlink(src, dst, (err) => { + if (err) return reject(err); + try { + const srcStat = statSync(src, { throwIfNoEntry: false }); + if (srcStat != null) { + const dstStat = statSync(dst, { throwIfNoEntry: false }); + deepStrictEqual(dstStat, srcStat); + } else { + // The source does not exist but the symlink should still. + const dstStat = lstatSync(dst); + ok(dstStat.isSymbolicLink()); + } + strictEqual(readlinkSync(dst), src); + strictEqual(realpathSync(dst), src); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + const doFailedSymlinkTest = async (src, dst, expectedError) => { + const { promise, resolve, reject } = Promise.withResolvers(); + symlink(src, dst, (err) => { + try { + strictEqual(err.code, expectedError.code); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + const doUnlinkTest = async (dst) => { + const { promise, resolve, reject } = Promise.withResolvers(); + unlink(dst, (err) => { + try { + if (err) return reject(err); + ok(!existsSync(dst)); + ok(existsSync(src)); + } catch (err) { + reject(err); + } + resolve(); + }); + await promise; + }; + + ok(existsSync(src)); + ok(!existsSync(dst)); + await doSymlinkTest(src, dst); + await doSymlinkTest("/does/not/exist", "/tmp/abc"); + + // Exists sync follows the symlink, and show return false. + ok(!existsSync("/tmp/abc")); + + await doFailedSymlinkTest(src, dst, kEExistError); + await doFailedSymlinkTest(src, "/foo/", kEInvalError); + await doFailedSymlinkTest(src, "/dev/null/test", kEInvalError); + await doFailedSymlinkTest("/dev/null/test", "/tmp/null/test", kENoentError); + + // Symlinking directories works. + await doSymlinkTest("/tmp", "/tmp/linked-dir"); + + // The destination can be unlinked + await doUnlinkTest(dst); + }, +}; + +export const symlinkAndUnlinkPromisesTest = { + async test() { + await rejects(promises.symlink(), kInvalidArgTypeError); + await rejects(promises.symlink(""), kInvalidArgTypeError); + await rejects(promises.realpath(), kInvalidArgTypeError); + await rejects(promises.readlink(), kInvalidArgTypeError); + await rejects(promises.unlink(), kInvalidArgTypeError); + + ok(existsSync(src)); + ok(!existsSync(dst)); + await promises.symlink(src, dst); + + deepStrictEqual(statSync(dst), statSync(src)); + + // The symlink points to the source. + strictEqual(await promises.readlink(dst), src); + + // The realpath is the destination + strictEqual(await promises.realpath(dst), src); + + // And readlink does not throw + strictEqual(await promises.readlink(dst), src); + + // Linking from a non-existent file works + await promises.symlink("/does/not/exist", "/tmp/abc"); + + // Exists sync follows the symlink, and show return false. + ok(!existsSync("/tmp/abc")); + + // But the symlink does exist. + const linkStat = lstatSync("/tmp/abc"); + ok(linkStat.isSymbolicLink()); + + // Creating a link in a non-existent directory throws. + await rejects(promises.symlink("/dev/null", "/tmp/abc/xyz"), kENoentError); + + // Linking when the path already exists throws. + await rejects(promises.symlink(src, dst), kEExistError); + + // Linking with an empty file name throws. + await rejects(promises.symlink(src, "/foo/"), kEInvalError); + + // Invalid paths throw + await rejects(promises.symlink(src, "/dev/null/test"), kEInvalError); + await rejects( + promises.symlink("/dev/null/test", "/tmp/null/test"), + kENoentError, + ); + + // Symlinking directories works. + await promises.symlink("/tmp", "/tmp/linked-dir"); + ok(existsSync("/tmp/linked-dir")); + + // The destination can be unlinked + await promises.unlink(dst); + ok(!existsSync(dst)); + + // But unlinking does not remove the source. + ok(existsSync(src)); + }, +}; diff --git a/src/compat/fs/upstream-misc.fixture.mjs b/src/compat/fs/upstream-misc.fixture.mjs new file mode 100644 index 00000000..6f4eb1dd --- /dev/null +++ b/src/compat/fs/upstream-misc.fixture.mjs @@ -0,0 +1,240 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { notStrictEqual, strictEqual, throws } from "node:assert"; + +// sync and datasync are generally non-ops. The most they do is +// verify that the file descriptor is valid. + +import { + openSync, + closeSync, + fsyncSync, + fdatasyncSync, + fdatasync, + fsync, + statfs, + statfsSync, + openAsBlob, + WriteStream, + FileWriteStream, + ReadStream, + FileReadStream, + constants, + F_OK, + R_OK, + W_OK, + X_OK, + writeFileSync, + writeSync, +} from "./index.js"; + +strictEqual(typeof openSync, "function"); +strictEqual(typeof closeSync, "function"); +strictEqual(typeof fdatasyncSync, "function"); +strictEqual(typeof fsyncSync, "function"); +strictEqual(typeof fdatasync, "function"); +strictEqual(typeof fsync, "function"); + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; +const kBadFError = { code: "EBADF" }; + +export const miscTest = { + async test() { + throws(() => fsyncSync(), kInvalidArgTypeError); + throws(() => fdatasyncSync(), kInvalidArgTypeError); + throws(() => fsyncSync("hello"), kInvalidArgTypeError); + throws(() => fdatasyncSync("hello"), kInvalidArgTypeError); + throws(() => fsync(), kInvalidArgTypeError); + throws(() => fdatasync(), kInvalidArgTypeError); + throws(() => fsync("hello"), kInvalidArgTypeError); + throws(() => fdatasync("hello"), kInvalidArgTypeError); + throws(() => fsyncSync(123), kBadFError); + throws(() => fdatasyncSync(123), kBadFError); + + const fd = openSync("/dev/null", "r"); + fsyncSync(fd); + fdatasyncSync(fd); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fsync(fd, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fdatasync(fd, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fsync(123, (err) => { + if (!err) { + reject(new Error("Expected an error")); + return; + } + strictEqual(err.code, "EBADF"); + resolve(); + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fdatasync(123, (err) => { + if (!err) { + reject(new Error("Expected an error")); + return; + } + strictEqual(err.code, "EBADF"); + resolve(); + }); + await promise; + } + + closeSync(fd); + }, +}; + +export const statFsTest = { + async test() { + const stat = statfsSync("/"); + strictEqual(typeof stat, "object"); + strictEqual(stat.type, 0); + strictEqual(stat.bsize, 0); + strictEqual(stat.blocks, 0); + strictEqual(stat.bfree, 0); + strictEqual(stat.bavail, 0); + strictEqual(stat.files, 0); + strictEqual(stat.ffree, 0); + + throws(() => statfsSync(123), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => statfsSync("/does/not/exist", { bigint: 123 }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + const { promise, resolve, reject } = Promise.withResolvers(); + statfs("/", (err, stat) => { + if (err) reject(err); + else { + strictEqual(typeof stat, "object"); + strictEqual(stat.type, 0); + strictEqual(stat.bsize, 0); + strictEqual(stat.blocks, 0); + strictEqual(stat.bfree, 0); + strictEqual(stat.bavail, 0); + strictEqual(stat.files, 0); + strictEqual(stat.ffree, 0); + resolve(); + } + }); + await promise; + + throws(() => statfs(123, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => statfs("/does/not/exist", { bigint: 123 }), { + code: "ERR_INVALID_ARG_TYPE", + }); + }, +}; + +export const pathLimitTest = { + test() { + // Trying to open a path longer than 4096 characters should throw an error. + const longPath = "/tmp/a".repeat(4097); + throws(() => openSync(longPath, "r"), { + message: /File path is too long/, + }); + + // Trying to open a path with more than 48 segments should throw an error. + const tooManySegments = "/a".repeat(49); + throws(() => openSync(tooManySegments, "r"), { + message: /File path has too many segments/, + }); + }, +}; + +export const openAsBlobTest = { + async test() { + // We currently do not implement openAsBlob, but let's verify arg validation. + throws(() => openAsBlob(), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => openAsBlob(1), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => openAsBlob("/", 123), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => openAsBlob("/", { type: 123 }), { + code: "ERR_INVALID_ARG_TYPE", + }); + + writeFileSync("/tmp/abc", "123"); + throws(() => openAsBlob("/"), { + message: /illegal operation on a directory/, + }); + throws(() => openAsBlob(new URL("file:///")), { + message: /illegal operation on a directory/, + }); + + const blob = openAsBlob("/tmp/abc", { type: "text/plain" }); + strictEqual(blob instanceof Blob, true); + strictEqual(blob.size, 3); + strictEqual(blob.type, "text/plain"); + strictEqual(await blob.text(), "123"); + }, +}; + +export const otherExportsTest = { + test() { + strictEqual(WriteStream, FileWriteStream); + strictEqual(ReadStream, FileReadStream); + strictEqual(constants.F_OK, F_OK); + strictEqual(constants.R_OK, R_OK); + strictEqual(constants.W_OK, W_OK); + strictEqual(constants.X_OK, X_OK); + notStrictEqual(F_OK, undefined); + notStrictEqual(R_OK, undefined); + notStrictEqual(W_OK, undefined); + notStrictEqual(X_OK, undefined); + notStrictEqual(WriteStream, undefined); + notStrictEqual(ReadStream, undefined); + }, +}; + +export const oobWriteTest = { + test() { + const v3 = Buffer.from("Test data for write operations"); + const fd = openSync("/tmp/write-test.bin", "w+"); + throws( + () => { + strictEqual(writeSync(fd, v3, 10, 10, 4294967295), 0); + }, + { + message: /File size limit exceeded/, + }, + ); + + strictEqual(writeSync(fd, v3, 10, 10, 134217718), 10); + }, +}; diff --git a/src/compat/fs/upstream-nodejs.fixture.mjs b/src/compat/fs/upstream-nodejs.fixture.mjs new file mode 100644 index 00000000..dd1cc1ea --- /dev/null +++ b/src/compat/fs/upstream-nodejs.fixture.mjs @@ -0,0 +1,1615 @@ +import { changeDirectory as chdir } from "./writable.js"; +const process = { chdir }; +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { + ifError, + ok, + rejects, + deepStrictEqual, + strictEqual, + notStrictEqual, + throws, + doesNotThrow, +} from "node:assert"; +import zlib from "node:zlib"; +import { Readable } from "node:stream"; + +import { + existsSync, + statSync, + mkdirSync, + rmSync, + symlinkSync, + lstatSync, + openSync, + closeSync, + fstatSync, + ftruncateSync, + truncateSync, + writeSync, + writevSync, + readSync, + readvSync, + readFileSync, + writeFileSync, + appendFileSync, + copyFileSync, + renameSync, + close, + fstat, + ftruncate, + truncate, + unlinkSync, + write, + writev, + writeFile, + appendFile, + read, + readdir, + readlink, + realpath, + mkdtemp, + ReadStream, + WriteStream, + readdirSync, + readlinkSync, + readv, + readFile, + rename, + copyFile, + realpathSync, + mkdtempSync, + constants, + promises, + createWriteStream, + createReadStream, +} from "./index.js"; + +import { join } from "node:path"; + +const { COPYFILE_EXCL } = constants; + +const kErrInvalidArgType = { code: "ERR_INVALID_ARG_TYPE" }; +const kErrInvalidArgValue = { code: "ERR_INVALID_ARG_VALUE" }; +const kErrEBadf = { code: "EBADF" }; +const kErrEExist = { code: "EEXIST" }; +const kErrOutOfRange = { code: "ERR_OUT_OF_RANGE" }; + +export const openCloseTest = { + async test() { + throws(() => fstatSync(123), kErrEBadf); + throws(() => fstatSync(123, { bigint: "yes" }), kErrInvalidArgType); + throws(() => fstatSync("abc"), kErrInvalidArgType); + + // Test that all the mode combinations work + const modes = [ + "r", + "r+", + "w", + "w+", + "a", + "a+", + "rs", + "rs+", + "wx", + "wx+", + "ax", + "ax+", + ]; + for (const mode of modes) { + // Modes without O_CREAT need an existing file. + if (!/[wa]/.test(mode)) writeFileSync("/tmp/test.txt", ""); + const fd = openSync("/tmp/test.txt", mode); + ok(existsSync("/tmp/test.txt")); + const stat = fstatSync(fd, { bigint: true }); + ok(stat); + unlinkSync("/tmp/test.txt"); + } + + ok(!existsSync("/tmp/test.txt")); + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + // Check the exclusive option fails when the file already exists + throws(() => openSync("/tmp/test.txt", "wx"), kErrEExist); + throws(() => openSync("/tmp/test.txt", "wx+"), kErrEExist); + throws(() => openSync("/tmp/test.txt", "ax"), kErrEExist); + throws(() => openSync("/tmp/test.txt", "ax+"), kErrEExist); + throws(() => openSync(Buffer.from("/tmp/test.txt"), "wx"), kErrEExist); + throws(() => openSync(Buffer.from("/tmp/test.txt"), "wx+"), kErrEExist); + throws(() => openSync(Buffer.from("/tmp/test.txt"), "ax"), kErrEExist); + throws(() => openSync(Buffer.from("/tmp/test.txt"), "ax+"), kErrEExist); + + const stat = fstatSync(fd, { bigint: true }); + ok(stat); + ok(stat.isFile()); + ok(!stat.isDirectory()); + strictEqual(stat.size, 0n); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fstat(fd, (err, stat) => { + try { + ifError(err); + ok(stat); + ok(stat.isFile()); + ok(!stat.isDirectory()); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + // Close the file + closeSync(fd); + // Can close multiple times + closeSync(fd); + // Can close non-existent file descriptors + closeSync(123); + + ["a", {}, null].forEach((i) => { + throws(() => closeSync(i), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => close(i), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => close(0, i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + close(fd, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + }, +}; + +export const openFlagsTest = { + test() { + const { O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND, O_EXCL } = constants; + + // O_TRUNC truncates an existing file, both as a string flag and numeric. + for (const flags of ["w", "w+", O_WRONLY | O_CREAT | O_TRUNC]) { + writeFileSync("/tmp/test.txt", "hello world long"); + const fd = openSync("/tmp/test.txt", flags); + strictEqual(fstatSync(fd).size, 0); + writeSync(fd, Buffer.from("hi")); + closeSync(fd); + strictEqual(readFileSync("/tmp/test.txt", "utf8"), "hi"); + } + + // Without O_TRUNC, existing content is preserved. + writeFileSync("/tmp/test.txt", "hello world long"); + { + const fd = openSync("/tmp/test.txt", O_WRONLY | O_CREAT); + writeSync(fd, Buffer.from("hi")); + closeSync(fd); + strictEqual(readFileSync("/tmp/test.txt", "utf8"), "hillo world long"); + } + + // Numeric O_APPEND appends regardless of position. + writeFileSync("/tmp/test.txt", "hello"); + { + const fd = openSync("/tmp/test.txt", O_WRONLY | O_CREAT | O_APPEND); + writeSync(fd, Buffer.from(" world"), 0, 6, 0); + closeSync(fd); + strictEqual(readFileSync("/tmp/test.txt", "utf8"), "hello world"); + } + + // Numeric O_EXCL with O_CREAT fails on an existing file. + throws( + () => openSync("/tmp/test.txt", O_WRONLY | O_CREAT | O_EXCL), + kErrEExist, + ); + throws( + () => openSync("/tmp/test.txt", O_RDWR | O_CREAT | O_EXCL | O_TRUNC), + kErrEExist, + ); + strictEqual(readFileSync("/tmp/test.txt", "utf8"), "hello world"); + + // O_TRUNC on a read-only open is ignored. + { + const fd = openSync("/tmp/test.txt", O_TRUNC); + closeSync(fd); + strictEqual(readFileSync("/tmp/test.txt", "utf8"), "hello world"); + } + + unlinkSync("/tmp/test.txt"); + }, +}; + +export const openMissingTest = { + async test() { + const { O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC } = constants; + const kErrENoEnt = { + code: "ENOENT", + syscall: "open", + path: "/tmp/missing", + }; + + // Without O_CREAT, opening a missing path is ENOENT and creates nothing. + for (const flags of [ + "r", + "r+", + "rs+", + O_RDONLY, + O_WRONLY, + O_RDWR, + O_RDWR | O_TRUNC, + ]) { + throws(() => openSync("/tmp/missing", flags), kErrENoEnt); + ok(!existsSync("/tmp/missing")); + } + + // With O_CREAT, the file is created. + for (const flags of [ + "w", + "a", + "w+", + O_WRONLY | O_CREAT, + O_RDONLY | O_CREAT, + ]) { + const fd = openSync("/tmp/missing", flags); + closeSync(fd); + ok(existsSync("/tmp/missing")); + strictEqual(readFileSync("/tmp/missing", "utf8"), ""); + + // O_CREAT|O_EXCL fails once it exists, regardless of access mode. + throws( + () => openSync("/tmp/missing", O_RDONLY | O_CREAT | O_EXCL), + kErrEExist, + ); + throws(() => openSync("/tmp/missing", "wx"), kErrEExist); + unlinkSync("/tmp/missing"); + } + + // O_CREAT|O_EXCL on a missing path creates it. + closeSync(openSync("/tmp/missing", O_WRONLY | O_CREAT | O_EXCL)); + ok(existsSync("/tmp/missing")); + unlinkSync("/tmp/missing"); + + // O_CREAT does not create intermediate directories. + for (const flags of ["r", "w", "a", O_WRONLY | O_CREAT | O_EXCL]) { + throws(() => openSync("/tmp/missing/file", flags), { + code: "ENOENT", + syscall: "open", + path: "/tmp/missing/file", + }); + ok(!existsSync("/tmp/missing")); + } + + // A non-directory path component is ENOTDIR. + writeFileSync("/tmp/missing", ""); + for (const flags of ["r", "w"]) { + throws(() => openSync("/tmp/missing/file", flags), { + code: "ENOTDIR", + syscall: "open", + path: "/tmp/missing/file", + }); + } + strictEqual(readFileSync("/tmp/missing", "utf8"), ""); + unlinkSync("/tmp/missing"); + + // Streams open with their own flags: WriteStream defaults to 'w' and + // honours 'a'; ReadStream defaults to 'r' and fails on a missing path. + const writeAll = (path, data, options) => + new Promise((resolve, reject) => { + createWriteStream(path, options) + .on("close", resolve) + .on("error", reject) + .end(data); + }); + await writeAll("/tmp/missing", "first"); + strictEqual(readFileSync("/tmp/missing", "utf8"), "first"); + await writeAll("/tmp/missing", "second"); + strictEqual(readFileSync("/tmp/missing", "utf8"), "second"); + await writeAll("/tmp/missing", " third", { flags: "a" }); + strictEqual(readFileSync("/tmp/missing", "utf8"), "second third"); + unlinkSync("/tmp/missing"); + + const streamError = (make) => + new Promise((resolve, reject) => { + make().on("open", resolve).on("error", reject); + }); + await rejects( + streamError(() => createReadStream("/tmp/missing")), + kErrENoEnt, + ); + ok(!existsSync("/tmp/missing")); + + // Invalid flags are validated synchronously by fs.open and surface as + // the stream error rather than a hang. + await rejects( + streamError(() => createReadStream("/tmp/missing", { flags: "zz" })), + { code: "ERR_INVALID_ARG_VALUE" }, + ); + await rejects( + streamError(() => createWriteStream("/tmp/missing", { flags: "zz" })), + { code: "ERR_INVALID_ARG_VALUE" }, + ); + ok(!existsSync("/tmp/missing")); + }, +}; + +export const ftruncateTest = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + throws(() => ftruncateSync("hello"), kErrInvalidArgType); + throws(() => ftruncateSync(123), kErrEBadf); + throws(() => ftruncateSync(123, 10), kErrEBadf); + throws(() => ftruncateSync(fd, "hello"), kErrInvalidArgType); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + // Truncate to 10 bytes + ftruncateSync(fd, 10); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 10n); + + // Truncate to 0 bytes + ftruncateSync(fd, 0); + const stat3 = fstatSync(fd, { bigint: true }); + strictEqual(stat3.size, 0n); + + // Truncate to a negative size throws an error + throws(() => ftruncateSync(fd, -1), kErrOutOfRange); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + ftruncate(fd, 5, (err) => { + if (err) return reject(err); + const stat4 = fstatSync(fd, { bigint: true }); + strictEqual(stat4.size, 5n); + resolve(); + }); + await promise; + } + + { + throws(() => ftruncate(fd, -1, () => {}), { + code: "ERR_OUT_OF_RANGE", + }); + } + + { + throws(() => ftruncateSync(fd, 0xffffffff), { + message: /File size limit exceeded/, + }); + throws(() => ftruncateSync(fd, 0x08000000 + 1), { + message: /File size limit exceeded/, + }); + // 0x08000000 is the maximum allowed file size. + ftruncateSync(fd, 0x08000000); + } + + closeSync(fd); + }, +}; + +export const truncateTest = { + async test() { + throws(() => truncateSync(123), kErrInvalidArgType); + throws(() => truncateSync("/", "hello"), kErrInvalidArgType); + + ok(!existsSync("/tmp/test.txt")); + throws(() => truncateSync("/tmp/test.txt", 10), { + code: "ENOENT", + path: "/tmp/test.txt", + }); + + // Create the file + writeFileSync("/tmp/test.txt", "Hello World"); + ok(existsSync("/tmp/test.txt")); + let stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 11); + + // Truncate to 10 bytes + truncateSync("/tmp/test.txt", 10); + stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 10); + + // Truncate to 20 bytes + truncateSync("/tmp/test.txt", 20); + stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 20); + + // Truncate to 0 bytes + truncateSync("/tmp/test.txt", 0); + stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 0); + + // Truncate to a negative size throws an error + throws(() => truncateSync("/tmp/test.txt", -1), kErrOutOfRange); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + truncate("/tmp/test.txt", 5, (err) => { + if (err) return reject(err); + const stat2 = statSync("/tmp/test.txt"); + strictEqual(stat2.size, 5); + resolve(); + }); + await promise; + } + + throws(() => truncate("/tmp/test.txt", -1), kErrOutOfRange); + }, +}; + +export const writeSyncTest = { + test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + throws(() => writeSync(""), kErrInvalidArgType); + throws(() => writeSync(fd, "Hello World", ""), kErrInvalidArgType); + throws( + () => writeSync(fd, "Hello World", { position: "hello" }), + kErrInvalidArgType, + ); + throws(() => writeSync(123, "Hello World"), kErrEBadf); + throws(() => writeSync(fd, Buffer.alloc(2), { offset: 5 }), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws(() => writeSync(fd, Buffer.alloc(2), { length: 5 }), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws( + () => writeSync(fd, Buffer.alloc(2), { offset: "hello" }), + kErrInvalidArgType, + ); + + writeSync(fd, "Hello World"); + writeSync(fd, "!!!!"); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const dest = Buffer.alloc(Number(stat2.size)); + + // When we don't specify a position, it reads from the current position, + // which currently is the end of the file... so we get nothing here. + strictEqual(readSync(fd, dest), 0); + + // But when we do specify a position, we can read from the beginning... + strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), dest.byteLength); + strictEqual(dest.toString(), "Hello World!!!!"); + + // Likewise, we can use an options object for the position + dest.fill(0); + strictEqual(dest.toString(), "\0".repeat(dest.byteLength)); + strictEqual(readSync(fd, dest, { position: 0 }), 15); + strictEqual(dest.toString(), "Hello World!!!!"); + + const dest2 = readFileSync("/tmp/test.txt"); + const dest3 = readFileSync(fd); + const dest4 = readFileSync(fd, { encoding: "utf8" }); + strictEqual(dest2.toString(), "Hello World!!!!"); + strictEqual(dest3.toString(), "Hello World!!!!"); + strictEqual(dest4, "Hello World!!!!"); + + closeSync(fd); + }, +}; + +export const writeAsyncCallbackTest = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + function mustNotCall() { + throw new Error("This function must not be called"); + } + + throws(() => write(fd, "Hello World"), kErrInvalidArgType); + throws(() => write(fd, "Hello World", "", mustNotCall), kErrInvalidArgType); + throws( + () => write(fd, "Hello World", { position: "hello" }, mustNotCall), + kErrInvalidArgType, + ); + throws(() => write(fd, Buffer.alloc(2), { offset: 5 }, mustNotCall), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws(() => write(fd, Buffer.alloc(2), { length: 5 }, mustNotCall), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws( + () => write(fd, Buffer.alloc(2), { offset: "hello" }, mustNotCall), + kErrInvalidArgType, + ); + + await new Promise((resolve, reject) => { + write(fd, "Hello World", (err, written) => { + if (err) return reject(err); + strictEqual(written, 11); + resolve(); + }); + }); + + await new Promise((resolve, reject) => { + write(fd, "!!!!", (err, written) => { + if (err) return reject(err); + strictEqual(written, 4); + resolve(); + }); + }); + + await rejects( + new Promise((resolve, reject) => { + write(123, "Hello World", { position: 0 }, (err) => { + if (err) return reject(err); + resolve(); + }); + }), + kErrEBadf, + ); + + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const dest = Buffer.alloc(Number(stat2.size)); + + // When we don't specify a position, it reads from the current position, + // which currently is the end of the file... so we get nothing here. + strictEqual(readSync(fd, dest), 0); + + // But when we do specify a position, we can read from the beginning... + strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), dest.byteLength); + strictEqual(dest.toString(), "Hello World!!!!"); + + closeSync(fd); + }, +}; + +export const writeSyncTest2 = { + test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + strictEqual(writeSync(fd, "Hello World", 2n), 11); + + // Writing to a position beyond max uint32_t is not allowed. + throws(() => writeSync(fd, "Hello World", 2n ** 32n), { + code: "EINVAL", + }); + throws(() => writeSync(fd, "Hello World", 2 ** 32), { + code: "ERR_OUT_OF_RANGE", + }); + + strictEqual(writeSync(fd, "aa", 0, "ascii"), 2); + + const stat2 = fstatSync(fd); + strictEqual(stat2.size, 13); + + const dest = Buffer.alloc(stat2.size); + strictEqual(readSync(fd, dest, 0, dest.byteLength, 0), 13); + strictEqual(dest.toString(), "aaHello World"); + + closeSync(fd); + }, +}; + +export const writeSyncTest3 = { + test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + writeSync(fd, Buffer.from("Hello World")); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 11n); + + closeSync(fd); + }, +}; + +export const writeSyncTest4 = { + test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + // Writing a partial buffer works + writeSync(fd, Buffer.from("Hello World"), 1, 3, 1); + + // Specifying an offset or length beyond the buffer size is not allowed. + throws(() => writeSync(fd, Buffer.from("Hello World"), 100, 3), { + message: /outside of buffer bounds/, + }); + // Specifying an offset or length beyond the buffer size is not allowed. + throws(() => writeSync(fd, Buffer.from("Hello World"), 0, 100), { + message: /outside of buffer bounds/, + }); + + throws(() => writeSync(fd, Buffer.from("hello world"), "a"), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => writeSync(fd, Buffer.from("hello world"), 1n), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => writeSync(fd, Buffer.from("hello world"), 0, "a"), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => writeSync(fd, Buffer.from("hello world"), 1, 1n), { + code: "ERR_INVALID_ARG_TYPE", + }); + + throws(() => writeSync(fd, 123), { + code: "ERR_INVALID_ARG_TYPE", + }); + + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 4n); + + closeSync(fd); + }, +}; + +export const writeReadOffsetBeyondLength = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + const heap = new Uint8Array(64); + for (let i = 0; i < heap.length; i++) heap[i] = i; + + // offset > length is valid as long as offset + length <= byteLength + strictEqual(writeSync(fd, heap, 10, 2), 2); + strictEqual(writeSync(fd, heap, 20, 3, 2), 3); + strictEqual(writeSync(fd, heap, { offset: 30, length: 1, position: 5 }), 1); + strictEqual(writeSync(fd, heap, { offset: 62, position: 6 }), 2); + strictEqual(writeSync(fd, heap, 63, undefined, 8), 1); + strictEqual(writeSync(fd, heap, 64, 0, 9), 0); + strictEqual(fstatSync(fd).size, 9); + + // A view into a larger backing buffer: offset is relative to the view. + const view = new Uint8Array(heap.buffer, 8, 16); + strictEqual(writeSync(fd, view, 12, 4, 9), 4); + strictEqual(fstatSync(fd).size, 13); + + throws(() => writeSync(fd, heap, 65, 0), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws(() => writeSync(fd, heap, 60, 5), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws(() => writeSync(fd, view, 12, 5), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + throws(() => writeSync(fd, view, 17), { + code: "ERR_BUFFER_OUT_OF_BOUNDS", + }); + + const dest = new Uint8Array(64); + strictEqual(readSync(fd, dest, 40, 13, 0), 13); + deepStrictEqual( + [...dest.subarray(40, 53)], + [10, 11, 20, 21, 22, 30, 62, 63, 63, 20, 21, 22, 23], + ); + strictEqual(readSync(fd, dest, 50, 2, 0), 2); + strictEqual(readSync(fd, dest, { offset: 60, position: 0 }), 4); + deepStrictEqual([...dest.subarray(60)], [10, 11, 20, 21]); + strictEqual(readSync(fd, dest, 64, 0, 0), 0); + + const destView = new Uint8Array(dest.buffer, 8, 16); + destView.fill(0); + strictEqual(readSync(fd, destView, 12, 4, 0), 4); + deepStrictEqual([...destView.subarray(12)], [10, 11, 20, 21]); + deepStrictEqual([...dest.subarray(24, 26)], [0, 0]); + + // Zero-length reads are a no-op regardless of offset, as in Node. + strictEqual(readSync(fd, dest, 65, 0, 0), 0); + strictEqual(readSync(fd, destView, 17, 0, 0), 0); + throws(() => readSync(fd, dest, 60, 5, 0), kErrOutOfRange); + throws(() => readSync(fd, destView, 12, 5, 0), kErrOutOfRange); + throws(() => readSync(fd, dest, 65, 1, 0), kErrOutOfRange); + + // Callback and promise variants share the same validation. + await new Promise((resolve, reject) => { + write(fd, heap, 10, 2, 13, (err, written) => { + if (err) return reject(err); + strictEqual(written, 2); + resolve(); + }); + }); + await new Promise((resolve, reject) => { + read(fd, dest, 40, 2, 13, (err, bytesRead) => { + if (err) return reject(err); + strictEqual(bytesRead, 2); + deepStrictEqual([...dest.subarray(40, 42)], [10, 11]); + resolve(); + }); + }); + await new Promise((resolve, reject) => { + read(fd, dest, 65, 0, 0, (err, bytesRead) => { + if (err) return reject(err); + strictEqual(bytesRead, 0); + resolve(); + }); + }); + closeSync(fd); + + const handle = await promises.open("/tmp/test.txt", "r+"); + strictEqual((await handle.write(heap, 10, 2, 15)).bytesWritten, 2); + strictEqual((await handle.read(dest, 40, 2, 15)).bytesRead, 2); + strictEqual((await handle.read(dest, 62)).bytesRead, 2); + strictEqual((await handle.read(destView, 12, 4, 15)).bytesRead, 2); + strictEqual((await handle.read(dest, 65, 0, 0)).bytesRead, 0); + await handle.close(); + }, +}; + +export const writeSyncAppend = { + test() { + const fd = openSync("/tmp/test.txt", "a"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + // In append mode, the position is ignored. + + writeSync(fd, "Hello World", 1000); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 11n); + + writeSync(fd, "!!!!", 2000); + const stat3 = fstatSync(fd, { bigint: true }); + strictEqual(stat3.size, 15n); + + closeSync(fd); + }, +}; + +export const writevSyncTest = { + test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + writevSync(fd, [Buffer.from("Hello World"), Buffer.from("!!!!")]); + + throws(() => writevSync(fd, [1, 2]), { + code: /ERR_INVALID_ARG_TYPE/, + }); + + throws(() => writevSync(100, [Buffer.from("")]), { + message: "bad file descriptor", + code: "EBADF", + }); + + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const dest1 = Buffer.alloc(5); + const dest2 = Buffer.alloc(10); + const dest3 = Buffer.alloc(5); + let read = readvSync(fd, [dest1, dest2, dest3], 0); + strictEqual(read, 15); + let dest = Buffer.concat([dest1, dest2, dest3]); + strictEqual(dest.toString("utf8", 0, read), "Hello World!!!!"); + + dest1.fill(0); + dest2.fill(0); + dest3.fill(0); + read = readvSync(fd, [dest1, dest2, dest3], 1); + strictEqual(read, 14); + dest = Buffer.concat([dest1, dest2, dest3]); + strictEqual(dest.toString("utf8", 0, read), "ello World!!!!"); + + // Reading from a position beyond the end of the file returns nothing. + strictEqual(readvSync(fd, [dest1], 100), 0); + + closeSync(fd); + }, +}; + +export const writevAsyncCallbackTest = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + throws(() => writev("hello"), kErrInvalidArgType); + throws(() => writev(0, 123), kErrInvalidArgType); + throws(() => writev(fd, [Buffer.from("")], 123), kErrInvalidArgType); + + await new Promise((resolve, reject) => { + writev( + fd, + [Buffer.from("Hello World"), Buffer.from("!!!!")], + (err, written) => { + if (err) return reject(err); + strictEqual(written, 15); + resolve(); + }, + ); + }); + + throws( + () => + writev(fd, [1, 2], (err) => { + if (err) throw err; + }), + { + code: /ERR_INVALID_ARG_TYPE/, + }, + ); + + await rejects( + new Promise((resolve, reject) => { + writev(100, [Buffer.from("")], (err) => { + if (err) return reject(err); + resolve(); + }); + }), + { + code: "EBADF", + }, + ); + + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const dest1 = Buffer.alloc(5); + const dest2 = Buffer.alloc(10); + const dest3 = Buffer.alloc(5); + let read = readvSync(fd, [dest1, dest2, dest3], 0); + strictEqual(read, 15); + let dest = Buffer.concat([dest1, dest2, dest3]); + strictEqual(dest.toString("utf8", 0, read), "Hello World!!!!"); + + dest1.fill(0); + dest2.fill(0); + dest3.fill(0); + read = readvSync(fd, [dest1, dest2, dest3], 1); + strictEqual(read, 14); + dest = Buffer.concat([dest1, dest2, dest3]); + strictEqual(dest.toString("utf8", 0, read), "ello World!!!!"); + + // Reading from a position beyond the end of the file returns nothing. + strictEqual(readvSync(fd, [dest1], 100), 0); + + closeSync(fd); + }, +}; + +export const writeFileSyncTest = { + test() { + ok(!existsSync("/tmp/test.txt")); + strictEqual(writeFileSync("/tmp/test.txt", "Hello World"), 11); + ok(existsSync("/tmp/test.txt")); + let stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 11); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World"); + + strictEqual(appendFileSync("/tmp/test.txt", "!!!!"), 4); + stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 15); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World!!!!"); + + // We can also use a file descriptor + const fd = openSync("/tmp/test.txt", "a+"); + writeFileSync(fd, "##"); + strictEqual(readFileSync(fd).toString(), "Hello World!!!!##"); + closeSync(fd); + }, +}; + +export const appendFileSyncFlush = { + test() { + ok(!existsSync("/tmp/test.txt")); + + // The flush option really is not supported in any particular way in + // our implementation but let's verify it. + appendFileSync("/tmp/test.txt", "hello world", { flush: true }); + + ["no", {}, null, -1].forEach((i) => { + throws( + () => appendFileSync("/tmp/test.txt", "hello world", { flush: "no" }), + { + code: "ERR_INVALID_ARG_TYPE", + }, + ); + }); + + ok(existsSync("/tmp/test.txt")); + strictEqual(readFileSync("/tmp/test.txt").toString(), "hello world"); + }, +}; + +export const writeFileAsyncCallbackTest = { + async test() { + ok(!existsSync("/tmp/test.txt")); + + throws(() => writeFile({}), kErrInvalidArgType); + throws(() => writeFile("", 123), kErrInvalidArgType); + throws(() => writeFile("", "", 123), kErrInvalidArgType); + throws(() => writeFile("", "", {}), kErrInvalidArgType); + + await new Promise((resolve, reject) => { + writeFile("/tmp/test.txt", "Hello World", (err) => { + if (err) return reject(err); + ok(existsSync("/tmp/test.txt")); + resolve(); + }); + }); + + let stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 11); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World"); + + await new Promise((resolve, reject) => { + appendFile("/tmp/test.txt", "!!!!", (err) => { + strictEqual( + readFileSync("/tmp/test.txt").toString(), + "Hello World!!!!", + ); + if (err) return reject(err); + resolve(); + }); + }); + + stat = statSync("/tmp/test.txt"); + strictEqual(stat.size, 15); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World!!!!"); + + // We can also use a file descriptor + const fd = openSync("/tmp/test.txt", "a+"); + await new Promise((resolve, reject) => { + writeFile(fd, "##", "utf8", (err) => { + if (err) return reject(err); + resolve(); + }); + }); + strictEqual(readFileSync(fd).toString(), "Hello World!!!!##"); + closeSync(fd); + + // We can use the promise API as well. + await promises.appendFile("/tmp/test.txt", "!!!"); + strictEqual( + readFileSync("/tmp/test.txt").toString(), + "Hello World!!!!##!!!", + ); + }, +}; + +export const appendFileCases = { + async test() { + ok(!existsSync("/tmp/test.txt")); + // It accepts bufers + appendFileSync("/tmp/test.txt", Buffer.from("Hello World")); + ok(existsSync("/tmp/test.txt")); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World"); + + // With the callback API also + const { promise, resolve, reject } = Promise.withResolvers(); + appendFile("/tmp/test.txt", Buffer.from("!!!!"), (err) => { + if (err) return reject(err); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World!!!!"); + resolve(); + }); + await promise; + + // And the promises API + await promises.appendFile("/tmp/test.txt", Buffer.from("!!!")); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World!!!!!!!"); + + // But invalid types throw errors + [123, {}, null, []].forEach((data) => { + throws(() => appendFileSync("/tmp/test.txt", data), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + }, +}; + +export const readAsyncCallbackTest = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + throws(() => read("hello"), kErrInvalidArgType); + throws(() => read(0, 123), kErrInvalidArgType); + throws(() => read(0, Buffer.alloc(1), 123), kErrInvalidArgType); + throws(() => read(0, Buffer.alloc(1), {}), kErrInvalidArgType); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + writeSync(fd, "Hello World"); + writeSync(fd, "!!!!"); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const dest = Buffer.alloc(15); + let bytesRead = await new Promise((resolve, reject) => { + read(fd, dest, 0, dest.byteLength, 0, (err, bytesRead) => { + if (err) return reject(err); + resolve(bytesRead); + }); + }); + + strictEqual(bytesRead, 15); + strictEqual(dest.toString(), "Hello World!!!!"); + + closeSync(fd); + }, +}; + +export const readvAsyncCallbackTest = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + throws(() => readv("hello"), kErrInvalidArgType); + throws(() => readv(0, 123), kErrInvalidArgType); + throws(() => readv(0, [Buffer.alloc(1)], 123), kErrInvalidArgType); + throws(() => readv(0, [Buffer.alloc(1)], {}), kErrInvalidArgType); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + writeSync(fd, "Hello World"); + writeSync(fd, "!!!!"); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const dest1 = Buffer.alloc(5); + const dest2 = Buffer.alloc(10); + let bytesRead = await new Promise((resolve, reject) => { + readv(fd, [dest1, dest2], 0, (err, bytesRead) => { + if (err) return reject(err); + resolve(bytesRead); + }); + }); + + strictEqual(bytesRead, 15); + strictEqual(Buffer.concat([dest1, dest2]).toString(), "Hello World!!!!"); + + closeSync(fd); + }, +}; + +export const readFileAsyncCallbackTest = { + async test() { + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + throws(() => readFile("hello"), kErrInvalidArgType); + throws(() => readFile(0, 123), kErrInvalidArgType); + throws(() => readFile(0, {}), kErrInvalidArgType); + + const stat = fstatSync(fd, { bigint: true }); + strictEqual(stat.size, 0n); + + writeSync(fd, "Hello World"); + writeSync(fd, "!!!!"); + const stat2 = fstatSync(fd, { bigint: true }); + strictEqual(stat2.size, 15n); + + const content = await new Promise((resolve, reject) => { + readFile(fd, (err, content) => { + if (err) return reject(err); + resolve(content); + }); + }); + + strictEqual(content.toString(), "Hello World!!!!"); + + closeSync(fd); + }, +}; + +export const copyAndRenameSyncTest = { + test() { + ok(!existsSync("/tmp/test.txt")); + ok(!existsSync("/tmp/test2.txt")); + writeFileSync("/tmp/test.txt", "Hello World"); + ok(existsSync("/tmp/test.txt")); + ok(!existsSync("/tmp/test2.txt")); + + copyFileSync("/tmp/test.txt", "/tmp/test2.txt"); + // Both files exist + ok(existsSync("/tmp/test.txt")); + ok(existsSync("/tmp/test2.txt")); + + strictEqual( + readFileSync("/tmp/test.txt").toString(), + readFileSync("/tmp/test2.txt").toString(), + ); + + copyFileSync("/tmp/test.txt", "/tmp/test4.txt", 0); + // Both files exist + ok(existsSync("/tmp/test.txt")); + ok(existsSync("/tmp/test4.txt")); + + strictEqual( + readFileSync("/tmp/test.txt").toString(), + readFileSync("/tmp/test4.txt").toString(), + ); + + throws( + () => copyFileSync("/tmp/test.txt", "/tmp/test4.txt", COPYFILE_EXCL), + { + code: "EEXIST", + }, + ); + + throws( + () => + copyFileSync( + "/tmp/test.txt", + "/tmp/nope.txt", + constants.COPYFILE_FICLONE_FORCE, + ), + { + message: /unsupported/, + }, + ); + + copyFileSync("/tmp/test.txt", "/tmp/test5.txt", constants.COPYFILE_FICLONE); + // Both files exist + ok(existsSync("/tmp/test.txt")); + ok(existsSync("/tmp/test5.txt")); + + strictEqual( + readFileSync("/tmp/test.txt").toString(), + readFileSync("/tmp/test5.txt").toString(), + ); + + [false, 1, {}, [], null, undefined].forEach((i) => { + throws(() => copyFileSync(i, "/tmp/nope.txt"), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => copyFileSync("/tmp/test.txt", i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + [false, {}, [], null].forEach((i) => { + throws(() => copyFileSync("/tmp/test.txt", "/tmp/test2.txt", i), { + code: "ERR_INVALID_ARG_VALUE", + }); + }); + + // We can modify one of the files and the other remains unchanged + writeFileSync("/tmp/test.txt", "Hello World 2"); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World 2"); + strictEqual(readFileSync("/tmp/test2.txt").toString(), "Hello World"); + + // Renaming the files work + renameSync("/tmp/test.txt", "/tmp/test3.txt"); + ok(!existsSync("/tmp/test.txt")); + ok(existsSync("/tmp/test3.txt")); + strictEqual(readFileSync("/tmp/test3.txt").toString(), "Hello World 2"); + }, +}; + +export const renameReplaceTest = { + test() { + // Renaming onto an existing file replaces it. + writeFileSync("/tmp/src.txt", "new"); + writeFileSync("/tmp/dest.txt", "old"); + renameSync("/tmp/src.txt", "/tmp/dest.txt"); + ok(!existsSync("/tmp/src.txt")); + strictEqual(readFileSync("/tmp/dest.txt", "utf8"), "new"); + + // copyFile without COPYFILE_EXCL overwrites too. + writeFileSync("/tmp/src.txt", "newer"); + copyFileSync("/tmp/src.txt", "/tmp/dest.txt"); + strictEqual(readFileSync("/tmp/dest.txt", "utf8"), "newer"); + strictEqual(readFileSync("/tmp/src.txt", "utf8"), "newer"); + + // Renaming onto itself is a no-op, including via URL aliases that differ + // only in search or fragment. + renameSync("/tmp/dest.txt", "/tmp/dest.txt"); + strictEqual(readFileSync("/tmp/dest.txt", "utf8"), "newer"); + renameSync( + new URL("file:///tmp/dest.txt#one"), + new URL("file:///tmp/dest.txt?two"), + ); + strictEqual(readFileSync("/tmp/dest.txt", "utf8"), "newer"); + + // Renaming onto an existing symlink replaces the link, not its target. + symlinkSync("/tmp/dest.txt", "/tmp/link"); + renameSync("/tmp/src.txt", "/tmp/link"); + ok(lstatSync("/tmp/link").isFile()); + strictEqual(readFileSync("/tmp/dest.txt", "utf8"), "newer"); + unlinkSync("/tmp/link"); + + // A file cannot replace a directory, and a directory cannot replace a file. + mkdirSync("/tmp/dir"); + throws(() => renameSync("/tmp/dest.txt", "/tmp/dir"), { code: "EISDIR" }); + throws(() => renameSync("/tmp/dir", "/tmp/dest.txt"), { code: "ENOTDIR" }); + throws(() => copyFileSync("/tmp/dest.txt", "/tmp/dir"), { code: "EISDIR" }); + ok(existsSync("/tmp/dest.txt")); + ok(statSync("/tmp/dir").isDirectory()); + + // A directory replaces an empty directory but not a populated one. + mkdirSync("/tmp/dir2"); + writeFileSync("/tmp/dir/a.txt", "a"); + renameSync("/tmp/dir", "/tmp/dir2"); + ok(!existsSync("/tmp/dir")); + strictEqual(readFileSync("/tmp/dir2/a.txt", "utf8"), "a"); + mkdirSync("/tmp/dir"); + throws(() => renameSync("/tmp/dir", "/tmp/dir2"), { code: "ENOTEMPTY" }); + ok(existsSync("/tmp/dir")); + + // A file over a populated directory is EISDIR, not ENOTEMPTY, and a + // missing source is ENOENT regardless of the destination. + throws(() => renameSync("/tmp/dest.txt", "/tmp/dir2"), { code: "EISDIR" }); + throws(() => renameSync("/tmp/missing", "/tmp/dir2"), { code: "ENOENT" }); + throws(() => copyFileSync("/tmp/missing", "/tmp/dir2"), { code: "ENOENT" }); + + // A directory cannot be moved into itself or a descendant. + mkdirSync("/tmp/dir/sub"); + throws(() => renameSync("/tmp/dir", "/tmp/dir/sub"), { code: "EINVAL" }); + throws(() => renameSync("/tmp/dir", "/tmp/dir/sub/x"), { code: "EINVAL" }); + throws(() => renameSync("/tmp/dir/", "/tmp/dir/x"), { code: "EINVAL" }); + ok(statSync("/tmp/dir/sub").isDirectory()); + strictEqual(readdirSync("/tmp/dir").length, 1); + + rmSync("/tmp/dir", { recursive: true }); + rmSync("/tmp/dir2", { recursive: true }); + unlinkSync("/tmp/dest.txt"); + }, +}; + +export const copyAndRenameAsyncCallbackTest = { + async test() { + ok(!existsSync("/tmp/test.txt")); + ok(!existsSync("/tmp/test2.txt")); + writeFileSync("/tmp/test.txt", "Hello World"); + ok(existsSync("/tmp/test.txt")); + ok(!existsSync("/tmp/test2.txt")); + + throws(() => copyFile(123), kErrInvalidArgType); + throws(() => copyFile("/tmp/test.txt", 123), kErrInvalidArgType); + throws( + () => copyFile("/tmp/test.txt", "/tmp/test2.txt", 123), + kErrInvalidArgValue, + ); + throws( + () => copyFile("/tmp/test.txt", "/tmp/test2.txt", 0), + kErrInvalidArgType, + ); + + throws(() => rename(123), kErrInvalidArgType); + throws(() => rename("/tmp/test.txt", 123), kErrInvalidArgType); + throws( + () => rename("/tmp/test.txt", "/tmp/test2.txt", 0), + kErrInvalidArgType, + ); + + await new Promise((resolve, reject) => { + copyFile("/tmp/test.txt", "/tmp/test2.txt", (err) => { + if (err) return reject(err); + resolve(); + }); + }); + + // Test the exclusive option fails when the file already exists + await rejects( + new Promise((resolve, reject) => { + copyFile("/tmp/test.txt", "/tmp/test2.txt", COPYFILE_EXCL, (err) => { + if (err) return reject(err); + resolve(); + }); + }), + kErrEExist, + ); + + // Both files exist + ok(existsSync("/tmp/test.txt")); + ok(existsSync("/tmp/test2.txt")); + + strictEqual( + readFileSync("/tmp/test.txt").toString(), + readFileSync("/tmp/test2.txt").toString(), + ); + + // We can modify one of the files and the other remains unchanged + writeFileSync("/tmp/test.txt", "Hello World 2"); + strictEqual(readFileSync("/tmp/test.txt").toString(), "Hello World 2"); + strictEqual(readFileSync("/tmp/test2.txt").toString(), "Hello World"); + + // Renaming the files work + await new Promise((resolve, reject) => { + rename("/tmp/test.txt", "/tmp/test3.txt", (err) => { + if (err) return reject(err); + resolve(); + }); + }); + + ok(!existsSync("/tmp/test.txt")); + ok(existsSync("/tmp/test3.txt")); + strictEqual(readFileSync("/tmp/test3.txt").toString(), "Hello World 2"); + }, +}; + +export const fsCwdTest = { + test() { + process.chdir("/bundle"); + + throws( + () => { + writeFileSync("test-cwd.txt", "Hello from original cwd"); + }, + { code: "EPERM" }, + ); + + process.chdir("/tmp"); + + writeFileSync("test-cwd.txt", "Hello from /tmp"); + ok(existsSync("test-cwd.txt")); + ok(existsSync("/tmp/test-cwd.txt")); + + ok(existsSync("test-cwd.txt")); + ok(!existsSync(`/bundle/test-cwd.txt`)); + + strictEqual(readFileSync("test-cwd.txt").toString(), "Hello from /tmp"); + strictEqual( + readFileSync("/tmp/test-cwd.txt").toString(), + "Hello from /tmp", + ); + + process.chdir("/bundle"); + + ok(!existsSync("test-cwd.txt")); + throws( + () => { + readFileSync("test-cwd.txt"); + }, + { code: "ENOENT", path: "/bundle/test-cwd.txt" }, + ); + + unlinkSync("/tmp/test-cwd.txt"); + }, +}; + +export const readBadEncoding = { + test() { + const kErrorObj = { + code: "ERR_INVALID_ARG_VALUE", + }; + throws(() => readFileSync("/tmp/test.txt", "bad-encoding"), kErrorObj); + throws( + () => appendFileSync("/tmp/test.txt", "data", "bad-encoding"), + kErrorObj, + ); + throws(() => readdirSync("/tmp/test.txt", "bad-encoding"), kErrorObj); + throws(() => readlinkSync("/tmp/test.txt", "bad-encoding"), kErrorObj); + throws( + () => writeFileSync("/tmp/test.txt", "data", "bad-encoding"), + kErrorObj, + ); + throws( + () => appendFileSync("/tmp/test.txt", "data", "bad-encoding"), + kErrorObj, + ); + throws(() => realpathSync("/tmp/test.txt", "bad-encoding"), kErrorObj); + throws(() => mkdtempSync("/tmp/test.txt", "bad-encoding"), kErrorObj); + throws(() => ReadStream("/tmp/test.txt", "bad-encoding"), kErrorObj); + throws(() => WriteStream("/tmp/test.txt", "bad-encoding"), kErrorObj); + + throws( + () => writeFile("/tmp/test.txt", "data", "bad-encoding", mustNotCall), + kErrorObj, + ); + throws( + () => appendFile("/tmp/test.txt", "data", "bad-encoding", mustNotCall), + kErrorObj, + ); + + function mustNotCall() { + throw new Error("This function must not be called"); + } + + throws( + () => readFile("/tmp/test.txt", "bad-encoding", mustNotCall), + kErrorObj, + ); + throws( + () => readdir("/tmp/test.txt", "bad-encoding", mustNotCall), + kErrorObj, + ); + throws( + () => readlink("/tmp/test.txt", "bad-encoding", mustNotCall), + kErrorObj, + ); + throws( + () => realpath("/tmp/test.txt", "bad-encoding", mustNotCall), + kErrorObj, + ); + throws( + () => mkdtemp("/tmp/test.txt", "bad-encoding", mustNotCall), + kErrorObj, + ); + }, +}; + +export const fsConstantsTest = { + test() { + // Check if the two constants accepted by chmod() on Windows are defined. + notStrictEqual(constants.S_IRUSR, undefined); + notStrictEqual(constants.S_IWUSR, undefined); + + // Check null prototype. + strictEqual(Object.getPrototypeOf(constants), null); + + const knownFsConstantNames = [ + "UV_FS_SYMLINK_DIR", + "UV_FS_SYMLINK_JUNCTION", + "O_RDONLY", + "O_WRONLY", + "O_RDWR", + "UV_DIRENT_UNKNOWN", + "UV_DIRENT_FILE", + "UV_DIRENT_DIR", + "UV_DIRENT_LINK", + "UV_DIRENT_FIFO", + "UV_DIRENT_SOCKET", + "UV_DIRENT_CHAR", + "UV_DIRENT_BLOCK", + "S_IFMT", + "S_IFREG", + "S_IFDIR", + "S_IFCHR", + "S_IFBLK", + "S_IFIFO", + "S_IFLNK", + "S_IFSOCK", + "O_CREAT", + "O_EXCL", + "UV_FS_O_FILEMAP", + "O_NOCTTY", + "O_TRUNC", + "O_APPEND", + "O_DIRECTORY", + "O_EXCL", + "O_NOATIME", + "O_NOFOLLOW", + "O_SYNC", + "O_DSYNC", + "O_SYMLINK", + "O_DIRECT", + "O_NONBLOCK", + "S_IRWXU", + "S_IRUSR", + "S_IWUSR", + "S_IXUSR", + "S_IRWXG", + "S_IRGRP", + "S_IWGRP", + "S_IXGRP", + "S_IRWXO", + "S_IROTH", + "S_IWOTH", + "S_IXOTH", + "F_OK", + "R_OK", + "W_OK", + "X_OK", + "UV_FS_COPYFILE_EXCL", + "COPYFILE_EXCL", + "UV_FS_COPYFILE_FICLONE", + "COPYFILE_FICLONE", + "UV_FS_COPYFILE_FICLONE_FORCE", + "COPYFILE_FICLONE_FORCE", + "EXTENSIONLESS_FORMAT_JAVASCRIPT", + "EXTENSIONLESS_FORMAT_WASM", + ]; + + const fsConstantNames = Object.keys(constants); + const unknownFsConstantNames = fsConstantNames.filter((constant) => { + return !knownFsConstantNames.includes(constant); + }); + deepStrictEqual( + unknownFsConstantNames, + [], + `Unknown fs.constants: ${unknownFsConstantNames.join(", ")}`, + ); + + strictEqual(typeof constants.COPYFILE_EXCL, "number"); + strictEqual(typeof constants.COPYFILE_FICLONE, "number"); + strictEqual(typeof constants.COPYFILE_FICLONE_FORCE, "number"); + strictEqual(typeof constants.UV_FS_COPYFILE_EXCL, "number"); + strictEqual(typeof constants.UV_FS_COPYFILE_FICLONE, "number"); + strictEqual(typeof constants.UV_FS_COPYFILE_FICLONE_FORCE, "number"); + strictEqual(constants.COPYFILE_EXCL, constants.UV_FS_COPYFILE_EXCL); + strictEqual(constants.COPYFILE_FICLONE, constants.UV_FS_COPYFILE_FICLONE); + strictEqual( + constants.COPYFILE_FICLONE_FORCE, + constants.UV_FS_COPYFILE_FICLONE_FORCE, + ); + }, +}; + +export const fmapIgnoredTest = { + test() { + const filename = "/tmp/foo"; + const text = "Memory File Mapping Test"; + + const mw = + constants.UV_FS_O_FILEMAP | + constants.O_TRUNC | + constants.O_CREAT | + constants.O_WRONLY; + const mr = constants.UV_FS_O_FILEMAP | constants.O_RDONLY; + + writeFileSync(filename, text, { flag: mw }); + const r1 = readFileSync(filename, { encoding: "utf8", flag: mr }); + strictEqual(r1, text); + }, +}; + +export const fileNamesWithNullBytesTest = { + test() { + const filename = "/tmp/test\0.txt"; + throws(() => writeFileSync(filename, "Hello World"), { + code: "ERR_INVALID_ARG_VALUE", + }); + }, +}; + +export const fileNamesWithSurrogatePairsTest = { + test() { + const tempdir = mkdtempSync("/tmp/emoji-fruit-🍇 🍈 🍉 🍊 🍋"); + ok(existsSync(tempdir)); + const filename = "🚀🔥🛸.txt"; + const content = "Test content"; + writeFileSync(join(tempdir, filename), content); + const readContent = readFileSync(join(tempdir, filename), "utf8"); + strictEqual(readContent, content); + }, +}; + +export const testFsWithZlib = { + async test() { + const imgBase64 = + "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"; + + const imageStream = Readable.from(Buffer.from(imgBase64, "base64")); + await new Promise((resolve, reject) => { + const outStream = createWriteStream(`/tmp/image-stream.png.gz`); + outStream.on("close", resolve).on("error", reject); + imageStream.pipe(zlib.createGzip()).pipe(outStream); + }); + + doesNotThrow(() => { + zlib + .gunzipSync(readFileSync("/tmp/image-stream.png.gz")) + .toString("base64"); + }); + }, +}; diff --git a/src/compat/fs/upstream-readstream.fixture.mjs b/src/compat/fs/upstream-readstream.fixture.mjs new file mode 100644 index 00000000..669ab694 --- /dev/null +++ b/src/compat/fs/upstream-readstream.fixture.mjs @@ -0,0 +1,1057 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { + ReadStream, + createReadStream, + statSync, + writeFileSync, + openSync, + open as openAsync, + close as closeAsync, + read as readAsync, + promises, +} from "./index.js"; + +import { ok, strictEqual, throws } from "node:assert"; +import { mock } from "node:test"; +import * as process from "../stream/process.js"; + +strictEqual(typeof ReadStream, "function"); +strictEqual(typeof createReadStream, "function"); + +export const simpleReadStreamTest = { + async test() { + const largeData = "abc".repeat(100_000); + writeFileSync("/tmp/foo", largeData); + + const stream = createReadStream("/tmp/foo", { + encoding: "utf8", + highWaterMark: 10_000, + }); + + let data = ""; + for await (const chunk of stream) { + data += chunk; + } + strictEqual(data, largeData); + }, +}; + +function prepareFile() { + const path = "/tmp/elipses.txt"; + writeFileSync(path, "…".repeat(10000)); + return path; +} + +async function runTest(options) { + let paused = false; + let bytesRead = 0; + + const path = prepareFile(); + + const file = createReadStream(path, options); + const fileSize = statSync(path).size; + + strictEqual(file.bytesRead, 0); + + const promises = []; + + const { promise: openPromise, resolve: openResolve } = + Promise.withResolvers(); + const { promise: endPromise, resolve: endResolve } = Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + promises.push(openPromise); + promises.push(endPromise); + promises.push(closePromise); + + const onOpen = mock.fn((fd) => { + file.length = 0; + strictEqual(typeof fd, "number"); + strictEqual(file.bytesRead, 0); + ok(file.readable); + file.pause(); + file.resume(); + file.pause(); + file.resume(); + openResolve(); + }); + + const onData = mock.fn((data) => { + ok(data instanceof Buffer); + ok(data.byteOffset % 8 === 0); + ok(!paused); + file.length += data.length; + + bytesRead += data.length; + strictEqual(file.bytesRead, bytesRead); + + paused = true; + file.pause(); + + setTimeout(function () { + paused = false; + file.resume(); + }, 10); + }); + + const onEnd = mock.fn(() => { + strictEqual(bytesRead, fileSize); + strictEqual(file.bytesRead, fileSize); + endResolve(); + }); + + const onClose = mock.fn(() => { + strictEqual(bytesRead, fileSize); + strictEqual(file.bytesRead, fileSize); + closeResolve(); + }); + + file.once("open", onOpen); + file.once("end", onEnd); + file.once("close", onClose); + file.on("data", onData); + + await Promise.all(promises); + + strictEqual(file.length, 30000); + strictEqual(onOpen.mock.callCount(), 1); + strictEqual(onEnd.mock.callCount(), 1); + strictEqual(onClose.mock.callCount(), 1); + strictEqual(onData.mock.callCount(), 1); +} + +export const readStreamTest1 = { + async test() { + await runTest({}); + }, +}; + +export const readStreamTest2 = { + async test() { + const customFs = { + open: mock.fn((...args) => openAsync(...args)), + read: mock.fn((...args) => readAsync(...args)), + close: mock.fn((...args) => closeAsync(...args)), + }; + await runTest({ + fs: customFs, + }); + strictEqual(customFs.open.mock.callCount(), 1); + strictEqual(customFs.read.mock.callCount(), 2); + strictEqual(customFs.close.mock.callCount(), 1); + }, +}; + +export const readStreamTest3 = { + async test() { + const path = prepareFile(); + const file = createReadStream(path, { encoding: "utf8" }); + file.length = 0; + file.on("data", function (data) { + strictEqual(typeof data, "string"); + file.length += data.length; + + for (let i = 0; i < data.length; i++) { + // http://www.fileformat.info/info/unicode/char/2026/index.htm + strictEqual(data[i], "\u2026"); + } + }); + + const { promise, resolve } = Promise.withResolvers(); + file.on("close", resolve); + + await promise; + + strictEqual(file.length, 10000); + }, +}; + +export const readStreamTest4 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz"); + const file = createReadStream(path, { bufferSize: 1, start: 1, end: 2 }); + let contentRead = ""; + file.on("data", function (data) { + contentRead += data.toString("utf-8"); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on("end", function (data) { + strictEqual(contentRead, "yz"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest5 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const file = createReadStream(path, { bufferSize: 1, start: 1 }); + file.data = ""; + file.on("data", function (data) { + file.data += data.toString("utf-8"); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on("end", function () { + strictEqual(file.data, "yz\n"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest6 = { + async test() { + // Ref: https://github.com/nodejs/node-v0.x-archive/issues/2320 + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const file = createReadStream(path, { bufferSize: 1.23, start: 1 }); + file.data = ""; + file.on("data", function (data) { + file.data += data.toString("utf-8"); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on("end", function () { + strictEqual(file.data, "yz\n"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest7 = { + test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + throws(() => createReadStream(path, { start: 10, end: 2 }), { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }); + }, +}; + +export const readStreamTest8 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const stream = createReadStream(path, { start: 0, end: 0 }); + stream.data = ""; + + stream.on("data", function (chunk) { + stream.data += chunk; + }); + const { promise, resolve } = Promise.withResolvers(); + stream.on("end", function () { + strictEqual(stream.data, "x"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest9 = { + async test() { + // Verify that end works when start is not specified. + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const stream = new createReadStream(path, { end: 1 }); + stream.data = ""; + + stream.on("data", function (chunk) { + stream.data += chunk; + }); + + const { promise, resolve } = Promise.withResolvers(); + stream.on("end", function () { + strictEqual(stream.data, "xy"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest10 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + let file = createReadStream(path, { autoClose: false }); + let data = ""; + file.on("data", function (chunk) { + data += chunk; + }); + const { promise, resolve, reject } = Promise.withResolvers(); + file.on("end", function () { + strictEqual(data, "xyz\n"); + process.nextTick(function () { + ok(!file.closed); + ok(!file.destroyed); + fileNext().then(resolve, reject); + }); + }); + + await promise; + + async function fileNext() { + // This will tell us if the fd is usable again or not. + file = createReadStream(null, { fd: file.fd, start: 0 }); + file.data = ""; + file.on("data", function (data) { + file.data += data; + }); + const { promise, resolve } = Promise.withResolvers(); + const { promise: endPromise, resolve: endResolve } = + Promise.withResolvers(); + file.on("end", function (err) { + strictEqual(file.data, "xyz\n"); + endResolve(); + }); + file.on("close", resolve); + await Promise.all([promise, endPromise]); + ok(file.closed); + ok(file.destroyed); + } + }, +}; + +export const readStreamTest11 = { + async test() { + // Just to make sure autoClose won't close the stream because of error. + const { promise, resolve, reject } = Promise.withResolvers(); + const file = createReadStream(null, { fd: 13337, autoClose: false }); + file.on("data", () => reject(new Error("should not be called"))); + file.on("error", resolve); + await promise; + ok(!file.closed); + ok(!file.destroyed); + ok(file.fd); + }, +}; + +export const readStreamTest12 = { + async test() { + // Make sure stream is destroyed when file does not exist. + const file = createReadStream("/path/to/file/that/does/not/exist"); + const { promise, resolve, reject } = Promise.withResolvers(); + file.on("data", () => reject(new Error("should not be called"))); + file.on("error", resolve); + await promise; + ok(file.closed); + ok(file.destroyed); + }, +}; + +export const readStreamTest13 = { + async test() { + const example = "/tmp/x.txt"; + writeFileSync(example, "xyz\n"); + createReadStream(example, undefined); + createReadStream(example, null); + createReadStream(example, "utf8"); + createReadStream(example, { encoding: "utf8" }); + + const createReadStreamErr = (path, opt, error) => { + throws(() => createReadStream(path, opt), error); + }; + + const typeError = { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }; + + const rangeError = { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }; + + [123, 0, true, false].forEach((opts) => + createReadStreamErr(example, opts, typeError), + ); + + // Case 0: Should not throw if either start or end is undefined + [{}, { start: 0 }, { end: Infinity }].forEach((opts) => + createReadStream(example, opts), + ); + + // Case 1: Should throw TypeError if either start or end is not of type 'number' + [ + { start: "invalid" }, + { end: "invalid" }, + { start: "invalid", end: "invalid" }, + ].forEach((opts) => createReadStreamErr(example, opts, typeError)); + + // Case 2: Should throw RangeError if either start or end is NaN + [{ start: NaN }, { end: NaN }, { start: NaN, end: NaN }].forEach((opts) => + createReadStreamErr(example, opts, rangeError), + ); + + // Case 3: Should throw RangeError if either start or end is negative + [{ start: -1 }, { end: -1 }, { start: -1, end: -1 }].forEach((opts) => + createReadStreamErr(example, opts, rangeError), + ); + + // Case 4: Should throw RangeError if either start or end is fractional + [{ start: 0.1 }, { end: 0.1 }, { start: 0.1, end: 0.1 }].forEach((opts) => + createReadStreamErr(example, opts, rangeError), + ); + + // Case 5: Should not throw if both start and end are whole numbers + createReadStream(example, { start: 1, end: 5 }); + + // Case 6: Should throw RangeError if start is greater than end + createReadStreamErr(example, { start: 5, end: 1 }, rangeError); + + // Case 7: Should throw RangeError if start or end is not safe integer + const NOT_SAFE_INTEGER = 2 ** 53; + [ + { start: NOT_SAFE_INTEGER, end: Infinity }, + { start: 0, end: NOT_SAFE_INTEGER }, + ].forEach((opts) => createReadStreamErr(example, opts, rangeError)); + }, +}; + +export const readStreamTest14 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + let data = ""; + let first = true; + + const stream = createReadStream(path); + stream.setEncoding("utf8"); + stream.on("data", function (chunk) { + data += chunk; + if (first) { + first = false; + stream.resume(); + } + }); + + const { promise, resolve } = Promise.withResolvers(); + + process.nextTick(function () { + stream.pause(); + setTimeout(function () { + stream.resume(); + resolve(); + }, 100); + }); + + await promise; + strictEqual(data, "xyz\n"); + }, +}; + +export const readStreamTest15 = { + async test() { + const { promise, resolve } = Promise.withResolvers(); + ReadStream.prototype.open = resolve; + createReadStream("asd"); + await promise; + delete ReadStream.prototype.open; + }, +}; + +// const fn = fixtures.path('elipses.txt'); +// const rangeFile = fixtures.path('x.txt'); + +export const readStreamTest16 = { + async test() { + let paused = false; + const path = prepareFile(); + + const file = ReadStream(path); + + const promises = []; + const { promise: openPromise, resolve: openResolve } = + Promise.withResolvers(); + const { promise: endPromise, resolve: endResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + promises.push(openPromise); + promises.push(endPromise); + promises.push(closePromise); + + file.on("open", function (fd) { + file.length = 0; + strictEqual(typeof fd, "number"); + ok(file.readable); + + // GH-535 + file.pause(); + file.resume(); + file.pause(); + file.resume(); + openResolve(); + }); + + file.on("data", function (data) { + ok(data instanceof Buffer); + ok(!paused); + file.length += data.length; + + paused = true; + file.pause(); + + setTimeout(function () { + paused = false; + file.resume(); + }, 10); + }); + + file.on("end", endResolve); + + file.on("close", function () { + strictEqual(file.length, 30000); + closeResolve(); + }); + + await Promise.all(promises); + }, +}; + +export const readStreamTest17 = { + async test() { + const path = prepareFile(); + const file = createReadStream(path, { __proto__: { encoding: "utf8" } }); + file.length = 0; + file.on("data", function (data) { + strictEqual(typeof data, "string"); + file.length += data.length; + + for (let i = 0; i < data.length; i++) { + // http://www.fileformat.info/info/unicode/char/2026/index.htm + strictEqual(data[i], "\u2026"); + } + }); + + const { promise, resolve } = Promise.withResolvers(); + file.on("close", function () { + strictEqual(file.length, 10000); + resolve(); + }); + + await promise; + }, +}; + +export const readStreamTest18 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const options = { __proto__: { bufferSize: 1, start: 1, end: 2 } }; + const file = createReadStream(path, options); + strictEqual(file.start, 1); + strictEqual(file.end, 2); + let contentRead = ""; + file.on("data", function (data) { + contentRead += data.toString("utf-8"); + }); + + const { promise, resolve } = Promise.withResolvers(); + file.on("end", function () { + strictEqual(contentRead, "yz"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest19 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const options = { __proto__: { bufferSize: 1, start: 1 } }; + const file = createReadStream(path, options); + strictEqual(file.start, 1); + file.data = ""; + file.on("data", function (data) { + file.data += data.toString("utf-8"); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on("end", function () { + strictEqual(file.data, "yz\n"); + resolve(); + }); + await promise; + }, +}; + +// https://github.com/joyent/node/issues/2320 +export const readStreamTest20 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const options = { __proto__: { bufferSize: 1.23, start: 1 } }; + const file = createReadStream(path, options); + strictEqual(file.start, 1); + file.data = ""; + file.on("data", function (data) { + file.data += data.toString("utf-8"); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on("end", function () { + strictEqual(file.data, "yz\n"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest21 = { + test() { + const path = "/tmp/x.txt"; + throws(() => createReadStream(path, { __proto__: { start: 10, end: 2 } }), { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }); + }, +}; + +export const readStreamTest22 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const options = { __proto__: { start: 0, end: 0 } }; + const stream = createReadStream(path, options); + strictEqual(stream.start, 0); + strictEqual(stream.end, 0); + stream.data = ""; + + stream.on("data", function (chunk) { + stream.data += chunk; + }); + + const { promise, resolve } = Promise.withResolvers(); + stream.on("end", function () { + strictEqual(stream.data, "x"); + resolve(); + }); + await promise; + }, +}; + +export const readStreamTest23 = { + async test() { + const path = "/tmp/x.txt"; + let output = ""; + writeFileSync(path, "hello world"); + const fd = openSync(path, "r"); + const stream = createReadStream(null, { fd: fd, encoding: "utf8" }); + strictEqual(stream.path, undefined); + stream.on("data", (data) => { + output += data; + }); + const { promise, resolve } = Promise.withResolvers(); + stream.on("close", resolve); + await promise; + strictEqual(output, "hello world"); + }, +}; + +export const readStreamTest24 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "hello world"); + const stream = createReadStream(path); + const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); + const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); + stream.close(resolve1); + stream.close(resolve2); + await Promise.all([promise1, promise2]); + }, +}; + +export const readStreamTest25 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "hello world"); + const stream = createReadStream(path); + const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); + const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); + stream.destroy(null, resolve1); + stream.destroy(null, resolve2); + await Promise.all([promise1, promise2]); + }, +}; + +export const readStreamTest26 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "hello world"); + const fh = await promises.open(path, "r"); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + fh.on("close", closeResolve); + const stream = createReadStream(null, { fd: fh, encoding: "utf8" }); + let data = ""; + stream.on("data", (chunk) => (data += chunk)); + const { promise, resolve } = Promise.withResolvers(); + stream.on("end", () => resolve()); + await Promise.all([promise, closePromise]); + strictEqual(data, "hello world"); + strictEqual(fh.fd, undefined); + }, +}; + +export const readStreamTest27 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + const { promise, resolve, reject } = Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + handle.on("close", resolve); + const stream = createReadStream(null, { fd: handle }); + stream.on("data", reject); + stream.on("close", closeResolve); + handle.close(); + await Promise.all([promise, closePromise]); + }, +}; + +export const readStreamTest28 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + const { promise: handlePromise, resolve: handleResolve } = + Promise.withResolvers(); + const { promise: streamPromise, resolve: streamResolve } = + Promise.withResolvers(); + handle.on("close", handleResolve); + const stream = createReadStream(null, { fd: handle }); + stream.on("close", streamResolve); + stream.on("data", () => handle.close()); + await Promise.all([handlePromise, streamPromise]); + }, +}; + +export const readStreamTest29 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + const { promise: handlePromise, resolve: handleResolve } = + Promise.withResolvers(); + const { promise: streamPromise, resolve: streamResolve } = + Promise.withResolvers(); + handle.on("close", handleResolve); + const stream = createReadStream(null, { fd: handle }); + stream.on("close", streamResolve); + stream.close(); + await Promise.all([handlePromise, streamPromise]); + }, +}; + +export const readStreamTest30 = { + async test() { + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + throws(() => createReadStream(null, { fd: handle, fs: {} }), { + code: "ERR_METHOD_NOT_IMPLEMENTED", + }); + handle.close(); + }, +}; + +export const readStreamTest31 = { + async test() { + // AbortSignal option test + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + const controller = new AbortController(); + const { signal } = controller; + const stream = handle.createReadStream({ signal }); + + stream.on("data", () => { + throw new Error("boom"); + }); + stream.on("end", () => { + throw new Error("boom"); + }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + stream.on("error", (err) => { + strictEqual(err.name, "AbortError"); + errorResolve(); + }); + + handle.on("close", closeResolve); + stream.on("close", () => handle.close()); + + controller.abort(); + + await Promise.all([errorPromise, closePromise]); + }, +}; + +export const readStreamTest32 = { + async test() { + // Already-aborted signal test + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + + const signal = AbortSignal.abort(); + const stream = handle.createReadStream({ signal }); + + stream.on("data", () => { + throw new Error("boom"); + }); + stream.on("end", () => { + throw new Error("boom"); + }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + + stream.on("error", (err) => { + strictEqual(err.name, "AbortError"); + errorResolve(); + }); + + handle.on("close", closeResolve); + stream.on("close", () => handle.close()); + + await Promise.all([errorPromise, closePromise]); + }, +}; + +export const readStreamTest33 = { + async test() { + // Invalid signal type test + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + + for (const signal of [ + 1, + {}, + [], + "", + NaN, + 1n, + () => {}, + Symbol(), + false, + true, + ]) { + throws(() => handle.createReadStream({ signal }), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + } + handle.close(); + }, +}; + +export const readStreamTest34 = { + async test() { + // Custom abort reason test + const path = "/tmp/x.txt"; + writeFileSync(path, "xyz\n"); + const handle = await promises.open(path, "r"); + const controller = new AbortController(); + const { signal } = controller; + const reason = new Error("some silly abort reason"); + const stream = handle.createReadStream({ signal }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + + stream.on("error", (err) => { + strictEqual(err.name, "AbortError"); + strictEqual(err.cause, reason); + errorResolve(); + }); + + handle.on("close", closeResolve); + stream.on("close", () => handle.close()); + + controller.abort(reason); + + await Promise.all([errorPromise, closePromise]); + }, +}; + +export const emptyReadStreamTest = { + async test() { + writeFileSync("/tmp/empty.txt", ""); + const stream = createReadStream("/tmp/empty.txt"); + const { promise, resolve, reject } = Promise.withResolvers(); + stream.once("data", () => { + reject(new Error("should not emit data")); + }); + stream.once("end", resolve); + await promise; + strictEqual(stream.bytesRead, 0); + }, +}; + +export const fileHandleReadableWebStreamTest = { + async test() { + writeFileSync("/tmp/stream.txt", "abcde".repeat(1000)); + const fh = await promises.open("/tmp/stream.txt", "r"); + const stream = fh.readableWebStream(); + const _enc = new TextEncoder(); + let data = ""; + for await (const chunk of stream) { + strictEqual(chunk instanceof Uint8Array, true); + data += new TextDecoder().decode(chunk); + } + strictEqual(data, "abcde".repeat(1000)); + strictEqual(fh.fd, undefined); + + // Should throw if the stream is closed. + throws(() => fh.readableWebStream(), { + code: "EBADF", + }); + + const fh2 = await promises.open("/tmp/stream.txt", "r"); + const stream2 = fh2.readableWebStream({ autoClose: false }); + await fh2.close(); + const res = await stream2.getReader().read(); + strictEqual(res.done, true); + strictEqual(res.value, undefined); + }, +}; + +/** + * TODO(node-fs): Revisit + * Temporarily comment out. These are larger tests causing timeouts + * In CI. Will move them out to separate tests in a follow on PR +export const readStreamTest98 = { + async test() { + const path = prepareFile(); + const content = readFileSync(path); + + const N = 20; + let started = 0; + let done = 0; + + const arrayBuffers = new Set(); + const promises = []; + + async function startRead() { + ++started; + const chunks = []; + const promises = []; + const { promise, resolve } = Promise.withResolvers(); + promises.push(promise); + createReadStream(path) + .on('data', (chunk) => { + chunks.push(chunk); + arrayBuffers.add(chunk.buffer); + }) + .on('end', () => { + if (started < N) promises.push(startRead()); + deepStrictEqual(Buffer.concat(chunks), content); + if (++done === N) { + const retainedMemory = [...arrayBuffers] + .map((ab) => ab.byteLength) + .reduce((a, b) => a + b); + ok( + retainedMemory / (N * content.length) <= 3, + `Retaining ${retainedMemory} bytes in ABs for ${N} ` + + `chunks of size ${content.length}` + ); + } + resolve(); + }); + await Promise.all(promises); + } + + // Don’t start the reads all at once – that way we would have to allocate + // a large amount of memory upfront. + for (let i = 0; i < 6; ++i) { + promises.push(startRead()); + } + await Promise.all(promises); + }, +}; + +export const readStreamTest99 = { + async test() { + const path = '/tmp/read_stream_pos_test.txt'; + writeFileSync(path, ''); + + let counter = 0; + + const writeInterval = setInterval(() => { + counter = counter + 1; + const line = `hello at ${counter}\n`; + writeFileSync(path, line, { flag: 'a' }); + }, 1); + + const hwm = 10; + let bufs = []; + let isLow = false; + let cur = 0; + let stream; + + const readInterval = setInterval(() => { + if (stream) return; + + stream = createReadStream(path, { + highWaterMark: hwm, + start: cur, + }); + stream.on('data', (chunk) => { + cur += chunk.length; + bufs.push(chunk); + if (isLow) { + const brokenLines = Buffer.concat(bufs) + .toString() + .split('\n') + .filter((line) => { + const s = 'hello at'.slice(0, line.length); + if (line && !line.startsWith(s)) { + return true; + } + return false; + }); + strictEqual(brokenLines.length, 0); + exitTest(); + return; + } + if (chunk.length !== hwm) { + isLow = true; + } + }); + stream.on('end', () => { + stream = null; + isLow = false; + bufs = []; + }); + }, 10); + + // Time longer than 10 seconds to exit safely + await scheduler.wait(5_000); + + clearInterval(readInterval); + clearInterval(writeInterval); + + if (stream && !stream.destroyed) { + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', resolve); + stream.destroy(); + await promise; + } + }, +}; +**/ diff --git a/src/compat/fs/upstream-readsync-byteoffset.fixture.mjs b/src/compat/fs/upstream-readsync-byteoffset.fixture.mjs new file mode 100644 index 00000000..5c3d7ec9 --- /dev/null +++ b/src/compat/fs/upstream-readsync-byteoffset.fixture.mjs @@ -0,0 +1,97 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { strictEqual } from "node:assert"; +import { openSync, closeSync, readSync, writeFileSync } from "./index.js"; + +// Regression test for AUTOVULN-CLOUDFLARE-WORKERD-13 +export const readSyncByteOffsetRegressionTest = { + test() { + writeFileSync("/tmp/byteoffset-test.txt", "ATTACK"); + const ab = new ArrayBuffer(13); + const full = new Uint8Array(ab); + full.set([83, 69, 67, 82, 69, 84, 124, 80, 85, 66, 76, 73, 67]); + const view = new Uint8Array(ab, 7, 6); + const fd = openSync("/tmp/byteoffset-test.txt", "r"); + try { + readSync(fd, view, { offset: 0, length: 6, position: 0 }); + } finally { + closeSync(fd); + } + strictEqual(new TextDecoder().decode(view), "ATTACK"); + strictEqual(full[0], 83); + strictEqual(full[6], 124); + }, +}; + +export const readSyncNumericOffsetTest = { + test() { + writeFileSync("/tmp/numoffset-test.txt", "HELLO"); + const ab = new ArrayBuffer(16); + new Uint8Array(ab).fill(65); + const view = new Uint8Array(ab, 8, 5); + const fd = openSync("/tmp/numoffset-test.txt", "r"); + try { + readSync(fd, view, 0, 5, 0); + } finally { + closeSync(fd); + } + strictEqual(new TextDecoder().decode(view), "HELLO"); + strictEqual(new Uint8Array(ab, 0, 8)[0], 65); + }, +}; + +// Nonzero numeric offset argument + undefined length: length should default to +// (buffer.byteLength - offset) of the *view*, and the read should land within +// the view starting at the given offset, not corrupting bytes outside. +export const readSyncNonzeroOffsetUndefinedLengthTest = { + test() { + writeFileSync("/tmp/nz-offset-undef-len-test.txt", "XYZ"); + const ab = new ArrayBuffer(16); + new Uint8Array(ab).fill(65); // 'A' everywhere + const view = new Uint8Array(ab, 4, 8); // view spans ab[4..12) + const fd = openSync("/tmp/nz-offset-undef-len-test.txt", "r"); + try { + // offset=2 within the view, length omitted (undefined), position=0. + readSync(fd, view, 2, undefined, 0); + } finally { + closeSync(fd); + } + // Data should land at view[2..5) == ab[6..9). + const full = new Uint8Array(ab); + // Bytes outside the view must remain 'A'. + for (let i = 0; i < 4; i++) strictEqual(full[i], 65); + for (let i = 12; i < 16; i++) strictEqual(full[i], 65); + // Bytes inside the view before the offset must remain 'A'. + strictEqual(full[4], 65); + strictEqual(full[5], 65); + // Data 'XYZ' should appear at ab[6..9). + strictEqual(full[6], 0x58); // 'X' + strictEqual(full[7], 0x59); // 'Y' + strictEqual(full[8], 0x5a); // 'Z' + }, +}; + +// Same scenario via the options-object form: nonzero offset, length omitted. +export const readSyncNonzeroOffsetUndefinedLengthOptionsTest = { + test() { + writeFileSync("/tmp/nz-offset-undef-len-opts-test.txt", "XYZ"); + const ab = new ArrayBuffer(16); + new Uint8Array(ab).fill(65); + const view = new Uint8Array(ab, 4, 8); + const fd = openSync("/tmp/nz-offset-undef-len-opts-test.txt", "r"); + try { + readSync(fd, view, { offset: 2, position: 0 }); + } finally { + closeSync(fd); + } + const full = new Uint8Array(ab); + for (let i = 0; i < 4; i++) strictEqual(full[i], 65); + for (let i = 12; i < 16; i++) strictEqual(full[i], 65); + strictEqual(full[4], 65); + strictEqual(full[5], 65); + strictEqual(full[6], 0x58); + strictEqual(full[7], 0x59); + strictEqual(full[8], 0x5a); + }, +}; diff --git a/src/compat/fs/upstream-stat.fixture.mjs b/src/compat/fs/upstream-stat.fixture.mjs new file mode 100644 index 00000000..40f48d18 --- /dev/null +++ b/src/compat/fs/upstream-stat.fixture.mjs @@ -0,0 +1,451 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert"; + +import { + openSync, + closeSync, + symlinkSync, + statSync, + lstatSync, + fstatSync, + stat, + lstat, + fstat, + promises, + statfsSync, + statfs, + Stats, +} from "./index.js"; + +strictEqual(typeof openSync, "function"); +strictEqual(typeof closeSync, "function"); +strictEqual(typeof statSync, "function"); +strictEqual(typeof lstatSync, "function"); +strictEqual(typeof fstatSync, "function"); +strictEqual(typeof stat, "function"); +strictEqual(typeof lstat, "function"); +strictEqual(typeof fstat, "function"); +strictEqual(typeof symlinkSync, "function"); +strictEqual(typeof statfsSync, "function"); +strictEqual(typeof statfs, "function"); +strictEqual(typeof promises.stat, "function"); +strictEqual(typeof promises.lstat, "function"); + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; + +function checkRootStat(stat, bigint = false) { + ok(stat instanceof Stats); + strictEqual(stat.isDirectory(), true); + strictEqual(stat.isFile(), false); + strictEqual(stat.isBlockDevice(), false); + strictEqual(stat.isCharacterDevice(), false); + strictEqual(stat.isSymbolicLink(), false); + strictEqual(stat.isFIFO(), false); + strictEqual(stat.isSocket(), false); + strictEqual(stat.dev, bigint ? 0n : 0); + strictEqual(stat.ino, bigint ? 0n : 0); + strictEqual(stat.mode, bigint ? 16676n : 16676); + strictEqual(stat.nlink, bigint ? 1n : 1); + strictEqual(stat.uid, bigint ? 0n : 0); + strictEqual(stat.gid, bigint ? 0n : 0); + strictEqual(stat.rdev, bigint ? 0n : 0); + strictEqual(stat.size, bigint ? 0n : 0); + strictEqual(stat.blksize, bigint ? 0n : 0); + strictEqual(stat.blocks, bigint ? 0n : 0); + strictEqual(stat.atime.getTime(), 0); + strictEqual(stat.mtime.getTime(), 0); + strictEqual(stat.ctime.getTime(), 0); + strictEqual(stat.birthtime.getTime(), 0); + strictEqual(stat.atimeMs, bigint ? 0n : 0); + strictEqual(stat.mtimeMs, bigint ? 0n : 0); + strictEqual(stat.ctimeMs, bigint ? 0n : 0); + strictEqual(stat.birthtimeMs, bigint ? 0n : 0); + if (bigint) { + strictEqual(stat.atimeNs, 0n); + strictEqual(stat.mtimeNs, 0n); + strictEqual(stat.ctimeNs, 0n); + strictEqual(stat.birthtimeNs, 0n); + } +} + +function checkFileStat(stat, bigint = false) { + ok(stat instanceof Stats); + strictEqual(stat.isDirectory(), false); + strictEqual(stat.isFile(), true); + strictEqual(stat.isBlockDevice(), false); + strictEqual(stat.isCharacterDevice(), false); + strictEqual(stat.isSymbolicLink(), false); + strictEqual(stat.isFIFO(), false); + strictEqual(stat.isSocket(), false); + strictEqual(stat.dev, bigint ? 0n : 0); + strictEqual(stat.ino, bigint ? 0n : 0); + strictEqual(stat.mode, bigint ? 33060n : 33060); + strictEqual(stat.nlink, bigint ? 1n : 1); + strictEqual(stat.uid, bigint ? 0n : 0); + strictEqual(stat.gid, bigint ? 0n : 0); + strictEqual(stat.rdev, bigint ? 0n : 0); + ok(stat.size > 0n || stat.size > 0); + strictEqual(stat.blksize, bigint ? 0n : 0); + strictEqual(stat.blocks, bigint ? 0n : 0); + // The size is going to be constantly changing for this test file, + // so let's just check that it's not zero. +} + +function checkCharacterDeviceStat(stat, bigint = false) { + ok(stat instanceof Stats); + strictEqual(stat.isDirectory(), false); + strictEqual(stat.isFile(), false); + strictEqual(stat.isBlockDevice(), false); + strictEqual(stat.isCharacterDevice(), true); + strictEqual(stat.isSymbolicLink(), false); + strictEqual(stat.isFIFO(), false); + strictEqual(stat.isSocket(), false); + strictEqual(stat.dev, bigint ? 1n : 1); + strictEqual(stat.ino, bigint ? 0n : 0); + strictEqual(stat.mode, bigint ? 8630n : 8630); + strictEqual(stat.nlink, bigint ? 1n : 1); + strictEqual(stat.uid, bigint ? 0n : 0); + strictEqual(stat.gid, bigint ? 0n : 0); + strictEqual(stat.rdev, bigint ? 0n : 0); +} + +function checkLinkStat(stat, bigint = false) { + ok(stat instanceof Stats); + strictEqual(stat.isDirectory(), false); + strictEqual(stat.isFile(), false); + strictEqual(stat.isBlockDevice(), false); + strictEqual(stat.isCharacterDevice(), false); + strictEqual(stat.isSymbolicLink(), true); + strictEqual(stat.isFIFO(), false); + strictEqual(stat.isSocket(), false); + strictEqual(stat.dev, bigint ? 0n : 0); + strictEqual(stat.ino, bigint ? 0n : 0); + strictEqual(stat.mode, bigint ? 41252n : 41252); + strictEqual(stat.nlink, bigint ? 1n : 1); + strictEqual(stat.uid, bigint ? 0n : 0); + strictEqual(stat.gid, bigint ? 0n : 0); + strictEqual(stat.rdev, bigint ? 0n : 0); +} + +export const statSyncTest = { + async test() { + throws(() => statSync(), kInvalidArgTypeError); + throws(() => statSync("/", "foo"), kInvalidArgTypeError); + throws(() => statSync("/", { bigint: "yes" }), kInvalidArgTypeError); + throws(() => lstatSync(), kInvalidArgTypeError); + throws(() => lstatSync("/", "foo"), kInvalidArgTypeError); + throws(() => lstatSync("/", { bigint: "yes" }), kInvalidArgTypeError); + throws(() => fstatSync(), kInvalidArgTypeError); + throws(() => fstatSync(123, "foo"), kInvalidArgTypeError); + throws(() => fstatSync(123, { bigint: "yes" }), kInvalidArgTypeError); + + symlinkSync("/", "/tmp/link-root"); + symlinkSync("/bundle/worker", "/tmp/link-bundle-worker"); + symlinkSync("/dev/null", "/tmp/link-dev-null"); + + const fd1 = openSync("/", "r"); + const fd2 = openSync("/bundle/worker", "r"); + const fd3 = openSync("/dev/null", "r"); + + checkRootStat(statSync("/")); + checkRootStat(statSync("/", { bigint: true }), true); + checkRootStat(lstatSync("/")); + checkRootStat(lstatSync("/", { bigint: true }), true); + checkRootStat(fstatSync(fd1)); + checkRootStat(fstatSync(fd1, { bigint: true }), true); + + checkFileStat(statSync("/bundle/worker")); + checkFileStat(statSync("/bundle/worker", { bigint: true }), true); + checkFileStat(lstatSync("/bundle/worker")); + checkFileStat(lstatSync("/bundle/worker", { bigint: true }), true); + checkFileStat(fstatSync(fd2)); + + checkCharacterDeviceStat(statSync("/dev/null")); + checkCharacterDeviceStat(statSync("/dev/null", { bigint: true }), true); + checkCharacterDeviceStat(lstatSync("/dev/null")); + checkCharacterDeviceStat(lstatSync("/dev/null", { bigint: true }), true); + checkCharacterDeviceStat(fstatSync(fd3)); + + // Check that symlinks are followed by default with statSync. + checkRootStat(statSync("/tmp/link-root")); + checkRootStat(statSync("/tmp/link-root", { bigint: true }), true); + checkFileStat(statSync("/tmp/link-bundle-worker")); + checkFileStat(statSync("/tmp/link-bundle-worker", { bigint: true }), true); + checkCharacterDeviceStat(statSync("/tmp/link-dev-null")); + checkCharacterDeviceStat( + statSync("/tmp/link-dev-null", { bigint: true }), + true, + ); + + // But lstatSync does not follow symlinks. + checkLinkStat(lstatSync("/tmp/link-root")); + checkLinkStat(lstatSync("/tmp/link-root", { bigint: true }), true); + checkLinkStat(lstatSync("/tmp/link-bundle-worker")); + checkLinkStat(lstatSync("/tmp/link-bundle-worker", { bigint: true }), true); + checkLinkStat(lstatSync("/tmp/link-dev-null")); + checkLinkStat(lstatSync("/tmp/link-dev-null", { bigint: true }), true); + + // Non-existent paths and file descriptors + throws(() => statSync("/non-existent-path"), { code: "ENOENT" }); + throws(() => lstatSync("/non-existent-path"), { code: "ENOENT" }); + throws(() => fstatSync(123), { code: "EBADF" }); + + strictEqual( + statSync("/non-existent-path", { throwIfNoEntry: false }), + undefined, + ); + strictEqual( + lstatSync("/non-existent-path", { throwIfNoEntry: false }), + undefined, + ); + + closeSync(fd1); + closeSync(fd2); + closeSync(fd3); + }, +}; + +export const statTest = { + async test() { + throws(() => stat(), kInvalidArgTypeError); + throws(() => stat("/", "foo"), kInvalidArgTypeError); + throws(() => stat("/", { bigint: "yes" }), kInvalidArgTypeError); + throws(() => stat("/"), kInvalidArgTypeError); + throws(() => lstat(), kInvalidArgTypeError); + throws(() => lstat("/", "foo"), kInvalidArgTypeError); + throws(() => lstat("/", { bigint: "yes" }), kInvalidArgTypeError); + throws(() => lstat("/"), kInvalidArgTypeError); + throws(() => fstat(), kInvalidArgTypeError); + throws(() => fstat(123, "foo"), kInvalidArgTypeError); + throws(() => fstat(123, { bigint: "yes" }), kInvalidArgTypeError); + throws(() => fstat(123), kInvalidArgTypeError); + + symlinkSync("/", "/tmp/link-root"); + symlinkSync("/bundle/worker", "/tmp/link-bundle-worker"); + symlinkSync("/dev/null", "/tmp/link-dev-null"); + + const fd1 = openSync("/", "r"); + const fd2 = openSync("/bundle/worker", "r"); + const fd3 = openSync("/dev/null", "r"); + + const doStatTest = async (statFn, path, bigint, checkFn) => { + const { promise, resolve, reject } = Promise.withResolvers(); + statFn(path, { bigint }, (err, stat) => { + if (err) { + reject(err); + } else { + try { + checkFn(stat, bigint); + resolve(); + } catch (e) { + reject(e); + } + } + }); + await promise; + }; + + const doFailTest = async (statFn, path, code) => { + const { promise, resolve, reject } = Promise.withResolvers(); + statFn(path, (err) => { + if (err) { + strictEqual(err.code, code); + resolve(); + } else { + reject(new Error(`Expected error for path: ${path}`)); + } + }); + await promise; + }; + + await doStatTest(stat, "/", false, checkRootStat); + await doStatTest(stat, "/", true, checkRootStat); + await doStatTest(stat, "/bundle/worker", false, checkFileStat); + await doStatTest(stat, "/bundle/worker", true, checkFileStat); + await doStatTest(stat, "/dev/null", false, checkCharacterDeviceStat); + await doStatTest(stat, "/dev/null", true, checkCharacterDeviceStat); + + await doStatTest(stat, "/tmp/link-root", false, checkRootStat); + await doStatTest(stat, "/tmp/link-root", true, checkRootStat); + await doStatTest(stat, "/tmp/link-bundle-worker", false, checkFileStat); + await doStatTest(stat, "/tmp/link-bundle-worker", true, checkFileStat); + await doStatTest( + stat, + "/tmp/link-dev-null", + false, + checkCharacterDeviceStat, + ); + await doStatTest( + stat, + "/tmp/link-dev-null", + true, + checkCharacterDeviceStat, + ); + + await doStatTest(lstat, "/", false, checkRootStat); + await doStatTest(lstat, "/", true, checkRootStat); + await doStatTest(lstat, "/bundle/worker", false, checkFileStat); + await doStatTest(lstat, "/bundle/worker", true, checkFileStat); + await doStatTest(lstat, "/dev/null", false, checkCharacterDeviceStat); + await doStatTest(lstat, "/dev/null", true, checkCharacterDeviceStat); + + await doStatTest(lstat, "/tmp/link-root", false, checkLinkStat); + await doStatTest(lstat, "/tmp/link-root", true, checkLinkStat); + await doStatTest(lstat, "/tmp/link-bundle-worker", false, checkLinkStat); + await doStatTest(lstat, "/tmp/link-bundle-worker", true, checkLinkStat); + await doStatTest(lstat, "/tmp/link-dev-null", false, checkLinkStat); + await doStatTest(lstat, "/tmp/link-dev-null", true, checkLinkStat); + + await doStatTest(fstat, fd1, false, checkRootStat); + await doStatTest(fstat, fd1, true, checkRootStat); + await doStatTest(fstat, fd2, false, checkFileStat); + await doStatTest(fstat, fd2, true, checkFileStat); + await doStatTest(fstat, fd3, false, checkCharacterDeviceStat); + await doStatTest(fstat, fd3, true, checkCharacterDeviceStat); + + // // Non-existent paths and file descriptors + await doFailTest(stat, "/non-existent-path", "ENOENT"); + await doFailTest(lstat, "/non-existent-path", "ENOENT"); + await doFailTest(fstat, 123, "EBADF"); + + closeSync(fd1); + closeSync(fd2); + closeSync(fd3); + }, +}; + +export const statPromisesTest = { + async test() { + await rejects(promises.stat(), kInvalidArgTypeError); + await rejects(promises.stat("/", "foo"), kInvalidArgTypeError); + await rejects(promises.stat("/", { bigint: "yes" }), kInvalidArgTypeError); + await rejects(promises.lstat(), kInvalidArgTypeError); + await rejects(promises.lstat("/", "foo"), kInvalidArgTypeError); + await rejects(promises.lstat("/", { bigint: "yes" }), kInvalidArgTypeError); + + symlinkSync("/", "/tmp/link-root"); + symlinkSync("/bundle/worker", "/tmp/link-bundle-worker"); + symlinkSync("/dev/null", "/tmp/link-dev-null"); + + await checkRootStat(await promises.stat("/")); + await checkRootStat(await promises.stat("/", { bigint: true }), true); + await checkRootStat(await promises.lstat("/")); + await checkRootStat(await promises.lstat("/", { bigint: true }), true); + + await checkFileStat(await promises.stat("/bundle/worker")); + await checkFileStat( + await promises.stat("/bundle/worker", { bigint: true }), + true, + ); + await checkFileStat(await promises.lstat("/bundle/worker")); + await checkFileStat( + await promises.lstat("/bundle/worker", { bigint: true }), + true, + ); + + await checkCharacterDeviceStat(await promises.stat("/dev/null")); + await checkCharacterDeviceStat( + await promises.stat("/dev/null", { bigint: true }), + true, + ); + await checkCharacterDeviceStat(await promises.lstat("/dev/null")); + await checkCharacterDeviceStat( + await promises.lstat("/dev/null", { bigint: true }), + true, + ); + + await checkRootStat(await promises.stat("/tmp/link-root")); + await checkRootStat( + await promises.stat("/tmp/link-root", { bigint: true }), + true, + ); + await checkFileStat(await promises.stat("/tmp/link-bundle-worker")); + await checkFileStat( + await promises.stat("/tmp/link-bundle-worker", { bigint: true }), + true, + ); + await checkCharacterDeviceStat(await promises.stat("/tmp/link-dev-null")); + await checkCharacterDeviceStat( + await promises.stat("/tmp/link-dev-null", { bigint: true }), + true, + ); + + await checkLinkStat(await promises.lstat("/tmp/link-root")); + await checkLinkStat( + await promises.lstat("/tmp/link-root", { bigint: true }), + true, + ); + await checkLinkStat(await promises.lstat("/tmp/link-bundle-worker")); + await checkLinkStat( + await promises.lstat("/tmp/link-bundle-worker", { bigint: true }), + true, + ); + await checkLinkStat(await promises.lstat("/tmp/link-dev-null")); + await checkLinkStat( + await promises.lstat("/tmp/link-dev-null", { bigint: true }), + true, + ); + }, +}; + +export const statfsTest = { + async test() { + const check = { + type: 0, + bsize: 0, + blocks: 0, + bfree: 0, + bavail: 0, + files: 0, + ffree: 0, + }; + const checkBn = { + type: 0n, + bsize: 0n, + blocks: 0n, + bfree: 0n, + bavail: 0n, + files: 0n, + ffree: 0n, + }; + + deepStrictEqual(statfsSync("/"), check); + deepStrictEqual(statfsSync("/bundle", { bigint: true }), checkBn); + + async function callStatfs(path, fn, bigint = false) { + const { promise, resolve, reject } = Promise.withResolvers(); + statfs(path, { bigint }, (err, stat) => { + if (err) return reject(err); + try { + fn(stat); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + await callStatfs("/", (stat) => { + deepStrictEqual(stat, check); + }); + await callStatfs( + "/", + (stat) => { + deepStrictEqual(stat, checkBn); + }, + true /* bigint */, + ); + + // Throws if the path is invalid / wrong type + throws(() => statfs(123), { + code: /ERR_INVALID_ARG_TYPE/, + }); + + throws(() => statfs("/", { bigint: "yes" }), { + code: /ERR_INVALID_ARG_TYPE/, + }); + }, +}; diff --git a/src/compat/fs/upstream-utimes.fixture.mjs b/src/compat/fs/upstream-utimes.fixture.mjs new file mode 100644 index 00000000..f3204d65 --- /dev/null +++ b/src/compat/fs/upstream-utimes.fixture.mjs @@ -0,0 +1,186 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { ok, match, rejects, strictEqual, throws } from "node:assert"; + +import { + openSync, + existsSync, + closeSync, + statSync, + fstatSync, + utimesSync, + futimesSync, + lutimesSync, + utimes, + futimes, + lutimes, + promises, +} from "./index.js"; + +strictEqual(typeof openSync, "function"); +strictEqual(typeof closeSync, "function"); +strictEqual(typeof statSync, "function"); +strictEqual(typeof utimesSync, "function"); +strictEqual(typeof futimesSync, "function"); +strictEqual(typeof lutimesSync, "function"); +strictEqual(typeof utimes, "function"); +strictEqual(typeof futimes, "function"); +strictEqual(typeof lutimes, "function"); +strictEqual(typeof promises.utimes, "function"); +strictEqual(typeof promises.lutimes, "function"); + +const kInvalidArgTypeError = { code: "ERR_INVALID_ARG_TYPE" }; + +function checkStat(path, mtimeMsCheck) { + const bigint = typeof mtimeMsCheck === "bigint"; + const { atimeMs, mtimeMs, ctimeMs, birthtimeMs } = + typeof path === "number" + ? fstatSync(path, { bigint }) + : statSync(path, { bigint }); + strictEqual(mtimeMs, mtimeMsCheck); + strictEqual(ctimeMs, mtimeMsCheck); + strictEqual(atimeMs, bigint ? 0n : 0); + strictEqual(birthtimeMs, bigint ? 0n : 0); +} + +export const utimesTest = { + async test() { + throws(() => utimesSync(123), kInvalidArgTypeError); + throws(() => utimesSync("", {}), kInvalidArgTypeError); + throws(() => utimesSync("", 0, {}), kInvalidArgTypeError); + throws(() => lutimesSync(123), kInvalidArgTypeError); + throws(() => lutimesSync("", {}), kInvalidArgTypeError); + throws(() => lutimesSync("", 0, {}), kInvalidArgTypeError); + throws(() => futimesSync(""), kInvalidArgTypeError); + throws(() => futimesSync(0, {}), kInvalidArgTypeError); + throws(() => futimesSync(0, 0, {}), kInvalidArgTypeError); + throws(() => utimes(0), kInvalidArgTypeError); + throws(() => utimes("", {}), kInvalidArgTypeError); + throws(() => utimes("", 0, {}), kInvalidArgTypeError); + throws(() => utimes("", 0, 0), kInvalidArgTypeError); + throws(() => utimes(""), kInvalidArgTypeError); + throws(() => lutimes(0), kInvalidArgTypeError); + throws(() => lutimes("", {}), kInvalidArgTypeError); + throws(() => lutimes("", 0, {}), kInvalidArgTypeError); + throws(() => lutimes("", 0, 0), kInvalidArgTypeError); + throws(() => lutimes(""), kInvalidArgTypeError); + throws(() => futimes(""), kInvalidArgTypeError); + throws(() => futimes(0, {}), kInvalidArgTypeError); + throws(() => futimes(0, 0, {}), kInvalidArgTypeError); + throws(() => futimes(0, 0, 0), kInvalidArgTypeError); + throws(() => futimes(0), kInvalidArgTypeError); + await rejects(promises.utimes(123), kInvalidArgTypeError); + await rejects(promises.utimes("", {}), kInvalidArgTypeError); + await rejects(promises.utimes("", 0, {}), kInvalidArgTypeError); + await rejects(promises.lutimes(123), kInvalidArgTypeError); + await rejects(promises.lutimes("", {}), kInvalidArgTypeError); + await rejects(promises.lutimes("", 0, {}), kInvalidArgTypeError); + + throws( + () => { + utimesSync("/tmp/test.txt", 1000, new Date("not a valid date")); + }, + { + message: /^The value cannot be converted/, + name: "TypeError", + }, + ); + + const fd = openSync("/tmp/test.txt", "w+"); + ok(existsSync("/tmp/test.txt")); + + checkStat(fd, 0n); + + utimesSync("/tmp/test.txt", 1000, 2000); + checkStat(fd, 2000n); + + utimesSync("/tmp/test.txt", 1000, new Date(0)); + checkStat(fd, 0); + + utimesSync("/tmp/test.txt", 3000, "1970-01-01T01:00:00.000Z"); + checkStat(fd, 3600000n); + + lutimesSync("/tmp/test.txt", 3000, 4000); + checkStat(fd, 4000n); + + lutimesSync("/tmp/test.txt", 1000, new Date(0)); + checkStat(fd, 0); + + lutimesSync("/tmp/test.txt", 3000, "1970-01-01T01:00:00.000Z"); + checkStat(fd, 3600000n); + + futimesSync(fd, 5000, 6000); + checkStat(fd, 6000n); + + futimesSync(fd, 1000, new Date(0)); + checkStat(fd, 0); + + futimesSync(fd, 3000, "1970-01-01T01:00:00.000Z"); + checkStat(fd, 3600000n); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + utimes("/tmp/test.txt", 8000, new Date("not a valid date"), (err) => { + try { + ok(err); + strictEqual(err.name, "TypeError"); + match(err.message, /The value cannot be converted/); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + utimes("/tmp/test.txt", 8000, 9000, (err) => { + if (err) return reject(err); + try { + checkStat(fd, 9000n); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + lutimes("/tmp/test.txt", 8000, 10000, (err) => { + if (err) return reject(err); + try { + checkStat(fd, 10000n); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + futimes(fd, 7000, 11000, (err) => { + if (err) return reject(err); + try { + checkStat(fd, 11000n); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + await promises.utimes("/tmp/test.txt", 12000, 13000); + checkStat(fd, 13000n); + await promises.lutimes("/tmp/test.txt", 14000, 15000); + checkStat(fd, 15000n); + + closeSync(fd); + }, +}; diff --git a/src/compat/fs/upstream-writestream.fixture.mjs b/src/compat/fs/upstream-writestream.fixture.mjs new file mode 100644 index 00000000..eb87afd3 --- /dev/null +++ b/src/compat/fs/upstream-writestream.fixture.mjs @@ -0,0 +1,516 @@ +// Copyright (c) 2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { + WriteStream, + createWriteStream, + createReadStream, + readFileSync, + writeFileSync, + closeSync, + open as openAsync, + close as closeAsync, + write as writeAsync, + writev as writevAsync, + fsync as fsyncAsync, + promises, +} from "./index.js"; + +import { Writable } from "../stream/index.js"; + +import { + deepStrictEqual, + ok, + notStrictEqual, + strictEqual, + throws, +} from "node:assert"; +import { mock } from "node:test"; + +strictEqual(typeof WriteStream, "function"); +strictEqual(typeof createWriteStream, "function"); + +export const simpleWriteStreamTest = { + async test() { + const path = "/tmp/workerd-fs-writestream-test.txt"; + const data = "Hello, World!"; + const writeStream = createWriteStream(path, { flags: "w" }); + writeStream.write(data); + writeStream.end(); + const { promise, resolve } = Promise.withResolvers(); + const { promise: finishPromise, resolve: finishResolve } = + Promise.withResolvers(); + writeStream.on("finish", finishResolve); + writeStream.on("close", resolve); + await Promise.all([promise, finishPromise]); + const check = readFileSync(path, "utf8"); + strictEqual(check, data); + }, +}; + +export const writeStreamTest1 = { + async test() { + const path = "/tmp/workerd-fs-writestream-test1.txt"; + const { promise, resolve } = Promise.withResolvers(); + const stream = WriteStream(path, { + fs: { + close(fd) { + ok(fd); + closeSync(fd); + resolve(); + }, + }, + }); + stream.destroy(); + await promise; + }, +}; + +export const writeStreamTest2 = { + async test() { + const path = "/tmp/workerd-fs-writestream-test2.txt"; + const stream = createWriteStream(path); + + const { promise, resolve, reject } = Promise.withResolvers(); + + stream.on("drain", reject); + stream.on("close", resolve); + stream.destroy(); + await promise; + }, +}; + +export const writeStreamTest3 = { + async test() { + const path = "/tmp/workerd-fs-writestream-test3.txt"; + const stream = createWriteStream(path); + const { promise, resolve, reject } = Promise.withResolvers(); + stream.on("error", reject); + stream.on("close", resolve); + throws(() => stream.write(42), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + stream.destroy(); + await promise; + }, +}; + +export const writeStreamTest4 = { + test() { + const example = "/tmp/workerd-fs-writestream-test4.txt"; + createWriteStream(example, undefined).end(); + createWriteStream(example, null).end(); + createWriteStream(example, "utf8").end(); + createWriteStream(example, { encoding: "utf8" }).end(); + + const createWriteStreamErr = (path, opt) => { + throws(() => createWriteStream(path, opt), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + }; + + createWriteStreamErr(example, 123); + createWriteStreamErr(example, 0); + createWriteStreamErr(example, true); + createWriteStreamErr(example, false); + }, +}; + +export const writeStreamTest5 = { + async test() { + const { promise, resolve } = Promise.withResolvers(); + WriteStream.prototype.open = resolve; + createWriteStream("/tmp/test"); + await promise; + delete WriteStream.prototype.open; + }, +}; + +export const writeStreamTest6 = { + async test() { + const path = "/tmp/write-end-test0.txt"; + const fs = { + open: mock.fn(openAsync), + write: mock.fn(writeAsync), + close: mock.fn(closeAsync), + }; + const { promise, resolve } = Promise.withResolvers(); + const stream = createWriteStream(path, { fs }); + stream.on("close", resolve); + stream.end("asd"); + + await promise; + strictEqual(fs.open.mock.callCount(), 1); + strictEqual(fs.write.mock.callCount(), 1); + strictEqual(fs.close.mock.callCount(), 1); + }, +}; + +export const writeStreamTest7 = { + async test() { + const path = "/tmp/write-end-test1.txt"; + const fs = { + open: mock.fn(openAsync), + write: writeAsync, + writev: mock.fn(writevAsync), + close: mock.fn(closeAsync), + }; + const stream = createWriteStream(path, { fs }); + stream.write("asd"); + stream.write("asd"); + stream.write("asd"); + stream.end(); + const { promise, resolve } = Promise.withResolvers(); + stream.on("close", resolve); + await promise; + + strictEqual(fs.open.mock.callCount(), 1); + strictEqual(fs.writev.mock.callCount(), 1); + strictEqual(fs.close.mock.callCount(), 1); + }, +}; + +let cnt = 0; +function nextFile() { + return `/tmp/${cnt++}.out`; +} + +export const writeStreamTest8 = { + test() { + for (const flush of ["true", "", 0, 1, [], {}, Symbol()]) { + throws( + () => { + createWriteStream(nextFile(), { flush }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + } + }, +}; + +export const writeStreamTest9 = { + async test() { + const fs = { + fsync: mock.fn(fsyncAsync), + }; + const stream = createWriteStream(nextFile(), { flush: true, fs }); + + const { promise, resolve, reject } = Promise.withResolvers(); + + stream.write("hello", (err) => { + if (err) return reject(); + stream.close((err) => { + if (err) return reject(err); + resolve(); + }); + }); + + await promise; + + strictEqual(fs.fsync.mock.callCount(), 1); + }, +}; + +export const writeStreamTest10 = { + async test() { + const values = [undefined, null, false]; + const fs = { + fsync: mock.fn(fsyncAsync), + }; + let cnt = 0; + + const { promise, resolve, reject } = Promise.withResolvers(); + + for (const flush of values) { + const file = nextFile(); + const stream = createWriteStream(file, { flush }); + stream.write("hello world", (err) => { + if (err) return reject(err); + stream.close((err) => { + if (err) return reject(err); + strictEqual(readFileSync(file, "utf8"), "hello world"); + cnt++; + if (cnt === values.length) { + strictEqual(fs.fsync.mock.callCount(), 0); + resolve(); + } + }); + }); + } + + await promise; + }, +}; + +export const writeStreamTest11 = { + async test() { + const file = nextFile(); + const handle = await promises.open(file, "w"); + const stream = handle.createWriteStream({ flush: true }); + + const { promise, resolve, reject } = Promise.withResolvers(); + + stream.write("hello", (err) => { + if (err) return reject(err); + stream.close((err) => { + if (err) return reject(err); + strictEqual(readFileSync(file, "utf8"), "hello"); + resolve(); + }); + }); + + await promise; + }, +}; + +export const writeStreamTest12 = { + async test() { + const file = nextFile(); + const handle = await promises.open(file, "w+"); + + const { promise, resolve } = Promise.withResolvers(); + handle.on("close", resolve); + const stream = createWriteStream(null, { fd: handle }); + + stream.end("hello"); + stream.on("close", () => { + const output = readFileSync(file, "utf-8"); + strictEqual(output, "hello"); + }); + + await promise; + }, +}; + +export const writeStreamTest13 = { + async test() { + const file = nextFile(); + const handle = await promises.open(file, "w+"); + let _calls = 0; + const { write: _originalWriteFunction, writev: _originalWritevFunction } = + handle; + handle.write = mock.fn(handle.write.bind(handle)); + handle.writev = mock.fn(handle.writev.bind(handle)); + const stream = createWriteStream(null, { fd: handle }); + stream.end("hello"); + const { promise, resolve } = Promise.withResolvers(); + stream.on("close", () => { + console.log("test"); + ok(handle.write.mock.callCount() + handle.writev.mock.callCount() > 0); + resolve(); + }); + await promise; + }, +}; + +export const writeStreamTest14 = { + async test() { + const path = "/tmp/out"; + + let writeCalls = 0; + const fs = { + write: mock.fn((...args) => { + switch (writeCalls++) { + case 0: { + return writeAsync(...args); + } + case 1: { + args[args.length - 1](new Error("BAM")); + break; + } + default: { + // It should not be called again! + throw new Error("BOOM!"); + } + } + }), + close: mock.fn(closeAsync), + }; + + const stream = createWriteStream(path, { + highWaterMark: 10, + fs, + }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: writePromise, resolve: writeResolve } = + Promise.withResolvers(); + + stream.on("error", (err) => { + strictEqual(stream.fd, null); + strictEqual(err.message, "BAM"); + errorResolve(); + }); + + stream.write(Buffer.allocUnsafe(256), () => { + stream.write(Buffer.allocUnsafe(256), (err) => { + strictEqual(err.message, "BAM"); + writeResolve(); + }); + }); + + await Promise.all([errorPromise, writePromise]); + }, +}; + +export const writeStreamTest15 = { + async test() { + const file = "/tmp/write-end-test0.txt"; + const stream = createWriteStream(file); + stream.end(); + const { promise, resolve } = Promise.withResolvers(); + stream.on("close", resolve); + await promise; + }, +}; + +export const writeStreamTest16 = { + async test() { + const file = "/tmp/write-end-test1.txt"; + const stream = createWriteStream(file); + stream.end("a\n", "utf8"); + const { promise, resolve } = Promise.withResolvers(); + stream.on("close", () => { + const content = readFileSync(file, "utf8"); + strictEqual(content, "a\n"); + resolve(); + }); + await promise; + }, +}; + +export const writeStreamTest17 = { + async test() { + const file = "/tmp/write-end-test2.txt"; + const stream = createWriteStream(file); + stream.end(); + + const { promise: openPromise, resolve: openResolve } = + Promise.withResolvers(); + const { promise: finishPromise, resolve: finishResolve } = + Promise.withResolvers(); + stream.on("open", openResolve); + stream.on("finish", finishResolve); + await Promise.all([openPromise, finishPromise]); + }, +}; + +export const writeStreamTest18 = { + async test() { + const examplePath = "/tmp/a"; + const dummyPath = "/tmp/b"; + const firstEncoding = "base64"; + const secondEncoding = "latin1"; + + writeFileSync(examplePath, "xyz\n"); + const exampleReadStream = createReadStream(examplePath, { + encoding: firstEncoding, + }); + + const dummyWriteStream = createWriteStream(dummyPath, { + encoding: firstEncoding, + }); + + const { promise, resolve } = Promise.withResolvers(); + exampleReadStream.pipe(dummyWriteStream).on("finish", () => { + const assertWriteStream = new Writable({ + write: function (chunk, enc, next) { + const expected = Buffer.from("xyz\n"); + deepStrictEqual(expected, chunk); + next(); + }, + }); + assertWriteStream.setDefaultEncoding(secondEncoding); + createReadStream(dummyPath, { + encoding: secondEncoding, + }) + .pipe(assertWriteStream) + .on("close", resolve); + }); + + await promise; + }, +}; + +export const writeStreamTest19 = { + async test() { + const file = "/tmp/write-end-test3.txt"; + const stream = createWriteStream(file); + const { promise: closePromise1, resolve: closeResolve1 } = + Promise.withResolvers(); + const { promise: closePromise2, resolve: closeResolve2 } = + Promise.withResolvers(); + stream.close(closeResolve1); + stream.close(closeResolve2); + await Promise.all([closePromise1, closePromise2]); + }, +}; + +export const writeStreamTest20 = { + async test() { + const file = "/tmp/write-autoclose-opt1.txt"; + let stream = createWriteStream(file, { flags: "w+", autoClose: false }); + stream.write("Test1"); + stream.end(); + const { promise, resolve, reject } = Promise.withResolvers(); + stream.on("finish", () => { + stream.on("close", reject); + process.nextTick(() => { + strictEqual(stream.closed, false); + notStrictEqual(stream.fd, null); + resolve(); + }); + }); + await promise; + + const { promise: nextPromise, resolve: nextResolve } = + Promise.withResolvers(); + const stream2 = createWriteStream(null, { fd: stream.fd, start: 0 }); + stream2.write("Test2"); + stream2.end(); + stream2.on("finish", () => { + strictEqual(stream2.closed, false); + stream2.on("close", () => { + strictEqual(stream2.fd, null); + strictEqual(stream2.closed, true); + nextResolve(); + }); + }); + + await nextPromise; + + const data = readFileSync(file, "utf8"); + strictEqual(data, "Test2"); + }, +}; + +export const writeStreamTest21 = { + async test() { + // This is to test success scenario where autoClose is true + const file = "/tmp/write-autoclose-opt2.txt"; + const stream = createWriteStream(file, { autoClose: true }); + stream.write("Test3"); + stream.end(); + const { promise, resolve } = Promise.withResolvers(); + stream.on("finish", () => { + strictEqual(stream.closed, false); + stream.on("close", () => { + strictEqual(stream.fd, null); + strictEqual(stream.closed, true); + resolve(); + }); + }); + await promise; + }, +}; + +export const writeStreamTest22 = { + test() { + throws(() => WriteStream.prototype.autoClose, { + code: "ERR_INVALID_THIS", + }); + }, +}; diff --git a/src/compat/fs/writable.spec.ts b/src/compat/fs/writable.spec.ts index 11cab151..3a09c860 100644 --- a/src/compat/fs/writable.spec.ts +++ b/src/compat/fs/writable.spec.ts @@ -98,13 +98,14 @@ test("a temporary symlink can read but cannot mutate embedded assets", async () expect(fs.readFileSync("/bundle/secret", "utf8")).toBe("original"); }); -test("relative temporary symlinks resolve against their parent directory", async () => { +test("relative temporary symlinks resolve from the Workers bundle cwd", async () => { await runWithTemporaryFiles(async () => { fs.mkdirSync("/tmp/nested"); fs.writeFileSync("/tmp/nested/data", "local"); + fs.registerVirtualFiles({ "/bundle/data": "bundled" }); fs.symlinkSync("data", "/tmp/nested/link"); - expect(fs.readFileSync("/tmp/nested/link", "utf8")).toBe("local"); - await fs.promises.symlink("../nested/data", "/tmp/nested/async-link"); + expect(fs.readFileSync("/tmp/nested/link", "utf8")).toBe("bundled"); + await fs.promises.symlink("../tmp/nested/data", "/tmp/nested/async-link"); expect(await fs.promises.readFile("/tmp/nested/async-link", "utf8")).toBe( "local", ); diff --git a/src/compat/fs/writable.ts b/src/compat/fs/writable.ts index 02970396..5a1bbfc9 100644 --- a/src/compat/fs/writable.ts +++ b/src/compat/fs/writable.ts @@ -1,3 +1,9 @@ +import { + currentDirectory, + runWithDirectory, + registerDirectoryValidator, +} from "../process/directory.js"; +export { currentDirectory, changeDirectory } from "../process/directory.js"; import * as embedded from "./embedded.js"; import { fileMode } from "./mode.js"; import { registerRequestScope } from "../../shared/request-scope.js"; @@ -6,24 +12,76 @@ import { registerRequestScope } from "../../shared/request-scope.js"; import { Volume, createFsFromVolume } from "memfs"; import { Buffer } from "node:buffer"; import { posix } from "node:path"; -import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +import { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE } from "../net/errors.js"; import { AsyncLocalStorage } from "../async-hooks/index.js"; export function pathOf(value: any): string { - if (value instanceof URL) value = decodeURIComponent(value.pathname); - if (Buffer.isBuffer(value)) value = value.toString(); - if (typeof value !== "string") - throw new ERR_INVALID_ARG_TYPE("path", ["string", "Buffer", "URL"], value); - return posix.resolve("/bundle", value); + let url: URL; + if (value instanceof URL) url = value; + else { + if (Buffer.isBuffer(value)) value = value.toString(); + if (typeof value !== "string") + throw new ERR_INVALID_ARG_TYPE( + "path", + ["string", "Buffer", "URL"], + value, + ); + if (value.includes("\0")) + throw new ERR_INVALID_ARG_VALUE( + "path", + value, + "must not contain null bytes", + ); + url = new URL( + value.replace(/[#?]/g, encodeURIComponent).replace(/\/\//g, "/"), + "file://" + currentDirectory() + "/", + ); + } + if (url.protocol !== "file:") + throw new TypeError("File path must be a file: URL"); + if (url.host) throw new Error("File path must not have a host"); + const normalized = posix.normalize( + url.pathname.replace(/%([0-9a-f]{2})/gi, (_match, hex) => { + const char = String.fromCharCode(parseInt(hex, 16)); + return /[A-Za-z0-9._~-]/.test(char) ? char : "%" + hex.toUpperCase(); + }), + ); + if (Buffer.byteLength(normalized) > 4096) + throw new Error("File path is too long"); + if ((normalized.match(/\//g) || []).length > 48) + throw new Error("File path has too many segments"); + return normalized.length > 1 ? normalized.replace(/\/$/, "") : normalized; } + +const devices = new Set(["/dev/null", "/dev/zero", "/dev/full", "/dev/random"]); export function temporaryPath(value: any): boolean { if (typeof value === "number") return true; const path = pathOf(value); - return path === "/tmp" || path.startsWith("/tmp/"); + return ( + path === "/tmp" || + path.startsWith("/tmp/") || + path === "/dev" || + devices.has(path) + ); } export function writablePath(value: any, syscall: string) { if (typeof value === "number") return value; const path = pathOf(value); - if (!(path === "/tmp" || path.startsWith("/tmp/"))) + if ( + !( + path === "/tmp" || + path.startsWith("/tmp/") || + (devices.has(path) && + [ + "writeFile", + "appendFile", + "truncate", + "utimes", + "lutimes", + "createWriteStream", + "WriteStream", + ].includes(syscall)) + ) + ) throw Object.assign( new Error(`operation not permitted, ${syscall} '${path}'`), { code: "EPERM", errno: -1, syscall, path }, @@ -33,8 +91,135 @@ export function writablePath(value: any, syscall: string) { function createTemporary() { const volume = new Volume(); + // memfs supplies storage, while Workers keeps timestamps unchanged on writes + // and caps every file at 128 MiB. Configure nodes at this owned boundary so + // descriptors, FileHandles and streams cannot bypass these rules. + const core = (volume as any)._core; + let nextFd = 3; + core.newFdNumber = () => core.releasedFds.pop() ?? nextFd++; + const createNode = core.createNode.bind(core); + core.createNode = (...args: any[]) => { + const node = createNode(...args); + node.mtime = node.btime = new Date(0); + node.touch = () => {}; + const write = node.write.bind(node), + truncate = node.truncate.bind(node), + set = node._setBuf.bind(node); + const limit = (size: number) => { + if (size > 134217728) + throw Object.assign(new Error("File size limit exceeded"), { + code: "EPERM", + }); + }; + node.write = ( + buffer: any, + offset = 0, + length = buffer.length, + position = 0, + ) => { + limit(position + length); + return write(buffer, offset, length, position); + }; + node.truncate = (length = 0) => { + limit(length); + return truncate(length); + }; + node._setBuf = (buffer: any) => { + limit(buffer.byteLength); + return set(buffer); + }; + return node; + }; + core.root.getNode().mtime = core.root.getNode().btime = new Date(0); volume.mkdirSync("/tmp"); const fs = createFsFromVolume(volume) as any; + function installDevices() { + fs.mkdirSync("/dev"); + for (const path of devices) { + fs.writeFileSync(path, ""); + const node = core.getResolvedLinkOrThrow(path, "open").getNode(); + node.mode = 0o20666; + const rejected = () => { + throw Object.assign(new Error("operation not permitted"), { + code: "EPERM", + }); + }; + node.write = + path === "/dev/full" || path === "/dev/random" + ? rejected + : (_buffer: any, _offset: number, length: number) => length; + node.truncate = + path === "/dev/full" || path === "/dev/random" ? rejected : () => {}; + node._setBuf = + path === "/dev/full" || path === "/dev/random" ? rejected : () => {}; + node.read = ( + buffer: Uint8Array, + offset = 0, + length = buffer.byteLength, + ) => { + if (path === "/dev/null") return 0; + const bytes = new Uint8Array( + buffer.buffer, + buffer.byteOffset + offset, + length, + ); + if (path === "/dev/random") + for (let n = 0; n < bytes.length; n += 65536) + bytes.set( + globalThis.crypto.getRandomValues( + new Uint8Array(Math.min(65536, bytes.length - n)), + ), + n, + ); + else bytes.fill(0); + return length; + }; + } + } + installDevices(); + for (let fd = 0; fd < 3; fd++) { + const path = "/tmp/.stdio-" + fd, + allocated = fs.openSync(path, "w+"); + const file = core.getFileByFdOrThrow(allocated, "stdio"); + delete core.fds[String(allocated)]; + file.fd = fd; + core.fds[String(fd)] = file; + fs.unlinkSync(path); + let pending = ""; + file.node.read = () => 0; + file.node.write = ( + buffer: Uint8Array, + offset = 0, + length = buffer.byteLength, + ) => { + pending += Buffer.from( + buffer.buffer, + buffer.byteOffset + offset, + length, + ).toString(); + const lines = pending.split("\n"); + pending = lines.pop()!; + for (const line of lines) (fd === 2 ? console.error : console.log)(line); + return length; + }; + } + nextFd = 3; + const closeSync = fs.closeSync.bind(fs), + close = fs.close.bind(fs); + fs.closeSync = (fd: any) => { + if (typeof fd === "number" && fd >= 0 && fd <= 2 && Number.isInteger(fd)) + return; + return closeSync(fd); + }; + fs.close = (fd: any, callback: any) => { + if (typeof fd === "number" && fd >= 0 && fd <= 2 && Number.isInteger(fd)) { + if (typeof callback !== "function") + throw new ERR_INVALID_ARG_TYPE("callback", "function", callback); + queueMicrotask(() => callback(null)); + return; + } + return close(fd, callback); + }; const readonlyDescriptors = new Set(); const readonlyInodes = new Set(); const denied = (syscall: string, path?: string) => @@ -171,7 +356,7 @@ function createTemporary() { ["string", "Buffer"], target, ); - const absolute = posix.resolve(posix.dirname(args[1]), target); + const absolute = pathOf(target); mount(absolute); args[0] = absolute; } else if (["copyFile", "cp", "link"].includes(base)) { @@ -213,11 +398,19 @@ function createTemporary() { } throw error; } + if ( + base === "read" && + typeof args[2] === "number" && + args[3] === undefined && + args[1]?.byteLength !== undefined + ) + args[3] = args[1].byteLength - args[2]; if (base === "close") readonlyDescriptors.delete(args[0]); return fs[name](...args); }; } - api.openSync = (path: any, flags?: any, mode?: any) => { + api.openSync = (path: any, flags: any = "r", mode?: any) => { + if (typeof flags === "number" && !(flags & 3)) flags &= ~512; const entry = openPath(path, flags); const fd = fs.openSync(entry.path, flags, mode); if (entry.readonly) readonlyDescriptors.add(fd); @@ -258,9 +451,21 @@ function createTemporary() { }; api.promises = { ...fs.promises, - open: async (path: any, flags?: any, mode?: any) => { + open: async (path: any, flags: any = "r", mode?: any) => { + if (typeof flags === "number" && !(flags & 3)) flags &= ~512; const entry = openPath(path, flags); const handle = await fs.promises.open(entry.path, flags, mode); + handle.readFile = async (options: any = {}) => { + const encoding = + typeof options === "string" ? options : options?.encoding; + const size = (await handle.stat()).size; + const buffer = Buffer.alloc(size); + const { bytesRead } = await handle.read(buffer, 0, size, 0); + core.getFileByFdOrThrow(handle.fd, "readFile").position = size; + return encoding + ? buffer.subarray(0, bytesRead).toString(encoding) + : buffer.subarray(0, bytesRead); + }; for (const name of [ "write", "writev", @@ -301,6 +506,15 @@ function createTemporary() { ), ); return { + descriptorFlags(fd: number) { + return core.getFileByFdOrThrow(fd, "write").flags; + }, + readDescriptor(fd: number) { + const file = core.getFileByFdOrThrow(fd, "read"); + const data = Buffer.from(file.node.getBuffer()); + file.position = data.length; + return data; + }, api, statEmbedded(path: any, options?: any) { const normalized = pathOf(path); @@ -312,16 +526,17 @@ function createTemporary() { const path = pathOf(value); const target = follow ? resolved(path) : path; return ( + target === "/dev" || !temporaryPath(target) || - (follow && - fs.existsSync(target) && - readonlyInodes.has(fs.statSync(target).ino)) + (fs.existsSync(target) && + readonlyInodes.has((follow ? fs.statSync : fs.lstatSync)(target).ino)) ); }, clear() { for (const name of fs.readdirSync("/")) fs.rmSync(`/${name}`, { recursive: true, force: true }); fs.mkdirSync("/tmp"); + installDevices(); readonlyDescriptors.clear(); readonlyInodes.clear(); }, @@ -349,7 +564,7 @@ export function clearTemporaryFiles() { active().clear(); } export function runWithTemporaryFiles(callback: () => T): T { - return contexts.run(createTemporary(), callback); + return runWithDirectory(() => contexts.run(createTemporary(), callback)); } registerRequestScope(runWithTemporaryFiles); @@ -361,3 +576,23 @@ export function readonlyFile(value: any, follow = true): boolean { export function embeddedStat(path: any, options?: any): any { return active().statEmbedded(path, options); } + +registerDirectoryValidator((path) => { + const normalized = pathOf(path); + const stat = active().statEmbedded(normalized); + if (!stat.isDirectory()) + throw Object.assign(new Error("not a directory"), { + code: "ENOTDIR", + syscall: "chdir", + path: normalized, + }); + return normalized; +}); + +export function readDescriptor(fd: number): Buffer { + return active().readDescriptor(fd); +} + +export function descriptorFlags(fd: number): number { + return active().descriptorFlags(fd); +} diff --git a/src/compat/process/directory.ts b/src/compat/process/directory.ts new file mode 100644 index 00000000..ce9cc23a --- /dev/null +++ b/src/compat/process/directory.ts @@ -0,0 +1,35 @@ +import { AsyncLocalStorage } from "../async-hooks/index.js"; +import { registerRequestScope } from "../../shared/request-scope.js"; +const contexts = new AsyncLocalStorage<{ path: string }>(); +const fallback = { path: "/bundle" }; +const active = () => contexts.getStore() ?? fallback; +export const currentDirectory = () => active().path; +let validateDirectory = (path: string) => { + const normalized = + new URL(path, "file://" + currentDirectory() + "/").pathname.replace( + /\/$/, + "", + ) || "/"; + if (!["/", "/tmp", "/bundle", "/dev"].includes(normalized)) + throw Object.assign(new Error("no such file or directory"), { + code: "ENOENT", + syscall: "chdir", + path: normalized, + }); + return normalized; +}; +export function registerDirectoryValidator(validate: (path: string) => string) { + validateDirectory = validate; +} +export function changeDirectory(path: unknown) { + if (typeof path !== "string") + throw Object.assign( + new TypeError('The "directory" argument must be of type string.'), + { code: "ERR_INVALID_ARG_TYPE" }, + ); + active().path = validateDirectory(path); +} +export function runWithDirectory(callback: () => T): T { + return contexts.run({ path: "/bundle" }, callback); +} +registerRequestScope(runWithDirectory); diff --git a/src/compat/process/index.ts b/src/compat/process/index.ts index 63afd2e9..1fd88af7 100644 --- a/src/compat/process/index.ts +++ b/src/compat/process/index.ts @@ -1,3 +1,8 @@ +import { + currentDirectory as cwd, + changeDirectory as chdir, +} from "./directory.js"; +export { cwd, chdir }; import * as metadata from "./metadata.js"; export { arch, @@ -181,6 +186,8 @@ export const hrtime = Object.assign( ); Object.assign(process, { + cwd, + chdir, hrtime, ...metadata, on, diff --git a/src/compat/zlib/native-zlib.wasm b/src/compat/zlib/native-zlib.wasm old mode 100755 new mode 100644 diff --git a/vitest.config.ts b/vitest.config.ts index e43e97b5..b4e0994a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,29 @@ +import { transform as transformFixture } from "esbuild"; import { lowerAsync } from "./src/tooling/async.js"; import { readFile } from "node:fs/promises"; import { defineConfig } from "vitest/config"; export default defineConfig({ plugins: [ + { + name: "test-upstream-resource-management", + enforce: "pre", + async transform(code, id) { + if ( + !id.includes("/compat/fs/upstream-") || + !id.endsWith(".fixture.mjs") + ) + return; + // Lower await-using before the request-context async transform, as the + // production ES2022 build does. Keep upstream disposal assertions intact. + return ( + await transformFixture(code, { + loader: "js", + target: "es2022", + format: "esm", + }) + ).code; + }, + }, { name: "test-async-context", enforce: "post", @@ -48,6 +69,8 @@ export default defineConfig({ "src/compat/net/sockets.spec.ts", "src/compat/tls/upgrade.spec.ts", "src/compat/fs/writable.spec.ts", + "src/compat/fs/glob.spec.ts", + "src/compat/fs/directory.spec.ts", "src/shared/http-wire/index.spec.ts", ], }, From 622394fc77d8f56dade3800bb44bb2d4eb6a5322 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 01:44:42 +0900 Subject: [PATCH 03/30] feat: align digest and key derivation contracts with Workers --- .../node-compat/completion-report.mjs | 2 +- .../node-compat/native-measurements.mjs | 3 + docs/compatibility.md | 2 +- src/compat/crypto/LICENSE-3RD-PARTY.md | 9 + src/compat/crypto/hash-handle.ts | 108 ++++ src/compat/crypto/hash-types.spec.ts | 47 ++ src/compat/crypto/hash.spec.ts | 48 ++ src/compat/crypto/hash.ts | 325 ++++++++++ src/compat/crypto/hkdf.ts | 34 +- src/compat/crypto/index.ts | 39 +- src/compat/crypto/metadata.ts | 4 + src/compat/crypto/native-crypto.build.json | 4 +- src/compat/crypto/native-crypto.c | 19 +- src/compat/crypto/native-crypto.ts | 80 ++- src/compat/crypto/native-crypto.wasm | Bin 2293850 -> 2294746 bytes src/compat/crypto/pbkdf2-handle.ts | 35 ++ src/compat/crypto/pbkdf2.ts | 127 ++++ src/compat/crypto/symmetric.ts | 40 -- src/compat/crypto/upstream-hash.fixture.mjs | 342 +++++++++++ src/compat/crypto/upstream-hkdf.fixture.mjs | 283 +++++++++ src/compat/crypto/upstream-hmac.fixture.mjs | 558 ++++++++++++++++++ src/compat/crypto/upstream-pbkdf2.fixture.mjs | 349 +++++++++++ src/compat/crypto/validation.ts | 25 +- src/compat/expansion/README.md | 15 +- src/compat/expansion/index.spec.mjs | 164 +++++ vitest.config.ts | 2 + 26 files changed, 2572 insertions(+), 92 deletions(-) create mode 100644 src/compat/crypto/hash-handle.ts create mode 100644 src/compat/crypto/hash-types.spec.ts create mode 100644 src/compat/crypto/hash.spec.ts create mode 100644 src/compat/crypto/hash.ts create mode 100644 src/compat/crypto/pbkdf2-handle.ts create mode 100644 src/compat/crypto/pbkdf2.ts create mode 100644 src/compat/crypto/upstream-hash.fixture.mjs create mode 100644 src/compat/crypto/upstream-hkdf.fixture.mjs create mode 100644 src/compat/crypto/upstream-hmac.fixture.mjs create mode 100644 src/compat/crypto/upstream-pbkdf2.fixture.mjs diff --git a/demo/tests/experiments/node-compat/completion-report.mjs b/demo/tests/experiments/node-compat/completion-report.mjs index 4953e204..024c173f 100644 --- a/demo/tests/experiments/node-compat/completion-report.mjs +++ b/demo/tests/experiments/node-compat/completion-report.mjs @@ -23,7 +23,7 @@ const assessments = [ modules: ["crypto", "zlib"], priority: 2, category: "implementation-gap", - next: "Inventory algorithms, key/codec classes, synchronous and streaming behavior. Evaluate JS/Wasm implementations against all pinned operations; random/hash/HKDF/scrypt, owned secret/asymmetric keys, RSA/EC/Ed25519 signatures, prime generation/checking X509Certificate parsing/fields/name checks/signature checks and cipher metadata lookup now supplement dependency polyfills. Gzip/deflate now use zlib Wasm; all codec streams use the pinned Workers lifecycle, with option, reset, close, callback and output-limit contracts. Pinned Workers X509 key-check restrictions are documented. Additional key-generation options, remaining algorithms and complete codec contracts remain open. No technical-impossibility claim has been established.", + next: "Inventory algorithms, key/codec classes, synchronous and streaming behavior. Evaluate JS/Wasm implementations against all pinned operations; random/Hash/HMAC/PBKDF2/HKDF/scrypt, owned secret/asymmetric keys, RSA/EC/Ed25519 signatures, prime generation/checking X509Certificate parsing/fields/name checks/signature checks and cipher metadata lookup now supplement dependency polyfills. Gzip/deflate now use zlib Wasm; all codec streams use the pinned Workers lifecycle, with option, reset, close, callback and output-limit contracts. Pinned Workers X509 key-check restrictions are documented. The pinned 17 digest names, copying, finalization and KDF length limits have shared contracts; Float16Array input-brand coverage depends on engine availability recorded in native measurements. Additional key-generation options, remaining algorithms and complete codec contracts remain open. No technical-impossibility claim has been established.", evidence: [ "surface-gaps.json", "expansion-contracts.probe.mjs", diff --git a/demo/tests/experiments/node-compat/native-measurements.mjs b/demo/tests/experiments/node-compat/native-measurements.mjs index c3d17fad..1cfcabc0 100644 --- a/demo/tests/experiments/node-compat/native-measurements.mjs +++ b/demo/tests/experiments/node-compat/native-measurements.mjs @@ -26,6 +26,9 @@ export async function measureOperation(name, operation, warmOperation) { try { const value = await operation(); measurements[name] = { + hostCapabilities: { + float16Array: typeof globalThis.Float16Array === "function", + }, operationMs: performance.now() - started, wasm, wasmMemoryBytes: memories.reduce((n, m) => n + m.buffer.byteLength, 0), diff --git a/docs/compatibility.md b/docs/compatibility.md index 242ef4e1..6251292b 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -34,7 +34,7 @@ establish equivalent methods, validation, state, scheduling or engine behavior. | fs / fs/promises | Read-only embedded files plus request-local in-memory `/tmp`, with writes, descriptors and FileHandle operations. Watch is unsupported, as in Workers. Full filesystem semantics remain under audit. | | module | Limited builtin registry; createRequire loads VFS JSON only. Executable modules must be bundled; arbitrary dynamic resolution is unsupported. | | process, console, util / util/types / sys | Incomplete exports or behavior. Engine introspection, call sites and host-object detection are not equivalent to Workers. | -| crypto and zlib | Selected profiles cover random/hash/HKDF/scrypt, secret/asymmetric keys, signatures, primes and Brotli/Zstd streaming. X509Certificate parsing, fields and checks are implemented with the pinned Workers limits; additional algorithms and full crypto/codec parity remain gaps. | +| crypto and zlib | Selected profiles cover random/Hash/HMAC/PBKDF2/HKDF/scrypt, secret/asymmetric keys, signatures, primes and Brotli/Zstd streaming. X509Certificate parsing, fields and checks are implemented with the pinned Workers limits; additional algorithms and full crypto/codec parity remain gaps. | | TLS and HTTP internals | TLS and HTTP internal imports resolve. Direct Node TLS exchange and hostname rejection are tested on native transports. Existing native sockets upgrade with authenticated TLS before application writes. TLS 1.2/1.3 upgrades are tested on both OSes; custom Duplex wrapping and complete class/state contracts remain gaps. | | Streams | An owned implementation supplies byte streams and the tested Workers tee cancellation and pipeTo abort behavior on every host. Whole-module acceptance remains open. | | async_hooks | Build-transformed async functions retain scoped stores. Untransformed native async callbacks reject; arbitrary dynamic code and request-origin isolation are not fully supported. | diff --git a/src/compat/crypto/LICENSE-3RD-PARTY.md b/src/compat/crypto/LICENSE-3RD-PARTY.md index 7530a8e2..0924f2c0 100644 --- a/src/compat/crypto/LICENSE-3RD-PARTY.md +++ b/src/compat/crypto/LICENSE-3RD-PARTY.md @@ -17,3 +17,12 @@ pinned workerd module; internal helpers are not added to the public facade. X509 certificate wrappers and safe name rendering are adapted from `src/node/internal/crypto_x509.ts` and `src/workerd/api/crypto/x509.c++` at workerd commit `5358e3d337336eaf696a48bc1563d808b10feba5` (Apache-2.0). + +Hash/HMAC and PBKDF2 frontends and digest/KDF test fixtures are adapted from +`src/node/internal/crypto_hash.ts`, `src/node/internal/crypto_pbkdf2.ts` and +`src/workerd/api/node/tests/crypto_{hash,hmac,pbkdf2,hkdf}-test.js` at the same +pinned workerd commit. The Apache-2.0 and Node.js MIT headers are retained. +Imports use the owned adapters. Latin-1 fixture expectations written as literal +caret notation upstream are decoded using independently verified digest bytes; +comments identify those adaptations. Float16 input-brand cases require the +engine's intrinsic; native evidence records its availability. diff --git a/src/compat/crypto/hash-handle.ts b/src/compat/crypto/hash-handle.ts new file mode 100644 index 00000000..7190955a --- /dev/null +++ b/src/compat/crypto/hash-handle.ts @@ -0,0 +1,108 @@ +import { validateCryptoLength } from "./validation.js"; +import { secretMaterial, KeyObject } from "./keys.js"; +import { Buffer } from "node:buffer"; +import { NativeDigest, NativeHmac } from "./native-crypto.js"; +export const hashNames = [ + "md4", + "md5", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + "md5-sha1", + "RSA-MD5", + "RSA-SHA1", + "RSA-SHA224", + "RSA-SHA256", + "RSA-SHA384", + "RSA-SHA512", + "DSA-SHA", + "DSA-SHA1", + "ecdsa-with-SHA1", +]; +export const aliases: Record = Object.assign( + Object.create(null), + Object.fromEntries( + hashNames.map((name) => [ + name.toLowerCase(), + name + .toLowerCase() + .replace(/^rsa-/, "") + .replace(/^(dsa-sha1?|ecdsa-with-sha1)$/, "sha1"), + ]), + ), +); +class HashHandle { + private hash: NativeDigest; + private cached?: Buffer; + constructor(algorithm: string | NativeDigest, length?: number) { + if (algorithm instanceof NativeDigest) this.hash = algorithm; + else { + const normalized = aliases[algorithm.toLowerCase()]; + if (!normalized) throw new Error("Digest method not supported"); + this.hash = new NativeDigest(normalized, "Hash"); + } + if (length !== undefined && length !== this.hash.size) { + this.hash.dispose(); + throw new Error("invalid digest size"); + } + } + update(data: ArrayBufferView) { + if (this.cached) + throw new DOMException( + "Hash context has already been finalized.", + "OperationError", + ); + validateCryptoLength(data.byteLength, "data is too long"); + this.hash.update( + new Uint8Array(data.buffer, data.byteOffset, data.byteLength), + ); + return 1; + } + digest() { + this.cached ??= Buffer.from(this.hash.final()); + return Buffer.from(this.cached); + } + copy(length?: number) { + if (this.cached) + throw new DOMException( + "Hash context has already been finalized.", + "OperationError", + ); + return new HashHandle(this.hash.copy(), length); + } +} +class HmacHandle { + private hmac: NativeHmac; + private cached?: Buffer; + constructor(algorithm: string, key: any) { + if (typeof CryptoKey !== "undefined" && key instanceof CryptoKey) + key = KeyObject.from(key); + key = secretMaterial(key); + const data = ArrayBuffer.isView(key) + ? new Uint8Array(key.buffer, key.byteOffset, key.byteLength) + : new Uint8Array(key); + validateCryptoLength(data.byteLength, "key is too long"); + const normalized = aliases[algorithm.toLowerCase()]; + if (!normalized) throw new Error("Digest method not supported"); + this.hmac = new NativeHmac(normalized, data); + } + update(data: ArrayBufferView) { + if (this.cached) + throw new DOMException( + "HMAC context has already been finalized.", + "OperationError", + ); + validateCryptoLength(data.byteLength, "data is too long"); + this.hmac.update( + new Uint8Array(data.buffer, data.byteOffset, data.byteLength), + ); + return 1; + } + digest() { + this.cached ??= Buffer.from(this.hmac.final()); + return Buffer.from(this.cached); + } +} +export default { HashHandle, HmacHandle }; diff --git a/src/compat/crypto/hash-types.spec.ts b/src/compat/crypto/hash-types.spec.ts new file mode 100644 index 00000000..8a328fb8 --- /dev/null +++ b/src/compat/crypto/hash-types.spec.ts @@ -0,0 +1,47 @@ +import { expect, test } from "vitest"; +import { Hash, Hmac, createHash, createHmac } from "./hash.js"; +import { pbkdf2Sync } from "./pbkdf2.js"; +import { Buffer } from "node:buffer"; + +test("digest and KDF declarations describe ordinary public API usage", () => { + const callableHash: Hash = Hash("sha256"); + const callableHmac: Hmac = Hmac("sha1", "key"); + const hash: string = callableHash.update("abc").digest("hex"); + const hmac: string = callableHmac.update("data").digest("hex"); + const binary: Buffer = createHash("sha256").update("abc").digest(); + const explicitBinary: Buffer = createHmac("sha1", "key") + .update("data") + .digest("buffer"); + const key: Buffer = pbkdf2Sync( + new Uint8Array([1]), + new ArrayBuffer(1), + 1, + 8, + "sha256", + ); + const constructorHash: string = new Hash("sha256") + .update("abc") + .copy() + .digest("hex"); + const constructorHmac: string = new Hmac("sha1", "key") + .update("data") + .digest("hex"); + + // This closure is never executed: the declaration must reject a method that + // the Hmac prototype does not expose, without invoking it at runtime. + const invalidHmacCopy = () => { + // @ts-expect-error Hmac has no copy method. + return callableHmac.copy(); + }; + void invalidHmacCopy; + + expect(hash).toBe( + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + ); + expect(hmac).toBe("104152c5bfdca07bc633eebd46199f0255c9f49d"); + expect(binary.toString("hex")).toBe(hash); + expect(explicitBinary.toString("hex")).toBe(hmac); + expect(constructorHash).toBe(hash); + expect(constructorHmac).toBe(hmac); + expect(key.length).toBe(8); +}); diff --git a/src/compat/crypto/hash.spec.ts b/src/compat/crypto/hash.spec.ts new file mode 100644 index 00000000..9d07ae74 --- /dev/null +++ b/src/compat/crypto/hash.spec.ts @@ -0,0 +1,48 @@ +import { test, expect } from "vitest"; +import { createHash } from "./hash.js"; +import { getHashes } from "./metadata.js"; +// @ts-expect-error Upstream JavaScript fixture has no declarations. +import * as cases from "./upstream-hash.fixture.mjs"; +for (const [name, fixture] of Object.entries(cases)) + test(`pinned Workers hash: ${name}`, () => + (fixture as { test: () => unknown }).test()); +test("all pinned digest names support incremental and copied input", () => { + for (const name of getHashes()) { + const hash = createHash(name).update("a"); + const copy = hash.copy(); + expect(hash.update("bc").digest("hex")).toBe( + createHash(name).update("abc").digest("hex"), + ); + expect(copy.update("d").digest("hex")).toBe( + createHash(name).update("ad").digest("hex"), + ); + } +}); + +// @ts-expect-error Upstream JavaScript fixture has no declarations. +import * as hmacCases from "./upstream-hmac.fixture.mjs"; +for (const [name, fixture] of Object.entries(hmacCases)) + test(`pinned Workers hmac: ${name}`, () => + (fixture as { test: () => unknown }).test()); + +// @ts-expect-error Upstream JavaScript fixture has no declarations. +import * as pbkdfCases from "./upstream-pbkdf2.fixture.mjs"; +for (const [name, fixture] of Object.entries(pbkdfCases)) + test(`pinned Workers pbkdf2: ${name}`, () => + (fixture as { test: () => unknown }).test()); + +// @ts-expect-error Upstream JavaScript fixture has no declarations. +import * as hkdfCases from "./upstream-hkdf.fixture.mjs"; +for (const [name, fixture] of Object.entries(hkdfCases)) + test(`pinned Workers hkdf: ${name}`, () => + (fixture as { test: () => unknown }).test()); + +import { validateCryptoLength } from "./validation.js"; +test("native crypto length guard rejects before narrowing to signed int", () => { + for (const length of [0, 0x7fffffff]) + expect(() => validateCryptoLength(length, "too large")).not.toThrow(); + for (const length of [0x80000000, 0x100000000]) + expect(() => validateCryptoLength(length, "too large")).toThrow( + new RangeError("too large"), + ); +}); diff --git a/src/compat/crypto/hash.ts b/src/compat/crypto/hash.ts new file mode 100644 index 00000000..4ae46473 --- /dev/null +++ b/src/compat/crypto/hash.ts @@ -0,0 +1,325 @@ +// @ts-nocheck +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Ported from workerd@5358e3d337336eaf696a48bc1563d808b10feba5 crypto_hash.ts. +import cryptoImpl from "./hash-handle.js"; +import { Buffer } from "node:buffer"; +import { KeyObject } from "./keys.js"; +import { ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE } from "./sign-errors.js"; +const isCryptoKey = (value) => + typeof CryptoKey !== "undefined" && value instanceof CryptoKey; +import { isAnyBuffer as isAnyArrayBuffer } from "./validation.js"; +function getStringOption(options, key) { + let value; + if (options && (value = options[key]) != null) + validateString(value, `options.${key}`); + return value; +} +function getArrayBufferOrView(value, name, encoding) { + return typeof value === "string" + ? Buffer.from(value, encoding === "buffer" ? undefined : encoding) + : value; +} + +import { Transform } from "../stream/transform.js"; +import type { Transform as NodeTransform, TransformOptions } from "node:stream"; +import { validateString, validateUint32 } from "../net/validators.js"; +import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +import { NodeError } from "../net/error-base.js"; +const kHandle = Symbol("kHandle"), + kState = Symbol("kState"), + kFinalized = Symbol("kFinalized"); +const isArrayBufferView = ArrayBuffer.isView; +class ERR_CRYPTO_HASH_FINALIZED extends NodeError { + constructor() { + super("ERR_CRYPTO_HASH_FINALIZED", "Digest already called"); + } +} +class ERR_CRYPTO_HASH_UPDATE_FAILED extends NodeError { + constructor() { + super("ERR_CRYPTO_HASH_UPDATE_FAILED", "Hash update failed"); + } +} +export interface HashOptions extends TransformOptions { + outputLength?: number; +} + +interface _kState { + [kFinalized]: boolean; +} + +declare class Hash extends NodeTransform { + [kHandle]: InstanceType; + [kState]: _kState; + + constructor( + algorithm: string | InstanceType, + options?: HashOptions, + ); + + copy(options?: HashOptions): Hash; + update(data: string | Buffer | ArrayBufferView, encoding?: string): this; + digest(): Buffer; + digest(outputEncoding: "buffer"): Buffer; + digest(outputEncoding: BufferEncoding): string; + digest(outputEncoding?: string): Buffer | string; +} + +// These helper functions are needed because the constructors can +// use new, in which case V8 cannot inline the recursive constructor call +export function createHash(algorithm: string, options?: HashOptions): Hash { + return new Hash(algorithm, options); +} + +function Hash( + this: unknown, + algorithm: string | InstanceType, + options?: HashOptions, +): Hash { + if (!(this instanceof Hash)) { + return new Hash(algorithm, options); + } + + const xofLen = typeof options === "object" ? options.outputLength : undefined; + if (xofLen !== undefined) validateUint32(xofLen, "options.outputLength"); + if (algorithm instanceof cryptoImpl.HashHandle) { + this[kHandle] = algorithm.copy(xofLen as number); + } else { + validateString(algorithm, "algorithm"); + this[kHandle] = new cryptoImpl.HashHandle(algorithm, xofLen as number); + } + this[kState] = { + [kFinalized]: false, + }; + + Transform.call(this, options); + return this; +} + +Object.setPrototypeOf(Hash.prototype, Transform.prototype); +Object.setPrototypeOf(Hash, Transform); + +Hash.prototype.copy = function (this: Hash, options?: HashOptions): Hash { + const state = this[kState]; + if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED(); + + return new Hash(this[kHandle], options); +}; + +Hash.prototype._transform = function ( + this: Hash | Hmac, + chunk: string | Buffer | ArrayBufferView, + encoding: string, + callback: TransformCallback, +): void { + if (typeof chunk === "string") { + chunk = Buffer.from(chunk, encoding); + } + this[kHandle].update(chunk); + callback(); +}; + +Hash.prototype._flush = function ( + this: Hash | Hmac, + callback: TransformCallback, +): void { + this.push(Buffer.from(this[kHandle].digest())); + callback(); +}; + +Hash.prototype.update = function ( + this: Hash | Hmac, + data: string | Buffer | ArrayBufferView, + encoding?: string, +): Hash | Hmac { + encoding ??= "utf8"; + if (encoding === "buffer") { + encoding = undefined; + } + + const state = this[kState]; + if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED(); + + if (typeof data === "string") { + data = Buffer.from(data, encoding); + } else if (!isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE( + "data", + ["string", "Buffer", "TypedArray", "DataView"], + data, + ); + } + + if (!this[kHandle].update(data)) throw new ERR_CRYPTO_HASH_UPDATE_FAILED(); + return this; +}; + +Hash.prototype.digest = function ( + this: Hash, + outputEncoding?: string, +): Buffer | string { + const state = this[kState]; + if (state[kFinalized]) throw new ERR_CRYPTO_HASH_FINALIZED(); + + // Explicit conversion for backward compatibility. + const ret = Buffer.from(this[kHandle].digest()); + state[kFinalized] = true; + if (outputEncoding !== undefined && outputEncoding !== "buffer") { + return ret.toString(outputEncoding); + } else { + return ret; + } +}; + +export function hash( + algorithm: string, + data: string | ArrayBufferView, + outputEncoding: string = "hex", +): string | Buffer { + validateString(algorithm, "algorithm"); + validateString(outputEncoding, "outputEncoding"); + + if (typeof data === "string") { + const hash = createHash(algorithm); + hash.update(data, "utf8"); + return hash.digest(outputEncoding); + } else if (isArrayBufferView(data)) { + const hash = createHash(algorithm); + hash.update(data, "utf8"); + return hash.digest(outputEncoding); + } + + throw new ERR_INVALID_ARG_TYPE( + "data", + ["string", "Buffer", "TypedArray", "DataView"], + data, + ); +} + +declare class Hmac extends NodeTransform { + [kHandle]: InstanceType; + [kState]: _kState; + constructor(hmac: string, key: unknown, options?: TransformOptions); + update(data: string | Buffer | ArrayBufferView, encoding?: string): this; + digest(): Buffer; + digest(outputEncoding: "buffer"): Buffer; + digest(outputEncoding: BufferEncoding): string; + digest(outputEncoding?: string): Buffer | string; +} + +export function createHmac( + hmac: string, + key: unknown, + options?: TransformOptions, +): Hmac { + return new Hmac(hmac, key, options); +} + +function Hmac( + this: unknown, + hmac: string, + key: unknown, + options?: TransformOptions, +): Hmac { + if (!(this instanceof Hmac)) { + return new Hmac(hmac, key, options); + } + validateString(hmac, "hmac"); + const encoding = getStringOption(options, "encoding"); + + if (key instanceof KeyObject) { + if (key.type !== "secret") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + } + this[kHandle] = new cryptoImpl.HmacHandle(hmac, key); + } else if (isCryptoKey(key)) { + if (key.type !== "secret") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + } + this[kHandle] = new cryptoImpl.HmacHandle(hmac, key); + } else if ( + typeof key !== "string" && + !isArrayBufferView(key) && + !isAnyArrayBuffer(key) + ) { + throw new ERR_INVALID_ARG_TYPE( + "key", + [ + "ArrayBuffer", + "Buffer", + "ArrayBufferView", + "string", + "KeyObject", + "CryptoKey", + ], + key, + ); + } else { + this[kHandle] = new cryptoImpl.HmacHandle( + hmac, + getArrayBufferOrView(key, "key", encoding), + ); + } + + this[kState] = { + [kFinalized]: false, + }; + Transform.call(this, options); + return this; +} +Object.setPrototypeOf(Hmac.prototype, Transform.prototype); +Object.setPrototypeOf(Hmac, Transform); + +// eslint-disable-next-line @typescript-eslint/unbound-method +Hmac.prototype.update = Hash.prototype.update; + +Hmac.prototype.digest = function ( + this: Hmac, + outputEncoding?: string, +): Buffer | string { + const state = this[kState]; + if (state[kFinalized]) { + return !outputEncoding || outputEncoding === "buffer" + ? Buffer.from("") + : ""; + } + + // Explicit conversion for backward compatibility. + const ret = Buffer.from(this[kHandle].digest()); + state[kFinalized] = true; + if (outputEncoding !== undefined && outputEncoding !== "buffer") { + return ret.toString(outputEncoding); + } else { + return ret; + } +}; + +// eslint-disable-next-line @typescript-eslint/unbound-method +Hmac.prototype._flush = Hash.prototype._flush; +// eslint-disable-next-line @typescript-eslint/unbound-method +Hmac.prototype._transform = Hash.prototype._transform; + +export { Hash, Hmac }; diff --git a/src/compat/crypto/hkdf.ts b/src/compat/crypto/hkdf.ts index 5f7ed0c4..a57c615a 100644 --- a/src/compat/crypto/hkdf.ts +++ b/src/compat/crypto/hkdf.ts @@ -1,9 +1,9 @@ -import { secretMaterial } from "./keys.js"; +import { KeyObject } from "./keys.js"; +import { createHmac } from "./hash.js"; +import { aliases } from "./hash-handle.js"; // RFC 5869 extract-and-expand, using the existing HMAC implementation. import { Buffer } from "node:buffer"; -// @ts-expect-error dependency has no declarations -import original from "crypto-browserify/index.js"; -import { bytes, validateInteger } from "./validation.js"; +import { bytes, validateInteger, validateCryptoLength } from "./validation.js"; import { validateString, validateFunction } from "../net/validators.js"; import { ERR_OUT_OF_RANGE } from "../net/errors.js"; function parameters( @@ -13,8 +13,9 @@ function parameters( info: unknown, length: number, ) { + if (key instanceof KeyObject) key = key.export(); validateString(digest, "digest"); - const ikm = bytes(secretMaterial(key), "ikm"), + const ikm = bytes(key, "ikm"), s = bytes(salt, "salt"), i = bytes(info, "info"); validateInteger(length, "length", 0, 2147483647); @@ -33,21 +34,26 @@ function derive({ i, length, }: ReturnType): ArrayBuffer { - const prk = original.createHmac(digest, s).update(ikm).digest(); - if (length > 255 * prk.length) - throw Object.assign(new RangeError("Invalid key length"), { - code: "ERR_CRYPTO_INVALID_KEYLEN", - }); - const output = Buffer.alloc(length); - let previous = Buffer.alloc(0); + if (!aliases[digest.toLowerCase()]) + throw new TypeError("Invalid Hkdf digest: " + digest); + validateCryptoLength(i.byteLength, "Hkdf failed: info is too large"); + validateCryptoLength(s.byteLength, "Hkdf failed: salt is too large"); + validateCryptoLength(ikm.byteLength, "Hkdf failed: key is too large"); + const prk = createHmac(digest, s).update(ikm).digest(); + + let previous: Buffer = Buffer.alloc(0); try { + if (length > 255 * prk.length) + throw new RangeError("Invalid Hkdf key length"); + const output = Buffer.alloc(length); for (let offset = 0, counter = 1; offset < length; counter++) { - previous = original - .createHmac(digest, prk) + const next = createHmac(digest, prk) .update(previous) .update(i) .update(Buffer.from([counter])) .digest(); + previous.fill(0); + previous = next; const size = Math.min(previous.length, length - offset); output.set(previous.subarray(0, size), offset); offset += size; diff --git a/src/compat/crypto/index.ts b/src/compat/crypto/index.ts index 942e2dad..3b4dbb0c 100644 --- a/src/compat/crypto/index.ts +++ b/src/compat/crypto/index.ts @@ -1,3 +1,7 @@ +import { pbkdf2, pbkdf2Sync } from "./pbkdf2.js"; +export { pbkdf2, pbkdf2Sync }; +import { Hash, Hmac, createHmac, createHash, hash } from "./hash.js"; +export { Hash, Hmac, createHmac, createHash, hash }; import { X509Certificate } from "./x509.js"; export { X509Certificate }; import { Certificate } from "./certificate.js"; @@ -36,19 +40,12 @@ import original from "crypto-browserify/index.js"; import { hkdf, hkdfSync } from "./hkdf.js"; import { randomInt, randomUUID } from "./random.js"; import { scrypt, scryptSync } from "./scrypt.js"; -import { validateString } from "../net/validators.js"; -import { bytes } from "./validation.js"; export { hkdf, hkdfSync, randomInt, randomUUID, scrypt, scryptSync }; export const { prng, pseudoRandomBytes, rng, randomBytes, - Hash, - createHash, - getHashes, - pbkdf2, - pbkdf2Sync, Cipher, createCipher, Decipher, @@ -66,21 +63,12 @@ export const { constants, } = original; import { - Hmac, Cipheriv, Decipheriv, - createHmac, createCipheriv, createDecipheriv, } from "./symmetric.js"; -export { - Hmac, - Cipheriv, - Decipheriv, - createHmac, - createCipheriv, - createDecipheriv, -}; +export { Cipheriv, Decipheriv, createCipheriv, createDecipheriv }; import { publicEncrypt, privateEncrypt, @@ -92,23 +80,14 @@ export const webcrypto = globalThis.crypto; export const subtle = webcrypto.subtle; export const getRandomValues = webcrypto.getRandomValues.bind(webcrypto); export const CryptoKey = globalThis.CryptoKey; -export function hash( - algorithm: string, - data: unknown, - outputEncoding: string = "hex", -) { - validateString(algorithm, "algorithm"); - validateString(outputEncoding, "outputEncoding"); - const result = original - .createHash(algorithm) - .update(bytes(data, "data")) - .digest(); - return outputEncoding === "buffer" ? result : result.toString(outputEncoding); -} export default { X509Certificate, Certificate, ...original, + pbkdf2, + pbkdf2Sync, + Hash, + createHash, ...metadata, ...asymmetric, ...signatures, diff --git a/src/compat/crypto/metadata.ts b/src/compat/crypto/metadata.ts index c9be3a2d..a43d25b4 100644 --- a/src/compat/crypto/metadata.ts +++ b/src/compat/crypto/metadata.ts @@ -1,3 +1,7 @@ +import { hashNames } from "./hash-handle.js"; +export function getHashes() { + return [...hashNames]; +} // Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under Apache-2.0: https://opensource.org/licenses/Apache-2.0 // Adapted from workerd@5358e3d337336eaf696a48bc1563d808b10feba5. diff --git a/src/compat/crypto/native-crypto.build.json b/src/compat/crypto/native-crypto.build.json index fdd5bb62..b8bcb416 100644 --- a/src/compat/crypto/native-crypto.build.json +++ b/src/compat/crypto/native-crypto.build.json @@ -35,6 +35,6 @@ "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" ], "libraries": ["libssl.a", "libcrypto.a"], - "bindingSha256": "819bf16122da0df3c5ce7b6158b23700b8d42778fcb0e14f3a9e054d5f68831f", - "sha256": "b25197493fce8f3b40615654f877b3dec064d90e3cd1a34dc86695dd3bb68c50" + "bindingSha256": "af44c4b1f55853e3ca8f2431c299f830d2de32ca42b250e929835022e0597ab4", + "sha256": "0dd424c77484fc471212e8d6cf2006ca39fd13be728fd3f015bae844c71e549b" } diff --git a/src/compat/crypto/native-crypto.c b/src/compat/crypto/native-crypto.c index fd193da5..2c35de21 100644 --- a/src/compat/crypto/native-crypto.c +++ b/src/compat/crypto/native-crypto.c @@ -1,6 +1,7 @@ // OpenSSL bridge owned by cf-workers-native (MIT). #include #include +#include #include #include #include @@ -145,15 +146,31 @@ API int wk_prime_generate(int bits,int safe,const unsigned char *add,int add_len BN_clear_free(a);BN_clear_free(r);BN_clear_free(prime);BN_CTX_free(ctx);return ok; } API EVP_MD_CTX *wk_digest_new(const char *algorithm) { - ERR_clear_error();const EVP_MD *md=EVP_get_digestbyname(algorithm);EVP_MD_CTX *ctx=EVP_MD_CTX_new(); + ERR_clear_error();const EVP_MD *md=EVP_get_digestbyname(algorithm); + if(md && EVP_MD_type(md)==NID_md4){static int legacy_initialized=0;if(!legacy_initialized){OSSL_PROVIDER_load(NULL,"default");OSSL_PROVIDER_load(NULL,"legacy");legacy_initialized=1;}} + EVP_MD_CTX *ctx=EVP_MD_CTX_new(); if(!md || !ctx || EVP_DigestInit_ex(ctx,md,NULL)!=1){EVP_MD_CTX_free(ctx);return NULL;}return ctx; } +API EVP_MD_CTX *wk_digest_copy(EVP_MD_CTX *source) {EVP_MD_CTX *ctx=EVP_MD_CTX_new();if(!ctx || EVP_MD_CTX_copy_ex(ctx,source)!=1){EVP_MD_CTX_free(ctx);return NULL;}return ctx;} +API int wk_digest_size(EVP_MD_CTX *ctx) {return EVP_MD_CTX_get_size(ctx);} API void wk_digest_free(EVP_MD_CTX *ctx) {EVP_MD_CTX_free(ctx);} API int wk_digest_update(EVP_MD_CTX *ctx,const unsigned char *data,size_t length) {return EVP_DigestUpdate(ctx,data,length);} API int wk_digest_final(EVP_MD_CTX *ctx) { unsigned int length=EVP_MAX_MD_SIZE;clear_output();output=OPENSSL_malloc(length);if(!output)return 0; output_length=length;if(EVP_DigestFinal_ex(ctx,output,&length)!=1){clear_output();return 0;}output_length=length;return 1; } +API HMAC_CTX *wk_hmac_new(const char *algorithm,const unsigned char *key,int length) { + EVP_MD_CTX *check=wk_digest_new(algorithm);if(!check)return NULL;EVP_MD_CTX_free(check); + HMAC_CTX *ctx=HMAC_CTX_new();if(!ctx||HMAC_Init_ex(ctx,key,length,EVP_get_digestbyname(algorithm),NULL)!=1){HMAC_CTX_free(ctx);return NULL;}return ctx; +} +API void wk_hmac_free(HMAC_CTX *ctx){HMAC_CTX_free(ctx);} +API int wk_hmac_update(HMAC_CTX *ctx,const unsigned char *data,size_t length){return HMAC_Update(ctx,data,length);} +API int wk_hmac_final(HMAC_CTX *ctx){unsigned int length=EVP_MAX_MD_SIZE;clear_output();output=OPENSSL_malloc(length);if(!output)return 0;output_length=length;if(HMAC_Final(ctx,output,&length)!=1){clear_output();return 0;}output_length=length;return 1;} +API int wk_pbkdf2(const unsigned char *password,int password_length,const unsigned char *salt,int salt_length,int iterations,int length,const char *algorithm){ + EVP_MD_CTX *check=wk_digest_new(algorithm);if(!check)return 0;EVP_MD_CTX_free(check); + clear_output();output=OPENSSL_malloc(length?length:1);if(!output)return 0;output_length=length; + if(!PKCS5_PBKDF2_HMAC((const char*)password,password_length,salt,salt_length,iterations,EVP_get_digestbyname(algorithm),length,output)){clear_output();return 0;}return 1; +} API int wk_sign_hash(EVP_PKEY *key,const char *digest,const unsigned char *data,size_t length,int padding,int saltlen) { ERR_clear_error();EVP_PKEY_CTX *ctx=EVP_PKEY_CTX_new(key,NULL);if(!ctx)return 0; int ok=EVP_PKEY_sign_init(ctx)>0 && EVP_PKEY_CTX_set_signature_md(ctx,EVP_get_digestbyname(digest))>0; diff --git a/src/compat/crypto/native-crypto.ts b/src/compat/crypto/native-crypto.ts index ce2beb36..670ddbd2 100644 --- a/src/compat/crypto/native-crypto.ts +++ b/src/compat/crypto/native-crypto.ts @@ -389,11 +389,34 @@ export class NativeDigest { constructor( algorithm: string, private context = "Signing", + pointer?: number, ) { - this.pointer = inputs([algorithm], (api, [p]) => api.wk_digest_new(p)); + this.pointer = + pointer ?? inputs([algorithm], (api, [p]) => api.wk_digest_new(p)); if (!this.pointer) throw new Error(`Unknown digest: ${algorithm}`); digestReleased.register(this, this.pointer, this); } + dispose() { + if (this.pointer) { + library().wk_digest_free(this.pointer); + this.pointer = 0; + digestReleased.unregister(this); + } + } + get size() { + return library().wk_digest_size(this.pointer); + } + copy() { + if (!this.pointer) + throw new DOMException( + "Hash context has already been finalized.", + "OperationError", + ); + const pointer = library().wk_digest_copy(this.pointer); + if (!pointer) throw error(); + return new NativeDigest("", this.context, pointer); + } + update(data: Uint8Array) { if (!this.pointer) throw new Error(`${this.context} context has already been finalized`); @@ -613,3 +636,58 @@ export class NativeTLS { return certs; } } + +const hmacReleased = new FinalizationRegistry((pointer) => + library().wk_hmac_free(pointer), +); +export class NativeHmac { + private pointer: number; + constructor(algorithm: string, key: Uint8Array) { + this.pointer = inputs([algorithm, key], (api, [a, k]) => + api.wk_hmac_new(a, k, key.byteLength), + ); + if (!this.pointer) throw new Error("Failed to initalize HMAC"); + hmacReleased.register(this, this.pointer, this); + } + update(data: Uint8Array) { + inputs([data], (api, [p]) => { + if (!api.wk_hmac_update(this.pointer, p, data.byteLength)) throw error(); + }); + } + final() { + const api = library(); + try { + if (!api.wk_hmac_final(this.pointer)) + throw new Error("Failed to finalize HMAC"); + return result(api); + } finally { + api.wk_hmac_free(this.pointer); + this.pointer = 0; + hmacReleased.unregister(this); + } + } +} + +export function nativePbkdf2( + password: Uint8Array, + salt: Uint8Array, + iterations: number, + length: number, + digest: string, +) { + return inputs([password, salt, digest], (api, [p, s, d]) => { + if ( + !api.wk_pbkdf2( + p, + password.byteLength, + s, + salt.byteLength, + iterations, + length, + d, + ) + ) + throw new Error("Pbkdf2 failed"); + return result(api); + }); +} diff --git a/src/compat/crypto/native-crypto.wasm b/src/compat/crypto/native-crypto.wasm index 3bb0cca00fa9564305802c821ebf4c01a4b6ded8..0ab580118d5a449d9dc3dc5c03e00a7520c09420 100755 GIT binary patch delta 530836 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 z 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 z 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 diff --git a/src/compat/crypto/pbkdf2-handle.ts b/src/compat/crypto/pbkdf2-handle.ts new file mode 100644 index 00000000..115147f8 --- /dev/null +++ b/src/compat/crypto/pbkdf2-handle.ts @@ -0,0 +1,35 @@ +import { validateCryptoLength } from "./validation.js"; +import { nativePbkdf2 } from "./native-crypto.js"; +import { aliases } from "./hash-handle.js"; +const sizes: Record = { + md4: 16, + md5: 16, + sha1: 20, + sha224: 28, + sha256: 32, + sha384: 48, + sha512: 64, + "md5-sha1": 36, +}; +export default { + getPbkdf( + password: Uint8Array, + salt: Uint8Array, + iterations: number, + length: number, + digest: string, + ) { + const normalized = aliases[digest.toLowerCase()]; + if (!normalized) throw new TypeError("Invalid Pbkdf2 digest: " + digest); + validateCryptoLength( + password.byteLength, + "Pbkdf2 failed: password is too large", + ); + validateCryptoLength(salt.byteLength, "Pbkdf2 failed: salt is too large"); + if (length > 255 * sizes[normalized]) + throw new RangeError( + "Pbkdf2 failed: derived key length exceeds maximum for this hash", + ); + return nativePbkdf2(password, salt, iterations, length, normalized); + }, +}; diff --git a/src/compat/crypto/pbkdf2.ts b/src/compat/crypto/pbkdf2.ts new file mode 100644 index 00000000..d51dd227 --- /dev/null +++ b/src/compat/crypto/pbkdf2.ts @@ -0,0 +1,127 @@ +// @ts-nocheck +// Copyright (c) 2017-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import cryptoImpl from "./pbkdf2-handle.js"; + +import { Buffer } from "node:buffer"; + +import { + validateInt32, + validateFunction, + validateString, +} from "../net/validators.js"; + +import { bytes as getArrayBufferOrView } from "./validation.js"; + +export type ArrayLike = + | string + | ArrayBuffer + | SharedArrayBuffer + | ArrayBufferView; + +export function pbkdf2Sync( + password: ArrayLike, + salt: ArrayLike, + iterations: number, + keylen: number, + digest: string, +): Buffer { + ({ password, salt, iterations, keylen, digest } = check( + password, + salt, + iterations, + keylen, + digest, + )); + + const result = cryptoImpl.getPbkdf( + password, + salt, + iterations, + keylen, + digest, + ); + return Buffer.from(result); +} + +export type Pbkdf2Callback = (err?: Error | null, result?: Buffer) => void; +export function pbkdf2( + password: ArrayLike, + salt: ArrayLike, + iterations: number, + keylen: number, + digest: string, + callback: Pbkdf2Callback, +): void { + if (typeof digest === "function") { + // Appease node test cases + validateString(undefined, "digest"); + } + validateFunction(callback, "callback"); + ({ password, salt, iterations, keylen, digest } = check( + password, + salt, + iterations, + keylen, + digest, + )); + + new Promise((res, rej) => { + try { + res(cryptoImpl.getPbkdf(password, salt, iterations, keylen, digest)); + } catch (err) { + rej(err as Error); + } + }).then( + (val: ArrayBuffer): unknown => callback(null, Buffer.from(val)), + (err: unknown): unknown => callback(err as Error), + ); +} + +function check( + password: ArrayLike | ArrayBufferView, + salt: ArrayLike | ArrayBufferView, + iterations: number, + keylen: number, + digest: string, +): { + password: ArrayLike | ArrayBufferView; + salt: ArrayLike | ArrayBufferView; + iterations: number; + keylen: number; + digest: string; +} { + validateString(digest, "digest"); + + password = getArrayBufferOrView(password, "password"); + salt = getArrayBufferOrView(salt, "salt"); + // OpenSSL uses a signed int to represent these values, so we are restricted + // to the 31-bit range here (which is plenty). + validateInt32(iterations, "iterations", 1); + validateInt32(keylen, "keylen", 0); + + return { password, salt, iterations, keylen, digest }; +} diff --git a/src/compat/crypto/symmetric.ts b/src/compat/crypto/symmetric.ts index 8cb36a21..0de34786 100644 --- a/src/compat/crypto/symmetric.ts +++ b/src/compat/crypto/symmetric.ts @@ -6,43 +6,6 @@ import { secretMaterial } from "./keys.js"; const BaseTransform = Transform as unknown as typeof import("node:stream").Transform; const handles = new WeakMap(); -export class Hmac extends BaseTransform { - constructor(algorithm: string, key: unknown, options?: any) { - super(options); - handles.set( - this, - original.createHmac( - algorithm, - typeof key === "string" - ? Buffer.from(key, options?.encoding) - : secretMaterial(key), - ), - ); - } - update(data: unknown, encoding?: string): this { - handles.get(this).update(data, encoding); - return this; - } - digest(encoding?: string): any { - return handles.get(this).digest(encoding); - } - override _transform(chunk: any, encoding: any, callback: any) { - try { - this.update(chunk, encoding); - callback(); - } catch (error) { - callback(error); - } - } - override _flush(callback: any) { - try { - this.push(this.digest()); - callback(); - } catch (error) { - callback(error); - } - } -} class CipherTransform extends BaseTransform { constructor( decrypt: boolean, @@ -110,9 +73,6 @@ export class Decipheriv extends CipherTransform { super(true, algorithm, key, iv, options); } } -export function createHmac(algorithm: string, key: unknown, options?: any) { - return new Hmac(algorithm, key, options); -} export function createCipheriv( algorithm: string, key: unknown, diff --git a/src/compat/crypto/upstream-hash.fixture.mjs b/src/compat/crypto/upstream-hash.fixture.mjs new file mode 100644 index 00000000..6b339713 --- /dev/null +++ b/src/compat/crypto/upstream-hash.fixture.mjs @@ -0,0 +1,342 @@ +// Copyright (c) 2017-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +import { Buffer } from "node:buffer"; +import * as assert from "node:assert"; +import * as crypto from "./hash.js"; +import * as stream from "../stream/index.js"; + +let cryptoType; +let digest; + +export const hash_correctness_tests = { + async test(ctrl, env, ctx) { + const a1 = crypto.createHash("sha1").update("Test123").digest("hex"); + const a2 = crypto.createHash("sha256").update("Test123").digest("base64"); + const a3 = crypto.createHash("sha512").update("Test123").digest(); // buffer + const a4 = crypto.createHash("sha1").update("Test123").digest("buffer"); + + // stream interface + let a5 = crypto.createHash("sha512"); + a5.end("Test123"); + a5 = a5.read(); + + let a6 = crypto.createHash("sha512"); + a6.write("Te"); + a6.write("st"); + a6.write("123"); + a6.end(); + a6 = a6.read(); + + let a7 = crypto.createHash("sha512"); + a7.end(); + a7 = a7.read(); + + let a8 = crypto.createHash("sha512"); + a8.write(""); + a8.end(); + a8 = a8.read(); + + cryptoType = "md5"; + digest = "latin1"; + const a0 = crypto.createHash(cryptoType).update("Test123").digest(digest); + assert.strictEqual( + a0, + // The pinned fixture spells some bytes as caret notation; use the + // equivalent hex vector confirmed against the pinned workerd runtime. + Buffer.from("68eacb97d86f0c4621fa2b0e17cabd8c", "hex").toString("latin1"), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`, + ); + + cryptoType = "md5"; + digest = "hex"; + assert.strictEqual( + a1, + "8308651804facb7b9af8ffc53a33a22d6a1c8ac2", + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`, + ); + cryptoType = "sha256"; + digest = "base64"; + assert.strictEqual( + a2, + "2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=", + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`, + ); + + cryptoType = "sha512"; + digest = "latin1"; + assert.deepStrictEqual( + a3, + Buffer.from( + "c12834f1031f6497214f27d4432f26517ad494156cb88d512bdb1dc4b57db2d692a3dfa269a19b0a0a2a0fd7d6a2a885e33c839c93c206da30a187392847ed27", + "hex", + ), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`, + ); + + cryptoType = "sha1"; + digest = "hex"; + assert.deepStrictEqual( + a4, + Buffer.from("8308651804facb7b9af8ffc53a33a22d6a1c8ac2", "hex"), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`, + ); + + cryptoType = "sha1"; + digest = "hex"; + assert.deepStrictEqual( + a4, + Buffer.from("8308651804FACB7B9AF8FFC53A33A22D6A1C8AC2", "hex"), + `${cryptoType} with ${digest} digest failed to evaluate to expected hash`, + ); + + // Stream interface should produce the same result. + assert.deepStrictEqual(a5, a3); + assert.deepStrictEqual(a6, a3); + assert.notStrictEqual(a7, undefined); + assert.notStrictEqual(a8, undefined); + + // Test multiple updates to same hash + const h1 = crypto.createHash("sha1").update("Test123").digest("hex"); + const h2 = crypto + .createHash("sha1") + .update("Test") + .update("123") + .digest("hex"); + assert.strictEqual(h1, h2); + }, +}; + +export const hash_error_test = { + async test(ctrl, env, ctx) { + // Issue https://github.com/nodejs/node-v0.x-archive/issues/2227: unknown digest + // method should throw an error. + assert.throws(function () { + crypto.createHash("xyzzy"); + }, /Digest method not supported/); + + // Issue https://github.com/nodejs/node/issues/9819: throwing encoding used to + // segfault. + assert.throws( + () => + crypto.createHash("sha256").digest({ + toString: () => { + throw new Error("boom"); + }, + }), + { + name: "Error", + message: "boom", + }, + ); + + // Issue https://github.com/nodejs/node/issues/25487: error message for invalid + // arg type to update method should include all possible types + assert.throws(() => crypto.createHash("sha256").update(), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + + // Default UTF-8 encoding + const hutf8 = crypto + .createHash("sha512") + .update("УТФ-8 text") + .digest("hex"); + assert.strictEqual( + hutf8, + "4b21bbd1a68e690a730ddcb5a8bc94ead9879ffe82580767ad7ec6fa8ba2dea6" + + "43a821af66afa9a45b6a78c712fecf0e56dc7f43aef4bcfc8eb5b4d8dca6ea5b", + ); + + assert.notStrictEqual( + hutf8, + crypto.createHash("sha512").update("УТФ-8 text", "latin1").digest("hex"), + ); + + const h3 = crypto.createHash("sha256"); + h3.digest(); + + assert.throws(() => h3.digest(), { + code: "ERR_CRYPTO_HASH_FINALIZED", + name: "Error", + }); + + assert.throws(() => h3.update("foo"), { + code: "ERR_CRYPTO_HASH_FINALIZED", + name: "Error", + }); + + assert.strictEqual( + crypto.createHash("sha256").update("test").digest("ucs2"), + crypto.createHash("sha256").update("test").digest().toString("ucs2"), + ); + + assert.throws(() => crypto.createHash(), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: + 'The "algorithm" argument must be of type string. ' + + "Received undefined", + }); + + { + const Hash = crypto.Hash; + const instance = crypto.Hash("sha256"); + assert.ok( + instance instanceof Hash, + "Hash is expected to return a new instance" + + " when called without `new`", + ); + } + + // shake*-based tests for XOF hash function are not supported in FIPS and have been removed. + { + // Non-XOF hash functions should accept valid outputLength options as well. + assert.strictEqual( + crypto.createHash("sha224", { outputLength: 28 }).digest("hex"), + "d14a028c2a3a2bc9476102bb288234c4" + "15a2b01f828ea62ac5b3e42f", + ); + + // Passing invalid sizes should throw during creation. + assert.throws( + () => { + crypto.createHash("sha256", { outputLength: 28 }); + }, + { + name: "Error", + }, + ); + + for (const outputLength of [null, {}, "foo", false]) { + assert.throws(() => crypto.createHash("sha256", { outputLength }), { + code: "ERR_INVALID_ARG_TYPE", + }); + } + + for (const outputLength of [-1, 0.5, Infinity, 2 ** 90]) { + assert.throws(() => crypto.createHash("sha256", { outputLength }), { + code: "ERR_OUT_OF_RANGE", + }); + } + } + }, +}; + +export const hash_copy_test = { + async test(ctrl, env, ctx) { + const h = crypto.createHash("sha512"); + h.digest(); + assert.throws(() => h.copy(), { code: "ERR_CRYPTO_HASH_FINALIZED" }); + assert.throws(() => h.digest(), { code: "ERR_CRYPTO_HASH_FINALIZED" }); + + const a = crypto.createHash("sha512").update("abc"); + const b = a.copy(); + const c = b.copy().update("def"); + const d = crypto.createHash("sha512").update("abcdef"); + assert.strictEqual(a.digest("hex"), b.digest("hex")); + assert.strictEqual(c.digest("hex"), d.digest("hex")); + }, +}; + +export const hash_pipe_test = { + async test(ctrl, env, ctx) { + const p = Promise.withResolvers(); + + const s = new stream.PassThrough(); + const h = crypto.createHash("sha512"); + const expect = + "fba055c6fd0c5b6645407749ed7a8b41" + + "b8f629f2163c3ca3701d864adabda1f8" + + "93c37bf82b22fdd151ba8e357f611da4" + + "88a74b6a5525dd9b69554c6ce5138ad7"; + + s.pipe(h) + .on("data", function (c) { + // Calling digest() after piping into a stream with SHA3 should not cause + // a segmentation fault, see https://github.com/nodejs/node/issues/28245. + if (c !== expect || h.digest("hex") !== expect) { + p.reject("Unexpected value for stream-based hash"); + } + p.resolve(); + }) + .setEncoding("hex"); + + s.end("aoeu"); + await p.promise; + }, +}; + +export const hash_one_shot = { + test() { + assert.strictEqual( + crypto.hash("sha1", "Node.js"), + "10b3493287f831e81a438811a1ffba01f8cec4b7", + ); + + const check = Buffer.from( + "10b3493287f831e81a438811a1ffba01f8cec4b7", + "hex", + ); + const base64 = "Tm9kZS5qcw=="; + assert.deepStrictEqual( + crypto.hash("sha1", Buffer.from(base64, "base64"), "buffer"), + check, + ); + + assert.throws(() => crypto.hash(1), { + code: "ERR_INVALID_ARG_TYPE", + }); + assert.throws(() => crypto.hash("sha1", 12), { + code: "ERR_INVALID_ARG_TYPE", + }); + assert.throws(() => crypto.hash("sha1", "test", 123), { + code: "ERR_INVALID_ARG_TYPE", + }); + assert.throws(() => crypto.hash("unknown", "what"), { + message: /Digest method not supported/, + }); + }, +}; + +export const HashCopyAfterDigest = { + async test() { + for (let i = 0; i < 50; i++) { + const hash = crypto.createHash("sha256"); + hash.update("x" + i); + + const kHandle = Object.getOwnPropertySymbols(hash).find( + (s) => s.toString() === "Symbol(kHandle)", + ); + const handle = hash[kHandle]; + handle.digest(); + + assert.throws(() => hash.copy(), { + message: /already been finalized/, + }); + } + }, +}; diff --git a/src/compat/crypto/upstream-hkdf.fixture.mjs b/src/compat/crypto/upstream-hkdf.fixture.mjs new file mode 100644 index 00000000..6e65d75c --- /dev/null +++ b/src/compat/crypto/upstream-hkdf.fixture.mjs @@ -0,0 +1,283 @@ +// Copyright (c) 2017-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +import { Buffer, kMaxLength } from "node:buffer"; +import * as assert from "node:assert"; + +import { + // createSecretKey, + hkdf, + hkdfSync, + getHashes, + createSecretKey, +} from "./index.js"; + +export const hkdf_error_tests = { + async test(ctrl, env, ctx) { + assert.throws(() => hkdf(), { + code: "ERR_INVALID_ARG_TYPE", + message: /The "digest" argument must be of type string/, + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf(i, "a"), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "digest" argument must be of type string/, + }); + assert.throws(() => hkdfSync(i, "a"), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "digest" argument must be of type string/, + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf("sha256", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "ikm" argument must be /, + }); + assert.throws(() => hkdfSync("sha256", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "ikm" argument must be /, + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf("sha256", "secret", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "salt" argument must be /, + }); + assert.throws(() => hkdfSync("sha256", "secret", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "salt" argument must be /, + }); + }); + + [1, {}, [], false, Infinity].forEach((i) => { + assert.throws(() => hkdf("sha256", "secret", "salt", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "info" argument must be /, + }); + assert.throws(() => hkdfSync("sha256", "secret", "salt", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "info" argument must be /, + }); + }); + + ["test", {}, [], false].forEach((i) => { + assert.throws(() => hkdf("sha256", "secret", "salt", "info", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "length" argument must be of type number/, + }); + assert.throws(() => hkdfSync("sha256", "secret", "salt", "info", i), { + code: "ERR_INVALID_ARG_TYPE", + message: /^The "length" argument must be of type number/, + }); + }); + + assert.throws(() => hkdf("sha256", "secret", "salt", "info", -1), { + code: "ERR_OUT_OF_RANGE", + }); + assert.throws(() => hkdfSync("sha256", "secret", "salt", "info", -1), { + code: "ERR_OUT_OF_RANGE", + }); + assert.throws( + () => hkdf("sha256", "secret", "salt", "info", kMaxLength + 1), + { + code: "ERR_OUT_OF_RANGE", + }, + ); + assert.throws( + () => hkdfSync("sha256", "secret", "salt", "info", kMaxLength + 1), + { + code: "ERR_OUT_OF_RANGE", + }, + ); + + { + const p = Promise.withResolvers(); + hkdf("unknown", "a", "", "", 10, (err, asyncResult) => { + if (err) { + return p.reject(err); + } + p.resolve(); + }); + await assert.rejects(p.promise); + } + assert.throws(() => hkdfSync("unknown", "a", "", "", 10), { + name: "TypeError", + }); + + assert.throws(() => hkdf("unknown", "a", "", Buffer.alloc(1025), 10), { + code: "ERR_OUT_OF_RANGE", + }); + assert.throws(() => hkdfSync("unknown", "a", "", Buffer.alloc(1025), 10), { + code: "ERR_OUT_OF_RANGE", + }); + + { + const p = Promise.withResolvers(); + hkdf("sha512", "a", "", "", 64 * 255 + 1, (err, asyncResult) => { + if (err) { + return p.reject(err); + } + p.resolve(); + }); + await assert.rejects(p.promise); + } + assert.throws(() => hkdfSync("sha512", "a", "", "", 64 * 255 + 1), { + name: "RangeError", + }); + }, +}; + +async function hkdfTestAlg([hash, secret, salt, info, length]) { + { + const syncResult = hkdfSync(hash, secret, salt, info, length); + assert.ok(syncResult instanceof ArrayBuffer); + let is_async = false; + const p = Promise.withResolvers(); + + hkdf(hash, secret, salt, info, length, (err, asyncResult) => { + if (err) return p.reject(err); + assert.ok(is_async); + assert.ok(asyncResult instanceof ArrayBuffer); + assert.deepStrictEqual(syncResult, asyncResult); + p.resolve(); + }); + // Keep this after the hkdf call above. This verifies + // that the callback is invoked asynchronously. + is_async = true; + await p.promise; + } + + { + const buf_secret = Buffer.from(secret); + const buf_salt = Buffer.from(salt); + const buf_info = Buffer.from(info); + const p = Promise.withResolvers(); + + const syncResult = hkdfSync(hash, buf_secret, buf_salt, buf_info, length); + hkdf(hash, buf_secret, buf_salt, buf_info, length, (err, asyncResult) => { + if (err) return p.reject(err); + assert.deepStrictEqual(syncResult, asyncResult); + p.resolve(); + }); + await p.promise; + } + + // Disabled for now, requires KeyObject support + // { + // const key_secret = createSecretKey(Buffer.from(secret)); + // const buf_salt = Buffer.from(salt); + // const buf_info = Buffer.from(info); + // const p = Promise.withResolvers(); + // + // const syncResult = hkdfSync(hash, key_secret, buf_salt, buf_info, length); + // hkdf(hash, key_secret, buf_salt, buf_info, length, (err, asyncResult) => { + // if (err) return p.reject(err); + // assert.deepStrictEqual(syncResult, asyncResult); + // p.resolve(); + // }); + // await p.promise; + // } + + { + const p = Promise.withResolvers(); + const ta_secret = new Uint8Array(Buffer.from(secret)); + const ta_salt = new Uint16Array(Buffer.from(salt)); + const ta_info = new Uint32Array(Buffer.from(info)); + + const syncResult = hkdfSync(hash, ta_secret, ta_salt, ta_info, length); + hkdf(hash, ta_secret, ta_salt, ta_info, length, (err, asyncResult) => { + if (err) return p.reject(err); + assert.deepStrictEqual(syncResult, asyncResult); + p.resolve(); + }); + await p.promise; + + const syncResultBuf = hkdfSync( + hash, + ta_secret.buffer, + ta_salt.buffer, + ta_info.buffer, + length, + ); + assert.deepStrictEqual(syncResult, syncResultBuf); + } + + { + const p = Promise.withResolvers(); + const ta_secret = new Uint8Array(Buffer.from(secret)); + const a_salt = new ArrayBuffer(0); + const a_info = new ArrayBuffer(1); + + const syncResult = hkdfSync(hash, ta_secret.buffer, a_salt, a_info, length); + hkdf(hash, ta_secret, a_salt, a_info, length, (err, asyncResult) => { + if (err) return p.reject(err); + assert.deepStrictEqual(syncResult, asyncResult); + p.resolve(); + }); + await p.promise; + } +} + +export const hkdf_correctness_tests = { + async test(ctrl, env, ctx) { + const algorithms = [ + ["sha256", "secret", "salt", "info", 10], + ["sha256", "", "", "", 10], + ["sha256", "", "salt", "", 10], + ["sha512", "secret", "salt", "", 15], + ]; + for (const element of algorithms) { + await hkdfTestAlg(element); + } + + getHashes().forEach((hash) => { + assert.ok(hkdfSync(hash, "key", "salt", "info", 5)); + }); + }, +}; + +export const hkdf_secret_key = { + async test() { + const check = "ae1db23b1051ea6bc1f182e5114d869c1b5e0b8d"; + + const key = createSecretKey("abcdef01020304", "hex"); + const result = hkdfSync("sha256", key, "hello", "there", 20); + assert.strictEqual(Buffer.from(result).toString("hex"), check); + + const { promise, resolve, reject } = Promise.withResolvers(); + + hkdf("sha256", key, "hello", "there", 20, (err, result) => { + if (err) return reject(err); + resolve(Buffer.from(result).toString("hex")); + }); + + assert.strictEqual(await promise, check); + }, +}; diff --git a/src/compat/crypto/upstream-hmac.fixture.mjs b/src/compat/crypto/upstream-hmac.fixture.mjs new file mode 100644 index 00000000..a16ba120 --- /dev/null +++ b/src/compat/crypto/upstream-hmac.fixture.mjs @@ -0,0 +1,558 @@ +// Copyright (c) 2017-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +import { Buffer } from "node:buffer"; +import * as assert from "node:assert"; +import { Hmac, createHmac } from "./hash.js"; +import { createSecretKey } from "./keys.js"; +const crypto = { Hmac, createHmac, createSecretKey }; + +export const hmac_error_tests = { + async test(ctrl, env, ctx) { + { + const Hmac = crypto.Hmac; + const instance = crypto.Hmac("sha256", "Node"); + assert.ok( + instance instanceof Hmac, + "Hmac is expected to return a new instance" + + " when called without `new`", + ); + } + + assert.throws(() => crypto.createHmac(null), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: 'The "hmac" argument must be of type string. Received null', + }); + + assert.throws( + () => + crypto.createHmac("sha256", "key").digest({ + toString: () => { + throw new Error("boom"); + }, + }), + { + name: "Error", + message: "boom", + }, + ); + + assert.throws(() => crypto.createHmac("sha1", null), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + }, +}; + +function testHmac(algo, key, data, expected) { + if (!Array.isArray(data)) data = [data]; + + // If the key is a Buffer, test Hmac with a key object as well. + const keyWrappers = [ + (key) => key, + ...(typeof key === "string" ? [] : [crypto.createSecretKey]), + ]; + + for (const keyWrapper of keyWrappers) { + const hmac = crypto.createHmac(algo, keyWrapper(key)); + for (const chunk of data) hmac.update(chunk); + const actual = hmac.digest("hex"); + assert.strictEqual(actual, expected); + } +} + +export const hmac_correctness_tests = { + async test(ctrl, env, ctx) { + { + // Test HMAC with multiple updates. + testHmac( + "sha1", + "Node", + ["some data", "to hmac"], + "19fd6e1ba73d9ed2224dd5094a71babe85d9a892", + ); + } + + // Test HMAC (Wikipedia Test Cases) + const wikipedia = [ + { + key: "key", + data: "The quick brown fox jumps over the lazy dog", + hmac: { + // HMACs lifted from Wikipedia. + md5: "80070713463e7749b90c2dc24911e275", + sha1: "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9", + sha256: + "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc" + + "2d1a3cd8", + }, + }, + { + key: "key", + data: "", + hmac: { + // Intermediate test to help debugging. + md5: "63530468a04e386459855da0063b6596", + sha1: "f42bb0eeb018ebbd4597ae7213711ec60760843f", + sha256: + "5d5d139563c95b5967b9bd9a8c9b233a9dedb45072794cd232dc1b74" + + "832607d0", + }, + }, + { + key: "", + data: "The quick brown fox jumps over the lazy dog", + hmac: { + // Intermediate test to help debugging. + md5: "ad262969c53bc16032f160081c4a07a0", + sha1: "2ba7f707ad5f187c412de3106583c3111d668de8", + sha256: + "fb011e6154a19b9a4c767373c305275a5a69e8b68b0b4c9200c383dc" + + "ed19a416", + }, + }, + { + key: "", + data: "", + hmac: { + // HMACs lifted from Wikipedia. + md5: "74e6f7298a9c2d168935f58c001bad88", + sha1: "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d", + sha256: + "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c71214" + + "4292c5ad", + }, + }, + ]; + + for (const { key, data, hmac } of wikipedia) { + for (const hash in hmac) testHmac(hash, key, data, hmac[hash]); + } + + // Test HMAC-SHA-* (rfc 4231 Test Cases) + const rfc4231 = [ + { + key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), + data: Buffer.from("4869205468657265", "hex"), // 'Hi There' + hmac: { + sha224: "896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22", + sha256: + "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c" + + "2e32cff7", + sha384: + "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c" + + "7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6", + sha512: + "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b305" + + "45e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f170" + + "2e696c203a126854", + }, + }, + { + key: Buffer.from("4a656665", "hex"), // 'Jefe' + data: Buffer.from( + "7768617420646f2079612077616e7420666f72206e6f74686" + "96e673f", + "hex", + ), // 'what do ya want for nothing?' + hmac: { + sha224: "a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44", + sha256: + "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b9" + + "64ec3843", + sha384: + "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec373" + + "6322445e8e2240ca5e69e2c78b3239ecfab21649", + sha512: + "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7" + + "ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b" + + "636e070a38bce737", + }, + }, + { + key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"), + data: Buffer.from( + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + + "ddddddddddddddddddddddddddddddddddddddddddddddddddd", + "hex", + ), + hmac: { + sha224: "7fb3cb3588c6c1f6ffa9694d7d6ad2649365b0c1f65d69d1ec8333ea", + sha256: + "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514" + + "ced565fe", + sha384: + "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e5" + + "5966144b2a5ab39dc13814b94e3ab6e101a34f27", + sha512: + "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33" + + "b2279d39bf3e848279a722c806b485a47e67c807b946a337bee89426" + + "74278859e13292fb", + }, + }, + { + key: Buffer.from( + "0102030405060708090a0b0c0d0e0f10111213141516171819", + "hex", + ), + data: Buffer.from( + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" + + "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "hex", + ), + hmac: { + sha224: "6c11506874013cac6a2abc1bb382627cec6a90d86efc012de7afec5a", + sha256: + "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff4" + + "6729665b", + sha384: + "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e" + + "1f573b4e6801dd23c4a7d679ccf8a386c674cffb", + sha512: + "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050" + + "361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2d" + + "e2adebeb10a298dd", + }, + }, + + { + key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"), + // 'Test With Truncation' + data: Buffer.from("546573742057697468205472756e636174696f6e", "hex"), + hmac: { + sha224: "0e2aea68a90c8d37c988bcdb9fca6fa8", + sha256: "a3b6167473100ee06e0c796c2955552b", + sha384: "3abf34c3503b2a23a46efc619baef897", + sha512: "415fad6271580a531d4179bc891d87a6", + }, + truncate: true, + }, + { + key: Buffer.from( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaa", + "hex", + ), + // 'Test Using Larger Than Block-Size Key - Hash Key First' + data: Buffer.from( + "54657374205573696e67204c6172676572205468616e20426" + + "c6f636b2d53697a65204b6579202d2048617368204b657920" + + "4669727374", + "hex", + ), + hmac: { + sha224: "95e9a0db962095adaebe9b2d6f0dbce2d499f112f2d2b7273fa6870e", + sha256: + "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f" + + "0ee37f54", + sha384: + "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05" + + "033ac4c60c2ef6ab4030fe8296248df163f44952", + sha512: + "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b0137" + + "83f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec" + + "8b915a985d786598", + }, + }, + { + key: Buffer.from( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaa", + "hex", + ), + // 'This is a test using a larger than block-size key and a larger ' + + // 'than block-size data. The key needs to be hashed before being ' + + // 'used by the HMAC algorithm.' + data: Buffer.from( + "5468697320697320612074657374207573696e672061206c6" + + "172676572207468616e20626c6f636b2d73697a65206b6579" + + "20616e642061206c6172676572207468616e20626c6f636b2" + + "d73697a6520646174612e20546865206b6579206e65656473" + + "20746f20626520686173686564206265666f7265206265696" + + "e6720757365642062792074686520484d414320616c676f72" + + "6974686d2e", + "hex", + ), + hmac: { + sha224: "3a854166ac5d9f023f54d517d0b39dbd946770db9c2b95c9f6f565d1", + sha256: + "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f5153" + + "5c3a35e2", + sha384: + "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82" + + "461e99c5a678cc31e799176d3860e6110c46523e", + sha512: + "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d" + + "20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de04460" + + "65c97440fa8c6a58", + }, + }, + ]; + + for (let i = 0, l = rfc4231.length; i < l; i++) { + for (const hash in rfc4231[i].hmac) { + const str = crypto.createHmac(hash, rfc4231[i].key); + str.end(rfc4231[i].data); + let strRes = str.read().toString("hex"); + let actual = crypto + .createHmac(hash, rfc4231[i].key) + .update(rfc4231[i].data) + .digest("hex"); + if (rfc4231[i].truncate) { + actual = actual.substr(0, 32); // first 128 bits == 32 hex chars + strRes = strRes.substr(0, 32); + } + const expected = rfc4231[i].hmac[hash]; + assert.strictEqual( + actual, + expected, + `Test HMAC-${hash} rfc 4231 case ${i + 1}: ${actual} must be ${expected}`, + ); + assert.strictEqual( + actual, + strRes, + `Should get same result from stream (hash: ${hash} and case: ${i + 1})` + + ` => ${actual} must be ${strRes}`, + ); + } + } + + // Test HMAC-MD5/SHA1 (rfc 2202 Test Cases) + const rfc2202_md5 = [ + { + key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), + data: "Hi There", + hmac: "9294727a3638bb1c13f48ef8158bfc9d", + }, + { + key: "Jefe", + data: "what do ya want for nothing?", + hmac: "750c783e6ab0b503eaa86e310a5db738", + }, + { + key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"), + data: Buffer.from( + "ddddddddddddddddddddddddddddddddddddddddddddddddd" + + "ddddddddddddddddddddddddddddddddddddddddddddddddddd", + "hex", + ), + hmac: "56be34521d144c88dbb8c733f0e8b3f6", + }, + { + key: Buffer.from( + "0102030405060708090a0b0c0d0e0f10111213141516171819", + "hex", + ), + data: Buffer.from( + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" + + "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + + "cdcdcdcdcd", + "hex", + ), + hmac: "697eaf0aca3a3aea3a75164746ffaa79", + }, + { + key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"), + data: "Test With Truncation", + hmac: "56461ef2342edc00f9bab995690efd4c", + }, + { + key: Buffer.from( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaa", + "hex", + ), + data: "Test Using Larger Than Block-Size Key - Hash Key First", + hmac: "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd", + }, + { + key: Buffer.from( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaa", + "hex", + ), + data: + "Test Using Larger Than Block-Size Key and Larger Than One " + + "Block-Size Data", + hmac: "6f630fad67cda0ee1fb1f562db3aa53e", + }, + ]; + + for (const { key, data, hmac } of rfc2202_md5) + testHmac("md5", key, data, hmac); + + const rfc2202_sha1 = [ + { + key: Buffer.from("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "hex"), + data: "Hi There", + hmac: "b617318655057264e28bc0b6fb378c8ef146be00", + }, + { + key: "Jefe", + data: "what do ya want for nothing?", + hmac: "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", + }, + { + key: Buffer.from("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "hex"), + data: Buffer.from( + "ddddddddddddddddddddddddddddddddddddddddddddd" + + "ddddddddddddddddddddddddddddddddddddddddddddd" + + "dddddddddd", + "hex", + ), + hmac: "125d7342b9ac11cd91a39af48aa17b4f63f175d3", + }, + { + key: Buffer.from( + "0102030405060708090a0b0c0d0e0f10111213141516171819", + "hex", + ), + data: Buffer.from( + "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdc" + + "dcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" + + "cdcdcdcdcd", + "hex", + ), + hmac: "4c9007f4026250c6bc8414f9bf50c86c2d7235da", + }, + { + key: Buffer.from("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "hex"), + data: "Test With Truncation", + hmac: "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04", + }, + { + key: Buffer.from( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaa", + "hex", + ), + data: "Test Using Larger Than Block-Size Key - Hash Key First", + hmac: "aa4ae5e15272d00e95705637ce8a3b55ed402112", + }, + { + key: Buffer.from( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + + "aaaaaaaaaaaaaaaaaaaaaa", + "hex", + ), + data: + "Test Using Larger Than Block-Size Key and Larger Than One " + + "Block-Size Data", + hmac: "e8e99d0f45237d786d6bbaa7965c7808bbff1a91", + }, + ]; + + for (const { key, data, hmac } of rfc2202_sha1) + testHmac("sha1", key, data, hmac); + }, +}; + +export const hmac_misc_tests = { + async test(ctrl, env, ctx) { + assert.strictEqual( + crypto.createHmac("sha256", "w00t").digest("ucs2"), + crypto.createHmac("sha256", "w00t").digest().toString("ucs2"), + ); + + // Check initialized -> uninitialized state transition after calling digest(). + { + // Normalize caret-spelled bytes in the pinned fixture using workerd's hex vector. + const expected = Buffer.from( + "104152c5bfdca07bc633eebd46199f0255c9f49d", + "hex", + ).toString("latin1"); + { + const h = crypto.createHmac("sha1", "key").update("data"); + assert.deepStrictEqual( + h.digest("buffer"), + Buffer.from(expected, "latin1"), + ); + assert.deepStrictEqual(h.digest("buffer"), Buffer.from("")); + } + { + const h = crypto.createHmac("sha1", "key").update("data"); + assert.strictEqual(h.digest("latin1"), expected); + assert.strictEqual(h.digest("latin1"), ""); + } + } + + // Check initialized -> uninitialized state transition after calling digest(). + // Calls to update() omitted intentionally. + { + // Normalize caret-spelled bytes in the pinned fixture using workerd's hex vector. + const expected = Buffer.from( + "f42bb0eeb018ebbd4597ae7213711ec60760843f", + "hex", + ).toString("latin1"); + { + const h = crypto.createHmac("sha1", "key"); + assert.deepStrictEqual( + h.digest("buffer"), + Buffer.from(expected, "latin1"), + ); + assert.deepStrictEqual(h.digest("buffer"), Buffer.from("")); + } + { + const h = crypto.createHmac("sha1", "key"); + assert.strictEqual(h.digest("latin1"), expected); + assert.strictEqual(h.digest("latin1"), ""); + } + } + + { + assert.throws( + () => crypto.createHmac("sha7", "key"), + /Digest method not supported/, + ); + } + + { + const buf = Buffer.alloc(0); + const keyObject = crypto.createSecretKey(Buffer.alloc(0)); + assert.deepStrictEqual( + crypto.createHmac("sha256", buf).update("foo").digest(), + crypto.createHmac("sha256", keyObject).update("foo").digest(), + ); + } + }, +}; diff --git a/src/compat/crypto/upstream-pbkdf2.fixture.mjs b/src/compat/crypto/upstream-pbkdf2.fixture.mjs new file mode 100644 index 00000000..03aae3b0 --- /dev/null +++ b/src/compat/crypto/upstream-pbkdf2.fixture.mjs @@ -0,0 +1,349 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +import * as assert from "node:assert"; +import * as crypto from "./pbkdf2.js"; + +function mustNotCall() { + return () => {}; +} + +async function runPBKDF2(password, salt, iterations, keylen, hash) { + const syncResult = crypto.pbkdf2Sync( + password, + salt, + iterations, + keylen, + hash, + ); + + const p = Promise.withResolvers(); + crypto.pbkdf2( + password, + salt, + iterations, + keylen, + hash, + (err, asyncResult) => { + assert.deepStrictEqual(asyncResult, syncResult); + p.resolve(); + }, + ); + await p.promise; + + return syncResult; +} + +async function testPBKDF2( + password, + salt, + iterations, + keylen, + expected, + encoding, +) { + const actual = await runPBKDF2(password, salt, iterations, keylen, "sha256"); + assert.strictEqual(actual.toString(encoding || "latin1"), expected); +} + +// +// Test PBKDF2 with RFC 6070 test vectors (except #4) +// + +export const pbkdf2_correctness_tests = { + async test(ctrl, env, ctx) { + await testPBKDF2( + "password", + "salt", + 1, + 20, + "\x12\x0f\xb6\xcf\xfc\xf8\xb3\x2c\x43\xe7\x22\x52" + + "\x56\xc4\xf8\x37\xa8\x65\x48\xc9", + ); + + await testPBKDF2( + "password", + "salt", + 2, + 20, + "\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9" + + "\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e", + ); + + await testPBKDF2( + "password", + "salt", + 4096, + 20, + "\xc5\xe4\x78\xd5\x92\x88\xc8\x41\xaa\x53\x0d\xb6" + + "\x84\x5c\x4c\x8d\x96\x28\x93\xa0", + ); + + await testPBKDF2( + "passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + 25, + "\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11" + + "\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c", + ); + + await testPBKDF2( + "pass\0word", + "sa\0lt", + 4096, + 16, + "\x89\xb6\x9d\x05\x16\xf8\x29\x89\x3c\x69\x62\x26\x65" + "\x0a\x86\x87", + ); + + await testPBKDF2( + "password", + "salt", + 32, + 32, + "64c486c55d30d4c5a079b8823b7d7cb37ff0556f537da8410233bcec330ed956", + "hex", + ); + }, +}; + +export const pbkdf2_no_callback_test = { + test(ctrl, env, ctx) { + // Error path should not leak memory (check with Valgrind). + assert.throws(() => crypto.pbkdf2("password", "salt", 1, 20, "sha1"), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + }, +}; + +export const pbkdf2_out_of_range_tests = { + test(ctrl, env, ctx) { + for (const iterations of [-1, 0, 2147483648]) { + assert.throws( + () => crypto.pbkdf2Sync("password", "salt", iterations, 20, "sha1"), + { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }, + ); + } + + ["str", null, undefined, [], {}].forEach((notNumber) => { + assert.throws( + () => { + crypto.pbkdf2Sync("password", "salt", 1, notNumber, "sha256"); + }, + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }, + ); + }); + + [Infinity, -Infinity, NaN].forEach((input) => { + assert.throws( + () => { + crypto.pbkdf2("password", "salt", 1, input, "sha256", mustNotCall()); + }, + { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: + 'The value of "keylen" is out of range. It ' + + `must be an integer. Received ${input}`, + }, + ); + }); + + [-1, 2147483648, 4294967296].forEach((input) => { + assert.throws( + () => { + crypto.pbkdf2("password", "salt", 1, input, "sha256", mustNotCall()); + }, + { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + }, + ); + }); + }, +}; + +export const empty_pwd_test = { + async test(ctrl, env, ctx) { + // Should not get FATAL ERROR with empty password and salt + // https://github.com/nodejs/node/issues/8571 + const p = Promise.withResolvers(); + crypto.pbkdf2("", "", 1, 32, "sha256", (err, prime) => { + p.resolve(); + }); + await p.promise; + }, +}; + +export const invalid_arg_tests = { + test(ctrl, env, ctx) { + assert.throws( + () => crypto.pbkdf2("password", "salt", 8, 8, mustNotCall()), + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: + 'The "digest" argument must be of type string. ' + + "Received undefined", + }, + ); + + assert.throws(() => crypto.pbkdf2Sync("password", "salt", 8, 8), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: + 'The "digest" argument must be of type string. ' + "Received undefined", + }); + + assert.throws(() => crypto.pbkdf2Sync("password", "salt", 8, 8, null), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + message: + 'The "digest" argument must be of type string. ' + "Received null", + }); + [1, {}, [], true, undefined, null].forEach((input) => { + assert.throws( + () => crypto.pbkdf2(input, "salt", 8, 8, "sha256", mustNotCall()), + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }, + ); + + assert.throws( + () => crypto.pbkdf2("pass", input, 8, 8, "sha256", mustNotCall()), + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }, + ); + + assert.throws(() => crypto.pbkdf2Sync(input, "salt", 8, 8, "sha256"), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + + assert.throws(() => crypto.pbkdf2Sync("pass", input, 8, 8, "sha256"), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + }); + + ["test", {}, [], true, undefined, null].forEach((i) => { + assert.throws( + () => crypto.pbkdf2("pass", "salt", i, 8, "sha256", mustNotCall()), + { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }, + ); + + assert.throws(() => crypto.pbkdf2Sync("pass", "salt", i, 8, "sha256"), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + }); + }, +}; + +export const TypedArray_tests = { + async test(ctrl, env, ctx) { + // Any TypedArray should work for password and salt. + for (const SomeArray of [ + Uint8Array, + Uint16Array, + Uint32Array, + // Some hosts lack Float16Array. The shared contract checks its input + // brand where available; native measurements record this host boundary. + ...(globalThis.Float16Array ? [globalThis.Float16Array] : []), + Float32Array, + Float64Array, + ArrayBuffer, + ]) { + await runPBKDF2(new SomeArray(10), "salt", 8, 8, "sha256"); + await runPBKDF2("pass", new SomeArray(10), 8, 8, "sha256"); + } + }, +}; + +export const invalid_digest_tests = { + async test(ctrl, env, ctx) { + { + const p = Promise.withResolvers(); + crypto.pbkdf2("pass", "salt", 8, 8, "md55", (err, prime) => { + if (err) return p.reject(err); + }); + await assert.rejects(p.promise); + } + + assert.throws(() => crypto.pbkdf2Sync("pass", "salt", 8, 8, "md55"), { + name: "TypeError", + }); + + // TODO(soon): Enable this once crypto.getHashes() is available. Note that shake* is not + // supported by BoringSSL so there's no need to filter it out, but we may want to filter other + // functions. + // const kNotPBKDF2Supported = ['shake128', 'shake256']; + // crypto.getHashes() + // .filter((hash) => !kNotPBKDF2Supported.includes(hash)) + // .forEach((hash) => { + // runPBKDF2(new Uint8Array(10), 'salt', 8, 8, hash); + // }); + + { + // This should not crash. + assert.throws(() => crypto.pbkdf2Sync("1", "2", 1, 1, "%"), { + name: "TypeError", + }); + } + }, +}; + +export const pbkdf2_keylen_cap = { + test() { + // SHA-1 digest = 20 bytes, max keylen = 255 * 20 = 5100 + assert.throws(() => crypto.pbkdf2Sync("p", "s", 1000, 5101, "sha1"), { + name: "RangeError", + }); + const r1 = crypto.pbkdf2Sync("p", "s", 1000, 5100, "sha1"); + assert.strictEqual(r1.length, 5100); + + // SHA-256 digest = 32 bytes, max keylen = 255 * 32 = 8160 + assert.throws(() => crypto.pbkdf2Sync("p", "s", 1000, 8161, "sha256"), { + name: "RangeError", + }); + const r2 = crypto.pbkdf2Sync("p", "s", 1000, 8160, "sha256"); + assert.strictEqual(r2.length, 8160); + }, +}; diff --git a/src/compat/crypto/validation.ts b/src/compat/crypto/validation.ts index 47c6c31d..829acb73 100644 --- a/src/compat/crypto/validation.ts +++ b/src/compat/crypto/validation.ts @@ -13,9 +13,27 @@ export function validateInteger( if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } +export function isAnyBuffer(value: unknown): value is ArrayBuffer { + try { + Reflect.getOwnPropertyDescriptor( + ArrayBuffer.prototype, + "byteLength", + )!.get!.call(value); + return true; + } catch {} + if (typeof SharedArrayBuffer !== "undefined") + try { + Reflect.getOwnPropertyDescriptor( + SharedArrayBuffer.prototype, + "byteLength", + )!.get!.call(value); + return true; + } catch {} + return false; +} export function bytes(value: unknown, name: string): Buffer { if (typeof value === "string") return Buffer.from(value); - if (value instanceof ArrayBuffer) return Buffer.from(value); + if (isAnyBuffer(value)) return Buffer.from(value); if (ArrayBuffer.isView(value)) return Buffer.from(value.buffer, value.byteOffset, value.byteLength); throw new ERR_INVALID_ARG_TYPE( @@ -24,3 +42,8 @@ export function bytes(value: unknown, name: string): Buffer { value, ); } + +// Native Workers crypto entry points use signed 32-bit buffer lengths. +export function validateCryptoLength(length: number, message: string): void { + if (length > 0x7fffffff) throw new RangeError(message); +} diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index b1c8a0d9..09abb3a6 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -9,7 +9,7 @@ whole modules. Implemented profiles include console's Workers stubs, sys aliasing, public util/types predicates, crypto random integers/UUIDs/Web Crypto exports, -hash/HKDF/scrypt, the four Workers ECDH curves, crypto engine/secure-heap stubs, +Hash/HMAC/PBKDF2/HKDF/scrypt, the four Workers ECDH curves, crypto engine/secure-heap stubs, writable memory-backed /tmp, module metadata, HTTP internal helpers, adapter-owned secret and asymmetric KeyObjects, prime generation and checking, incremental signatures, Certificate SPKAC and X509Certificate operations, @@ -43,6 +43,19 @@ with scripts/build-native-codecs.mjs or scripts/build-native-zlib.mjs and the pi Scrypt uses a synchronous JavaScript backend, including for callback completion, so large parameters can block the WebView. +Hash and HMAC use the owned OpenSSL backend, including incremental input, +Hash copying, stream finalization and repeated-digest behavior. `getHashes()` +returns the pinned 17 names, including MD4, MD5-SHA1 and the RSA/DSA aliases. +PBKDF2 and HKDF use the same digest mapping and preserve the pinned Workers +limit of 255 digest blocks. Callback completion is asynchronous, while the +underlying derivation runs synchronously; high PBKDF2 iteration counts can block +the WebView. The upstream digest/KDF fixtures run with +`npx vitest run src/compat/crypto/hash.spec.ts src/compat/crypto/hash-types.spec.ts`. +Native measurements record whether the engine provides `Float16Array`. +Where absent, its input brand is unverified; equivalent half-float bytes are +still tested as a byte-array input. This does not certify complete crypto input +coverage on that engine. + Adapter-created secret keys support copied raw/JWK export, equality, generation, HMAC, HKDF and symmetric ciphers. KeyObject.from can retain a host CryptoKey, but synchronous extraction of its opaque key material is unavailable. RSA, EC, Ed25519 and X25519 key generation and PEM/DER/JWK conversion use diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index e8b34cd0..5e854259 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,168 @@ export const eventCases = { + cryptoPbkdfContracts: async function ({ crypto }) { + const attempt = (fn) => { + try { + return fn(); + } catch (error) { + return { name: error.name, code: error.code ?? null }; + } + }; + const result = { algorithms: [], typed: [], errors: [] }; + for (const name of crypto.getHashes()) + result.algorithms.push([ + name, + crypto.pbkdf2Sync("pass", "salt", 3, 20, name).toString("hex"), + ]); + for (const Constructor of [ + Uint8Array, + Uint16Array, + Uint32Array, + Float32Array, + Float64Array, + ]) { + const value = new Constructor(new ArrayBuffer(16)); + value.set([1, 2]); + result.typed.push([ + Constructor.name, + crypto + .pbkdf2Sync(value, new DataView(value.buffer, 2, 6), 2, 8, "sha256") + .toString("hex"), + ]); + } + // Half-float bytes are portable; the Float16Array input brand is tested + // only where the engine provides it, and native evidence records availability. + const halfBytes = new Uint8Array(16); + new DataView(halfBytes.buffer).setUint16(0, 0x3c00, true); + new DataView(halfBytes.buffer).setUint16(2, 0x4000, true); + result.halfFloatBytes = crypto + .pbkdf2Sync( + halfBytes, + new DataView(halfBytes.buffer, 2, 6), + 2, + 8, + "sha256", + ) + .toString("hex"); + if (typeof globalThis.Float16Array === "function") { + const half = new globalThis.Float16Array(8); + half.set([1, 2]); + const actual = crypto + .pbkdf2Sync(half, new DataView(half.buffer, 2, 6), 2, 8, "sha256") + .toString("hex"); + if (actual !== result.halfFloatBytes) + throw new Error( + "Float16Array PBKDF2 differs from its byte representation", + ); + } + for (const args of [ + ["p", "s", 0, 1, "sha1"], + ["p", "s", 1, 5101, "sha1"], + ["p", "s", 1, 8161, "sha256"], + ["p", "s", 1, 0, "sha256"], + ["p", "s", 1, 1, "unknown"], + ]) + result.errors.push( + attempt(() => crypto.pbkdf2Sync(...args).toString("hex")), + ); + const order = []; + result.callback = await new Promise((resolve) => { + crypto.pbkdf2("p", "s", 2, 8, "sha256", (error, value) => { + order.push("callback"); + resolve(error ? error.name : value.toString("hex")); + }); + order.push("returned"); + }); + result.order = order; + result.callbackError = await new Promise((resolve) => + crypto.pbkdf2("p", "s", 1, 1, "unknown", (error) => + resolve([error.name, error.code ?? null]), + ), + ); + const hex = (buffer) => + Array.from(new Uint8Array(buffer), (byte) => + byte.toString(16).padStart(2, "0"), + ).join(""); + result.hkdf = crypto + .getHashes() + .map((name) => [ + name, + hex( + crypto.hkdfSync( + name, + crypto.createSecretKey(new Uint8Array([1, 2, 3])), + "salt", + "info", + 10, + ), + ), + ]); + result.hkdfErrors = [ + attempt(() => crypto.hkdfSync("unknown", "p", "s", "i", 1)), + attempt(() => crypto.hkdfSync("sha1", "p", "s", "i", 5101)), + attempt(() => crypto.hkdfSync("sha1", "p", "s", new Uint8Array(1025), 1)), + ]; + return result; + }, + cryptoDigestContracts: async function ({ crypto }) { + const attempt = (fn) => { + try { + return fn(); + } catch (error) { + return { name: error.name, code: error.code ?? null }; + } + }; + const result = { names: crypto.getHashes(), algorithms: [], errors: [] }; + for (const name of result.names) { + const hash = crypto.createHash(name).update("a"); + const copy = hash.copy(); + result.algorithms.push({ + name, + hash: hash.update("bc").digest("hex"), + copy: copy.update("d").digest("hex"), + hmac: attempt(() => + crypto.createHmac(name, "key").update("abc").digest("hex"), + ), + }); + } + for (const name of [ + "ripemd160", + "sha-256", + "SHA", + "ssl3-md5", + "ssl3-sha1", + "SHA256", + ]) + result.errors.push([ + name, + attempt(() => crypto.createHash(name).digest("hex")), + ]); + for (const outputLength of [0, 28, 32, null, -1, "32"]) + result.errors.push( + attempt(() => + crypto.createHash("sha256", { outputLength }).digest("hex"), + ), + ); + const hash = crypto.createHash("sha256"); + hash.end("abc"); + result.stream = [hash.read().toString("hex"), hash.digest("hex")]; + result.final = [ + attempt(() => hash.copy()), + attempt(() => hash.update("x")), + attempt(() => hash.digest()), + ]; + const hmac = crypto.Hmac( + "sha256", + crypto.createSecretKey(new Uint8Array([1, 2, 3])), + ); + hmac.end("abc"); + result.hmacStream = [ + hmac.read().toString("hex"), + hmac.digest("hex"), + hmac.digest("hex"), + attempt(() => hmac.update("x")), + ]; + return result; + }, fsIoContracts: async function ({ fs, process }) { const path = "/tmp/io-contract", result = {}; diff --git a/vitest.config.ts b/vitest.config.ts index b4e0994a..7d74a2a4 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -68,6 +68,8 @@ export default defineConfig({ "src/browser/fetch/scope.spec.ts", "src/compat/net/sockets.spec.ts", "src/compat/tls/upgrade.spec.ts", + "src/compat/crypto/hash.spec.ts", + "src/compat/crypto/hash-types.spec.ts", "src/compat/fs/writable.spec.ts", "src/compat/fs/glob.spec.ts", "src/compat/fs/directory.spec.ts", From 561f97d24b49398aa08b6e37abd75b66ce750f57 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 02:09:56 +0900 Subject: [PATCH 04/30] feat: implement Workers cipher contracts with BoringSSL --- .../node-compat/expansion-native.mjs | 40 +- scripts/build-codecs.mjs | 13 + scripts/build-native-cipher.mjs | 119 +++ scripts/test-package.mjs | 1 + src/compat/crypto/LICENSE-3RD-PARTY.md | 6 + src/compat/crypto/LICENSE.boringssl | 238 ++++++ src/compat/crypto/cipher-handle.ts | 349 +++++++++ src/compat/crypto/cipher.spec.ts | 55 ++ src/compat/crypto/index.ts | 5 - src/compat/crypto/metadata.ts | 80 +++ src/compat/crypto/native-cipher.build.json | 26 + src/compat/crypto/native-cipher.c | 38 + src/compat/crypto/native-cipher.ts | 77 ++ src/compat/crypto/native-cipher.wasm | Bin 0 -> 140935 bytes src/compat/crypto/symmetric.ts | 504 +++++++++++-- src/compat/crypto/upstream-cipher.fixture.mjs | 678 ++++++++++++++++++ src/compat/expansion/README.md | 14 +- src/compat/expansion/index.spec.mjs | 189 +++++ vitest.config.ts | 8 +- 19 files changed, 2358 insertions(+), 82 deletions(-) create mode 100644 scripts/build-native-cipher.mjs create mode 100644 src/compat/crypto/LICENSE.boringssl create mode 100644 src/compat/crypto/cipher-handle.ts create mode 100644 src/compat/crypto/cipher.spec.ts create mode 100644 src/compat/crypto/native-cipher.build.json create mode 100644 src/compat/crypto/native-cipher.c create mode 100644 src/compat/crypto/native-cipher.ts create mode 100755 src/compat/crypto/native-cipher.wasm create mode 100644 src/compat/crypto/upstream-cipher.fixture.mjs diff --git a/demo/tests/experiments/node-compat/expansion-native.mjs b/demo/tests/experiments/node-compat/expansion-native.mjs index 9568196f..4dbe1f71 100644 --- a/demo/tests/experiments/node-compat/expansion-native.mjs +++ b/demo/tests/experiments/node-compat/expansion-native.mjs @@ -158,6 +158,9 @@ export const runTls = async ({port,upgradePort}) => { const source = compiled.outputFiles[0].text; const hash = createHash("sha256").update(source).digest("hex"); await writeFile(`${artifacts}/candidate.js`, source); +const harnessSource = await readFile(fileURLToPath(import.meta.url)); +const harnessSha256 = createHash("sha256").update(harnessSource).digest("hex"); +await writeFile(`${artifacts}/harness.mjs`, harnessSource); await writeFile( `${artifacts}/inputs.json`, JSON.stringify( @@ -168,6 +171,7 @@ await writeFile( device, tarball, tarballSha256, + harnessSha256, consumerEvidence, cases: Object.keys(eventCases), }, @@ -379,12 +383,16 @@ const source = ${JSON.stringify(source)}; const names = ${JSON.stringify(Object.keys(eventCases))}; const report = async value => {await fetch(${JSON.stringify(backend.url.origin + "/__acceptance/report")}, {method:'POST', mode:'no-cors', headers:{'content-type':'text/plain'}, body:JSON.stringify(value)});}; const results = {}, measurements = {}; +// One immutable Blob avoids accumulating copies of the large fixture payload. +// URL fragments retain separate module identities for cold per-case measurements. +let moduleURL, activeCase; try { + moduleURL = URL.createObjectURL(new Blob([source], {type:'text/javascript'})); for (const mode of ['page', 'worker']) { results[mode] = {}; measurements[mode] = {}; for (const name of names) { - const url = URL.createObjectURL(new Blob([source], {type:'text/javascript'})); - try { + activeCase = {mode,name}; + const url = moduleURL+'#'+mode+'-'+name; if (mode === 'page') { const api = await import(/* @vite-ignore */ url); results[mode][name] = JSON.parse(JSON.stringify(await api.run(name))); measurements[mode][name] = api.measurements[name]; } else { const workerURL = URL.createObjectURL(new Blob(['import {run,measurements} from ' + JSON.stringify(url) + '; self.onmessage = async ({data}) => {try {self.postMessage({value:JSON.parse(JSON.stringify(await run(data))),measurements:measurements[data]});} catch(error) {self.postMessage({error:String(error)});}};'], {type:'text/javascript'})); @@ -393,17 +401,18 @@ try { results[mode][name] = await new Promise((resolve,reject) => { const timer = setTimeout(() => reject(new Error('Worker timeout: '+name)), 8000); worker.onmessage = ({data}) => {clearTimeout(timer); if(data.error)reject(new Error(data.error));else {measurements[mode][name]=data.measurements;resolve(data.value);}}; - worker.onerror = event => {clearTimeout(timer);reject(new Error(event.message || 'Worker error'));}; + worker.onerror = event => {clearTimeout(timer);reject(new Error('Worker error in '+name+': '+JSON.stringify({message:event.message,filename:event.filename,line:event.lineno,column:event.colno,type:event.type})));}; worker.postMessage(name); }); } finally {worker.terminate(); URL.revokeObjectURL(workerURL);} } - } finally {URL.revokeObjectURL(url);} } } - const tlsURL=URL.createObjectURL(new Blob([source],{type:'text/javascript'})); + activeCase={mode:'page',name:'nativeTls'}; + const tlsURL=moduleURL+'#native-tls'; const tlsApi=await import(/* @vite-ignore */ tlsURL); const nativeTls={page:await tlsApi.runTls(${JSON.stringify(tlsPorts)})}; + activeCase={mode:'worker',name:'nativeTls'}; const workerURL=URL.createObjectURL(new Blob(['import {runTls, initializeWorkerNetwork} from '+JSON.stringify(tlsURL)+'; self.onmessage=async ({data})=>{try {await initializeWorkerNetwork();self.postMessage({tls:await runTls(data)});}catch(e){self.postMessage({error:String(e)});}};'],{type:'text/javascript'})); const tlsWorker=new Worker(workerURL,{type:'module'}); const relay=tlsApi.createNetworkRelay(tlsWorker); @@ -412,13 +421,15 @@ try { const timer=setTimeout(()=>reject(new Error('Worker TLS timeout')),20000); tlsWorker.onmessage=({data})=>{if(data.tls || data.error){clearTimeout(timer);data.error?reject(new Error(data.error)):resolve(data.tls);}}; tlsWorker.postMessage(${JSON.stringify(tlsPorts)}); - });} finally {tlsWorker.terminate();await relay.dispose();URL.revokeObjectURL(workerURL);URL.revokeObjectURL(tlsURL);} + });} finally {tlsWorker.terminate();await relay.dispose();URL.revokeObjectURL(workerURL);} await report({stage:'complete', measurements, nativeTls, hash:${JSON.stringify(hash)}, results}); document.body.textContent = 'Compatibility contracts complete'; -} catch(error) {await report({stage:'failure', message:String(error), stack:error.stack});document.body.textContent=String(error);} +} catch(error) {await report({stage:'failure', activeCase, completed:{page:Object.keys(results.page??{}),worker:Object.keys(results.worker??{})}, message:String(error), stack:error.stack});document.body.textContent=String(error);} +finally {if(moduleURL) URL.revokeObjectURL(moduleURL);} } main(); `; +await writeFile(`${artifacts}/bootstrap.js`, bootstrapCode); await runNativeAcceptance({ backend, artifacts, @@ -451,6 +462,21 @@ await runNativeAcceptance({ upgradeUntrusted: { accepted: false, received: false }, upgradeInvalid: { accepted: false, received: false }, }); + // Fragmented module URLs must still instantiate a fresh backend per case. + for (const mode of ["page", "worker"]) + for (const name of [ + "cryptoCipherAlgorithms", + "cryptoCipherStates", + "cryptoPbkdfContracts", + "cryptoDigestContracts", + ]) + for (const kind of ["Module", "Instance"]) + assert.ok( + completed.measurements[mode][name].wasm.some( + (entry) => entry.kind === kind, + ), + `${mode}/${name}: missing cold Wasm ${kind}`, + ); assert.deepEqual(upgradeStats, { connections: 14, requests: 10, diff --git a/scripts/build-codecs.mjs b/scripts/build-codecs.mjs index a3bedfd2..1f448858 100644 --- a/scripts/build-codecs.mjs +++ b/scripts/build-codecs.mjs @@ -8,6 +8,10 @@ for (const [asset, manifest] of [ "src/compat/zlib/native-zlib.wasm", "src/compat/zlib/native-zlib.build.json", ], + [ + "src/compat/crypto/native-cipher.wasm", + "src/compat/crypto/native-cipher.build.json", + ], [ "src/compat/crypto/native-crypto.wasm", "src/compat/crypto/native-crypto.build.json", @@ -88,3 +92,12 @@ await copyFile( "src/compat/zlib/classic-base.d.cts", "dist/compat/zlib/classic-base.d.cts", ); + +await build({ + entryPoints: ["src/compat/crypto/native-cipher.ts"], + outfile: "dist/compat/crypto/native-cipher.js", + bundle: true, + format: "esm", + target: "es2022", + loader: { ".wasm": "binary" }, +}); diff --git a/scripts/build-native-cipher.mjs b/scripts/build-native-cipher.mjs new file mode 100644 index 00000000..d07347a7 --- /dev/null +++ b/scripts/build-native-cipher.mjs @@ -0,0 +1,119 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import { resolve, join } from "node:path"; +import { createHash } from "node:crypto"; +import assert from "node:assert/strict"; +import { format, resolveConfig } from "prettier"; +const source = resolve("artifacts/upstream/boringssl"), + sdk = resolve(process.env.CODEC_EMSDK ?? "artifacts/toolchains/emsdk"), + build = resolve("artifacts/upstream/boringssl-build"); +const revision = (directory) => + execFileSync("git", ["rev-parse", "HEAD"], { + cwd: directory, + encoding: "utf8", + }).trim(); +const revisions = { emsdk: revision(sdk), boringssl: revision(source) }; +assert.equal(revisions.emsdk, "e4fe26ef59168ff44f4c23c466e497bf60b3411e"); +assert.equal(revisions.boringssl, "427ec40cc8edc545253289232e885708c409e5ea"); +assert.equal( + execFileSync("git", ["diff", "HEAD", "--name-only"], { + cwd: source, + encoding: "utf8", + }).trim(), + "", +); +const compiler = join(sdk, "upstream/emscripten"); +assert.equal( + execFileSync("git", ["diff", "HEAD", "--name-only"], { + cwd: sdk, + encoding: "utf8", + }).trim(), + "", +); +const emccVersion = execFileSync(join(compiler, "emcc"), ["--version"], { + encoding: "utf8", +}).split("\n")[0]; +assert.equal( + emccVersion, + "emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.20 (6913738ec5371a88c4af5a80db0ab42bad3de681)", +); + +const cmake = resolve("artifacts/toolchains/boringssl-build/bin/cmake"), + ninja = resolve("artifacts/toolchains/boringssl-build/bin/ninja"); +const definitions = + "-DOPENSSL_NO_THREADS_CORRUPT_MEMORY_AND_LEAK_SECRETS_IF_THREADED -DOPENSSL_NO_FILESYSTEM -DOPENSSL_NO_POSIX_IO -DOPENSSL_NO_SOCK"; +const configure = [ + "-S", + source, + "-B", + build, + "-G", + "Ninja", + "-DCMAKE_MAKE_PROGRAM=" + ninja, + "-DCMAKE_TOOLCHAIN_FILE=" + + join(compiler, "cmake/Modules/Platform/Emscripten.cmake"), + "-DCMAKE_BUILD_TYPE=Release", + "-DBUILD_TESTING=OFF", + "-DOPENSSL_NO_ASM=1", + "-DCMAKE_C_FLAGS=" + definitions, + "-DCMAKE_CXX_FLAGS=" + definitions, +]; +if (!process.argv.includes("--link-only")) { + assert.match( + execFileSync(cmake, ["--version"], { encoding: "utf8" }), + /^cmake version 4\.2\.0\b/, + ); + assert.equal( + execFileSync(ninja, ["--version"], { encoding: "utf8" }).trim(), + "1.13.0", + ); + execFileSync(cmake, configure, { stdio: "inherit" }); + execFileSync( + cmake, + ["--build", build, "--target", "crypto", "decrepit", "-j4"], + { stdio: "inherit" }, + ); +} +const binding = "src/compat/crypto/native-cipher.c", + output = "src/compat/crypto/native-cipher.wasm"; +const flags = [ + "-O3", + "-sSTANDALONE_WASM=1", + "-sALLOW_MEMORY_GROWTH=1", + "-sFILESYSTEM=0", + "-sASSERTIONS=0", + "-sMALLOC=emmalloc", + "-sABORTING_MALLOC=0", + "--no-entry", + '-sEXPORTED_FUNCTIONS=["_malloc","_free"]', +]; +const libraries = ["libdecrepit.a", "libcrypto.a"]; +execFileSync( + join(compiler, "emcc"), + [ + binding, + "-I" + join(source, "include"), + ...libraries.map((n) => join(build, n)), + ...flags, + "-o", + output, + ], + { stdio: "inherit" }, +); +const sha = (p) => createHash("sha256").update(readFileSync(p)).digest("hex"); +const manifest = "src/compat/crypto/native-cipher.build.json"; +writeFileSync( + manifest, + await format( + JSON.stringify({ + revisions, + toolVersions: { cmake: "4.2.0", ninja: "1.13.0", emcc: emccVersion }, + definitions, + flags, + libraries, + bindingSha256: sha(binding), + sha256: sha(output), + }), + { ...(await resolveConfig(manifest)), filepath: manifest }, + ), +); diff --git a/scripts/test-package.mjs b/scripts/test-package.mjs index 3b24e537..136c63f9 100644 --- a/scripts/test-package.mjs +++ b/scripts/test-package.mjs @@ -113,6 +113,7 @@ for (const notice of [ (name) => `dist/compat/crypto/LICENSE.${name}`, ), "dist/compat/crypto/LICENSE-APACHE", + "dist/compat/crypto/LICENSE.boringssl", "dist/compat/crypto/LICENSE-3RD-PARTY.md", "dist/compat/zlib/LICENSE-APACHE", "dist/compat/zlib/LICENSE-3RD-PARTY.md", diff --git a/src/compat/crypto/LICENSE-3RD-PARTY.md b/src/compat/crypto/LICENSE-3RD-PARTY.md index 0924f2c0..46e266ea 100644 --- a/src/compat/crypto/LICENSE-3RD-PARTY.md +++ b/src/compat/crypto/LICENSE-3RD-PARTY.md @@ -26,3 +26,9 @@ Imports use the owned adapters. Latin-1 fixture expectations written as literal caret notation upstream are decoded using independently verified digest bytes; comments identify those adaptations. Float16 input-brand cases require the engine's intrinsic; native evidence records its availability. + +The Cipheriv/Decipheriv frontend is adapted from `src/node/internal/crypto_cipher.ts` +and lifecycle behavior from `src/workerd/api/node/crypto.c++` at that same workerd +revision. The cipher fixture retains the upstream `crypto_cipher-test.js` notices. +BoringSSL commit `427ec40cc8edc545253289232e885708c409e5ea` supplies the bundled cipher +primitives; its license and bundled third-party notices are in `LICENSE.boringssl`. diff --git a/src/compat/crypto/LICENSE.boringssl b/src/compat/crypto/LICENSE.boringssl new file mode 100644 index 00000000..37a5b743 --- /dev/null +++ b/src/compat/crypto/LICENSE.boringssl @@ -0,0 +1,238 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Licenses for support code +------------------------- + +Parts of the TLS test suite are under the Go license. This code is not included +in BoringSSL (i.e. libcrypto and libssl) when compiled, however, so +distributing code linked against BoringSSL does not trigger this license: + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/compat/crypto/cipher-handle.ts b/src/compat/crypto/cipher-handle.ts new file mode 100644 index 00000000..0b2f7eb5 --- /dev/null +++ b/src/compat/crypto/cipher-handle.ts @@ -0,0 +1,349 @@ +// Workers CipherHandle/AeadHandle lifecycle, pinned to workerd +// 5358e3d337336eaf696a48bc1563d808b10feba5 (crypto.c++), Apache-2.0. +import { library, inputs, output, size } from "./native-cipher.js"; +import { secretMaterial } from "./keys.js"; +import { isAnyBuffer } from "./validation.js"; +const aeadNames = new Set([ + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "aes-128-eax", + "aes-256-eax", + "aes-128-ccm-matter", + "aes-128-ccm-bluetooth", + "aes-128-ccm-bluetooth-8", + "aes-128-gcm-randnonce", + "aes-256-gcm-randnonce", + "aes-128-gcm-siv", + "aes-256-gcm-siv", + "aes-128-ctr-hmac-sha256", + "aes-256-ctr-hmac-sha256", + "xchacha20-poly1305", + "chacha20-poly1305", +]); +const finalized = "Cipher/decipher context has already been finalized"; +function requireValue(value: unknown, message: string): asserts value { + if (!value) throw new Error(message); +} +function bytes(value: ArrayBuffer | ArrayBufferView): Uint8Array { + if (isAnyBuffer(value)) return new Uint8Array(value); + if (ArrayBuffer.isView(value)) + return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + throw new TypeError("Expected an ArrayBuffer or ArrayBufferView"); +} +const released = new FinalizationRegistry<{ pointer: number; aead: boolean }>( + ({ pointer, aead }) => + library()[aead ? "cipher_aead_free" : "cipher_free"](pointer), +); +const validTag = (n: number) => n === 4 || n === 8 || (n >= 12 && n <= 16); +export class CipherHandle { + private pointer = 0; + private aead = false; + private auth = false; + private mode = 0; + private block = 1; + private iv: Uint8Array; + private aad?: Uint8Array; + private tag?: Uint8Array; + private tagLength?: number; + private tagPassed = false; + private updated = false; + private decrypt: boolean; + constructor( + direction: number, + algorithm: string, + key: unknown, + iv: ArrayBuffer | ArrayBufferView, + tagLength?: number, + ) { + this.decrypt = direction === 1; + this.iv = bytes(iv).slice(); + this.tagLength = tagLength; + const material = secretMaterial(key), + raw = bytes(material); + try { + inputs( + [algorithm, algorithm.toLowerCase().replaceAll("-", "_"), raw, this.iv], + (api, [name, aeadName, k, v]) => { + const cipher = api.cipher_lookup(name); + if (cipher) { + this.mode = api.cipher_info(cipher, 3); + this.auth = this.mode === 6; + this.block = api.cipher_info(cipher, 2); + const expected = api.cipher_info(cipher, 1); + requireValue( + !(expected && !this.iv.length) && + !(!this.auth && this.iv.length && this.iv.length !== expected), + "Invalid initialization vector", + ); + this.pointer = api.cipher_new(); + requireValue( + this.pointer, + "Failed to create cipher/decipher context", + ); + requireValue( + api.cipher_init(this.pointer, cipher, 0, 0, +!this.decrypt), + "Failed to initialize cipher/decipher context", + ); + if (this.auth) { + requireValue( + api.cipher_iv_size(this.pointer, this.iv.length), + "Invalid initialization vector", + ); + requireValue( + tagLength === undefined || validTag(tagLength), + "Invalid authentication tag length", + ); + } + requireValue( + api.cipher_key_size(this.pointer, raw.length), + "Invalid key length", + ); + requireValue( + api.cipher_init(this.pointer, 0, k, v, +!this.decrypt), + "Failed to initialize cipher/cipher context", + ); + } else { + const aead = aeadNames.has(algorithm.toLowerCase()) + ? api.cipher_aead(aeadName) + : 0; + requireValue(aead, "Unknown or unsupported cipher: " + algorithm); + this.aead = true; + this.auth = true; + this.mode = algorithm.toLowerCase().includes("gcm") + ? 6 + : algorithm.toLowerCase().includes("ccm") + ? 7 + : 0; + requireValue( + this.iv.length === api.cipher_aead_info(aead, 1), + "Invalid initialization vector", + ); + this.pointer = api.cipher_aead_new( + aead, + k, + raw.length, + tagLength ?? 0, + ); + requireValue( + this.pointer, + "Failed to initialize AEAD cipher/decipher context", + ); + if (this.mode === 6) + requireValue( + tagLength === undefined || validTag(tagLength), + "Invalid authentication tag length", + ); + else if (tagLength === undefined) { + requireValue( + algorithm.toLowerCase() === "chacha20-poly1305", + "The auth tag length is required for cipher " + algorithm, + ); + this.tagLength = 16; + } + } + }, + ); + released.register(this, { pointer: this.pointer, aead: this.aead }, this); + } catch (error) { + this.dispose(); + throw error; + } + } + private dispose() { + if (this.pointer) { + library()[this.aead ? "cipher_aead_free" : "cipher_free"](this.pointer); + this.pointer = 0; + released.unregister(this); + } + this.iv.fill(0); + this.aad?.fill(0); + } + private passTag() { + if (this.decrypt && this.auth && !this.tagPassed) { + this.tagPassed = true; + requireValue(this.tag, "No auth tag provided"); + inputs([this.tag], (api, [p]) => + requireValue( + api.cipher_tag(this.pointer, 0, this.tag!.length, p), + "Failed to set auth tag", + ), + ); + } + } + update(value: ArrayBuffer | ArrayBufferView) { + if (this.aead) + requireValue( + !this.updated, + "update() can only be invoked once on an AEAD", + ); + requireValue(this.pointer, finalized); + const data = bytes(value); + requireValue(data.length <= 0x7fffffff, "Data too large"); + if (this.mode === 7) + requireValue(data.length <= 65535, "Invalid message length"); + requireValue(data.length + this.block <= 0x7fffffff, "Data too large"); + if (this.aead) { + if (this.decrypt) requireValue(this.tag, "No auth tag provided"); + else this.tagLength ??= 16; + const tagLength = this.decrypt ? this.tag!.length : this.tagLength!; + const result = inputs( + [ + data, + this.iv, + this.aad ?? new Uint8Array(), + this.tag ?? tagLength, + data.length, + 4, + ], + (api, [d, v, a, t, o, n]) => { + const ok = this.decrypt + ? api.cipher_aead_open( + this.pointer, + o, + v, + this.iv.length, + d, + data.length, + t, + tagLength, + a, + this.aad?.length ?? 0, + ) + : api.cipher_aead_seal( + this.pointer, + o, + t, + n, + tagLength, + v, + this.iv.length, + d, + data.length, + a, + this.aad?.length ?? 0, + ); + // Workers exposes this backend failure as an internal Error with a per-run reference. + if (!ok && api.cipher_bad_decrypt()) + throw new DOMException( + "Decryption failed: Incorrect key/IV or corrupted ciphertext/authentication tag provided.", + "OperationError", + ); + requireValue(ok, "internal error"); + if (!this.decrypt) this.tag = output(api, t, size(api, n)); + return output(api, o, data.length); + }, + ); + this.updated = true; + return result; + } + this.passTag(); + return inputs([data, data.length + this.block, 4], (api, [d, o, n]) => { + api.cipher_update(this.pointer, o, n, d, data.length); + const length = size(api, n); + requireValue(length <= data.length + this.block, "Invalid buffer length"); + return output(api, o, length); + }); + } + final() { + requireValue(this.pointer, finalized); + if (this.aead) { + this.dispose(); + return new Uint8Array(); + } + this.passTag(); + const result = inputs([this.block, 4], (api, [o, n]) => { + requireValue( + api.cipher_final(this.pointer, o, n), + "Authentication failed", + ); + const length = size(api, n); + requireValue(length <= this.block, "Invalid buffer length"); + if (!this.decrypt && this.auth) { + this.tagLength ??= 16; + this.tag = inputs([this.tagLength], (api, [t]) => { + requireValue( + api.cipher_tag(this.pointer, 1, this.tagLength!, t), + "Authentication failed", + ); + return output(api, t, this.tagLength!); + }); + } + return output(api, o, length); + }); + this.dispose(); + return result; + } + setAAD(value: ArrayBuffer | ArrayBufferView, plaintextLength?: number) { + requireValue(this.pointer, finalized); + requireValue(this.auth, "Cipher does not support authenticated mode"); + const data = bytes(value); + if (this.aead) { + if (this.mode === 7) { + requireValue( + plaintextLength !== undefined, + "options.plaintextLength is required for CCM mode with AAD", + ); + requireValue(plaintextLength! <= 65535, "Data too large"); + if (this.decrypt) requireValue(this.tag, "No auth tag provided"); + } + this.aad?.fill(0); + this.aad = data.slice(); + } else + inputs([data, 4], (api, [p, n]) => + requireValue( + api.cipher_update(this.pointer, 0, n, p, data.length), + "Failed to set AAD", + ), + ); + } + setAutoPadding(value: boolean) { + requireValue(this.pointer, finalized); + requireValue(!this.aead, "Setting autopadding is not supported on AEADs"); + requireValue( + library().cipher_padding(this.pointer, +value), + "Failed to set autopadding", + ); + } + setAuthTag(value: ArrayBuffer | ArrayBufferView) { + if (!isAnyBuffer(value) && !ArrayBuffer.isView(value)) + throw new TypeError( + `Failed to execute 'setAuthTag' on '${this.aead ? "AeadHandle" : "CipherHandle"}': parameter 1 is not of type 'BufferSource'.`, + ); + requireValue(this.pointer, finalized); + requireValue(this.auth, "Cipher does not support authenticated mode"); + requireValue( + this.decrypt, + "Setting auth tag only support in decipher mode", + ); + requireValue(!this.tag, "Auth tag is already set"); + const data = bytes(value); + requireValue(data.length <= 0x7fffffff, "Auth tag is too big"); + requireValue( + this.mode === 6 + ? (this.tagLength === undefined || this.tagLength === data.length) && + validTag(data.length) + : this.tagLength === data.length, + "Invalid authentication tag length", + ); + this.tagLength = data.length; + this.tag = data.slice(); + } + getAuthTag() { + requireValue( + !this.pointer, + "Auth tag is only available once cipher context has been finalized", + ); + requireValue( + !this.decrypt, + "Getting the auth tag is only support for cipher", + ); + const tag = this.tag ?? new Uint8Array(); + this.tag = undefined; + return tag; + } +} +export default { + newHandle: (...args: ConstructorParameters) => + new CipherHandle(...args), +}; diff --git a/src/compat/crypto/cipher.spec.ts b/src/compat/crypto/cipher.spec.ts new file mode 100644 index 00000000..fb19c463 --- /dev/null +++ b/src/compat/crypto/cipher.spec.ts @@ -0,0 +1,55 @@ +import { test } from "vitest"; +import { generateKeyPairSync } from "node:crypto"; +// @ts-expect-error Vendored upstream JavaScript fixture. +import * as cases from "./upstream-cipher.fixture.mjs"; +const keys = generateKeyPairSync("rsa", { + modulusLength: 2048, + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type: "pkcs8", format: "pem" }, +}); +const env = { + "rsa_public.pem": keys.publicKey, + "rsa_private.pem": keys.privateKey, +}; +for (const [name, fixture] of Object.entries(cases)) + test("pinned Workers cipher: " + name, () => + ( + fixture as { + test: (controller: unknown, bindings: typeof env) => unknown; + } + ).test(null, env), + ); + +import { expect } from "vitest"; +import { Buffer } from "node:buffer"; +import { + createCipheriv, + createDecipheriv, + Cipheriv, + Decipheriv, +} from "./symmetric.js"; +test("cipher declarations support authentication options and encoded output", () => { + const key = new Uint8Array(16), + iv = new Uint8Array(16); + const cipher = createCipheriv("aes-128-eax", key, iv, { authLengthTag: 16 }); + const encrypted: Buffer = cipher.update("hello", "utf8"); + const last: Buffer = cipher.final(); + const tag: Buffer = cipher.getAuthTag(); + const decipher = createDecipheriv("aes-128-eax", key, iv, { + authLengthTag: 16, + }); + decipher.setAuthTag(tag); + const invalidTag = () => { + // @ts-expect-error Workers accepts only BufferSource tags, even with encoding. + decipher.setAuthTag("00", "hex"); + }; + void invalidTag; + const plain: string = decipher.update(encrypted, undefined, "utf8"); + const end: string = decipher.final("utf8"); + expect(plain + end).toBe("hello"); + expect(last.length).toBe(0); + const ctor = new Cipheriv("aes-128-eax", key, iv, { authLengthTag: 16 }); + const dec = new Decipheriv("aes-128-eax", key, iv, { authLengthTag: 16 }); + ctor.final(); + dec.final(); +}); diff --git a/src/compat/crypto/index.ts b/src/compat/crypto/index.ts index 3b4dbb0c..f521b118 100644 --- a/src/compat/crypto/index.ts +++ b/src/compat/crypto/index.ts @@ -46,11 +46,6 @@ export const { pseudoRandomBytes, rng, randomBytes, - Cipher, - createCipher, - Decipher, - createDecipher, - getCiphers, listCiphers, DiffieHellmanGroup, createDiffieHellmanGroup, diff --git a/src/compat/crypto/metadata.ts b/src/compat/crypto/metadata.ts index a43d25b4..94cf3cfa 100644 --- a/src/compat/crypto/metadata.ts +++ b/src/compat/crypto/metadata.ts @@ -26,3 +26,83 @@ export function setFips(_enabled: boolean): never { // not Workers' FIPS backend. That capability remains an explicit boundary. export { getCipherInfo } from "./cipher-info.js"; + +// Includes the duplicate GCM aliases exposed by the pinned backend. +const cipherNames = [ + "AES-128-CBC", + "AES-192-CBC", + "AES-256-CBC", + "AES-128-CTR", + "AES-192-CTR", + "AES-256-CTR", + "AES-128-ECB", + "AES-192-ECB", + "AES-256-ECB", + "AES-128-OFB", + "AES-192-OFB", + "AES-256-OFB", + "AES-128-GCM", + "AES-192-GCM", + "AES-256-GCM", + "DES-CBC", + "DES-ECB", + "DES-EDE", + "DES-EDE-CBC", + "DES-EDE3-CBC", + "RC2-CBC", + "RC4", + "aes-128-cbc", + "aes-192-cbc", + "aes-256-cbc", + "aes-128-ctr", + "aes-192-ctr", + "aes-256-ctr", + "aes-128-ecb", + "aes-192-ecb", + "aes-256-ecb", + "aes-128-ofb", + "aes-192-ofb", + "aes-256-ofb", + "aes-128-gcm", + "aes-192-gcm", + "aes-256-gcm", + "des-cbc", + "des-ecb", + "des-ede", + "des-ede-cbc", + "des-ede3-cbc", + "rc2-cbc", + "rc4", + "aes-256-eax", + "aes-128-ccm-matter", + "aes-128-ccm-bluetooth-8", + "aes-128-gcm-randnonce", + "aes-256-gcm-randnonce", + "aes-128-gcm-siv", + "aes-128-ccm-bluetooth", + "aes-256-ctr-hmac-sha256", + "aes-128-ctr-hmac-sha256", + "xchacha20-poly1305", + "aes-256-gcm-siv", + "aes-128-eax", + "chacha20-poly1305", + "aes-192-gcm", + "aes-256-gcm", + "aes-128-gcm", +]; +export function getCiphers(): string[] { + return [...cipherNames]; +} + +export function Cipher(): never { + return unavailable("Cipher"); +} +export function Decipher(): never { + return unavailable("Decipher"); +} +export function createCipher(): never { + return unavailable("createCipher"); +} +export function createDecipher(): never { + return unavailable("createDecipher"); +} diff --git a/src/compat/crypto/native-cipher.build.json b/src/compat/crypto/native-cipher.build.json new file mode 100644 index 00000000..41c16afd --- /dev/null +++ b/src/compat/crypto/native-cipher.build.json @@ -0,0 +1,26 @@ +{ + "revisions": { + "emsdk": "e4fe26ef59168ff44f4c23c466e497bf60b3411e", + "boringssl": "427ec40cc8edc545253289232e885708c409e5ea" + }, + "toolVersions": { + "cmake": "4.2.0", + "ninja": "1.13.0", + "emcc": "emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.20 (6913738ec5371a88c4af5a80db0ab42bad3de681)" + }, + "definitions": "-DOPENSSL_NO_THREADS_CORRUPT_MEMORY_AND_LEAK_SECRETS_IF_THREADED -DOPENSSL_NO_FILESYSTEM -DOPENSSL_NO_POSIX_IO -DOPENSSL_NO_SOCK", + "flags": [ + "-O3", + "-sSTANDALONE_WASM=1", + "-sALLOW_MEMORY_GROWTH=1", + "-sFILESYSTEM=0", + "-sASSERTIONS=0", + "-sMALLOC=emmalloc", + "-sABORTING_MALLOC=0", + "--no-entry", + "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" + ], + "libraries": ["libdecrepit.a", "libcrypto.a"], + "bindingSha256": "c9595546b91a40c78879f0eaad4ab3b8ac0cfb46faf0b9c948ad738dc20022a0", + "sha256": "67e892b2244c2fdd66a3106187cefa9f71aeb119d9bdbb84668490b46dc38e15" +} diff --git a/src/compat/crypto/native-cipher.c b/src/compat/crypto/native-cipher.c new file mode 100644 index 00000000..5a8e0c7a --- /dev/null +++ b/src/compat/crypto/native-cipher.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include +#define API EMSCRIPTEN_KEEPALIVE +#define AEAD(name) if(!strcmp(algorithm,#name)) return EVP_aead_##name(); +API const EVP_AEAD *cipher_aead(const char *algorithm) { + AEAD(aes_128_gcm) AEAD(aes_192_gcm) AEAD(aes_256_gcm) + AEAD(aes_128_eax) AEAD(aes_256_eax) + AEAD(aes_128_ccm_matter) AEAD(aes_128_ccm_bluetooth) AEAD(aes_128_ccm_bluetooth_8) + AEAD(aes_128_gcm_randnonce) AEAD(aes_256_gcm_randnonce) + AEAD(aes_128_gcm_siv) AEAD(aes_256_gcm_siv) + AEAD(aes_128_ctr_hmac_sha256) AEAD(aes_256_ctr_hmac_sha256) + AEAD(xchacha20_poly1305) AEAD(chacha20_poly1305) + return NULL; +} +API EVP_AEAD_CTX *cipher_aead_new(const EVP_AEAD *algorithm,const unsigned char *key,size_t size,size_t tag) {return EVP_AEAD_CTX_new(algorithm,key,size,tag);} +API void cipher_aead_free(EVP_AEAD_CTX *ctx) {EVP_AEAD_CTX_free(ctx);} +API int cipher_aead_seal(EVP_AEAD_CTX *ctx,unsigned char *out,unsigned char *tag,size_t *tag_size,size_t max_tag,const unsigned char *nonce,size_t nonce_size,const unsigned char *input,size_t size,const unsigned char *aad,size_t aad_size) {ERR_clear_error();return EVP_AEAD_CTX_seal_scatter(ctx,out,tag,tag_size,max_tag,nonce,nonce_size,input,size,NULL,0,aad,aad_size);} +API int cipher_aead_open(EVP_AEAD_CTX *ctx,unsigned char *out,const unsigned char *nonce,size_t nonce_size,const unsigned char *input,size_t size,const unsigned char *tag,size_t tag_size,const unsigned char *aad,size_t aad_size) {ERR_clear_error();return EVP_AEAD_CTX_open_gather(ctx,out,nonce,nonce_size,input,size,tag,tag_size,aad,aad_size);} +API int cipher_aead_info(const EVP_AEAD *a,int kind){return kind==0?EVP_AEAD_key_length(a):kind==1?EVP_AEAD_nonce_length(a):EVP_AEAD_max_overhead(a);} +API const EVP_CIPHER *cipher_lookup(const char *name){return EVP_get_cipherbyname(name);} +API EVP_CIPHER_CTX *cipher_new(void){return EVP_CIPHER_CTX_new();} +API void cipher_free(EVP_CIPHER_CTX *ctx){EVP_CIPHER_CTX_free(ctx);} +API int cipher_init(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher,const unsigned char *key,const unsigned char *iv,int encrypt){return EVP_CipherInit_ex(ctx,cipher,NULL,key,iv,encrypt);} +API int cipher_key_size(EVP_CIPHER_CTX *ctx,int size){return EVP_CIPHER_CTX_set_key_length(ctx,size);} +API int cipher_padding(EVP_CIPHER_CTX *ctx,int padding){return EVP_CIPHER_CTX_set_padding(ctx,padding);} +API int cipher_iv_size(EVP_CIPHER_CTX *ctx,int size){return EVP_CIPHER_CTX_ctrl(ctx,EVP_CTRL_AEAD_SET_IVLEN,size,NULL);} +API int cipher_tag(EVP_CIPHER_CTX *ctx,int get,int size,unsigned char *data){return EVP_CIPHER_CTX_ctrl(ctx,get?EVP_CTRL_AEAD_GET_TAG:EVP_CTRL_AEAD_SET_TAG,size,data);} +API int cipher_update(EVP_CIPHER_CTX *ctx,unsigned char *out,int *size,const unsigned char *data,int length){return EVP_CipherUpdate(ctx,out,size,data,length);} +API int cipher_final(EVP_CIPHER_CTX *ctx,unsigned char *out,int *size){return EVP_CipherFinal_ex(ctx,out,size);} +API unsigned long cipher_error(void){return ERR_get_error();} +API const char *cipher_reason(unsigned long code){return ERR_reason_error_string(code);} +API int cipher_info(const EVP_CIPHER *cipher,int kind){return kind==0?EVP_CIPHER_key_length(cipher):kind==1?EVP_CIPHER_iv_length(cipher):kind==2?EVP_CIPHER_block_size(cipher):EVP_CIPHER_mode(cipher);} + +API int cipher_bad_decrypt(void){uint32_t code=ERR_peek_last_error();int result=ERR_GET_LIB(code)==ERR_LIB_CIPHER && ERR_GET_REASON(code)==CIPHER_R_BAD_DECRYPT;ERR_clear_error();return result;} diff --git a/src/compat/crypto/native-cipher.ts b/src/compat/crypto/native-cipher.ts new file mode 100644 index 00000000..1280af3f --- /dev/null +++ b/src/compat/crypto/native-cipher.ts @@ -0,0 +1,77 @@ +import binary from "./native-cipher.wasm"; +type API = Record any> & { + memory: WebAssembly.Memory; +}; +let instance: API | undefined; +export function library(): API { + if (instance) return instance; + let api: API; + api = new WebAssembly.Instance( + new WebAssembly.Module(binary as BufferSource), + { + env: { emscripten_notify_memory_growth() {} }, + wasi_snapshot_preview1: { + random_get(pointer: number, length: number) { + for (let offset = 0; offset < length; offset += 65536) + globalThis.crypto.getRandomValues( + new Uint8Array( + api.memory.buffer, + pointer + offset, + Math.min(65536, length - offset), + ), + ); + return 0; + }, + fd_close() { + return 8; + }, + fd_write() { + return 8; + }, + fd_seek() { + return 8; + }, + }, + }, + ).exports as API; + api._initialize(); + instance = api; + return api; +} +export function inputs( + values: (string | Uint8Array | number)[], + operation: (api: API, pointers: number[]) => T, +): T { + const api = library(), + allocated: { pointer: number; length: number }[] = []; + try { + const pointers = values.map((value) => { + const data = + typeof value === "string" + ? new TextEncoder().encode(value + "\0") + : typeof value === "number" + ? undefined + : value; + const length = typeof value === "number" ? value : data!.byteLength; + const pointer = api.malloc(Math.max(1, length)); + if (!pointer) throw new RangeError("Memory limit exceeded"); + allocated.push({ pointer, length }); + const memory = new Uint8Array(api.memory.buffer, pointer, length); + if (data) memory.set(data); + else memory.fill(0); + return pointer; + }); + return operation(api, pointers); + } finally { + for (const { pointer, length } of allocated) { + new Uint8Array(api.memory.buffer, pointer, length).fill(0); + api.free(pointer); + } + } +} +export function output(api: API, pointer: number, length: number): Uint8Array { + return new Uint8Array(api.memory.buffer, pointer, length).slice(); +} +export function size(api: API, pointer: number): number { + return new DataView(api.memory.buffer).getUint32(pointer, true); +} diff --git a/src/compat/crypto/native-cipher.wasm b/src/compat/crypto/native-cipher.wasm new file mode 100755 index 0000000000000000000000000000000000000000..426709de17f01b86e6f74600f9536a98947f97b9 GIT binary patch literal 140935 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 literal 0 HcmV?d00001 diff --git a/src/compat/crypto/symmetric.ts b/src/compat/crypto/symmetric.ts index 0de34786..1171042e 100644 --- a/src/compat/crypto/symmetric.ts +++ b/src/compat/crypto/symmetric.ts @@ -1,91 +1,459 @@ -import { Buffer } from "node:buffer"; +// @ts-nocheck +// Copyright (c) 2017-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import cryptoImpl, { type CipherHandle } from "./cipher-handle.js"; import { Transform } from "../stream/transform.js"; -// @ts-expect-error dependency has no declarations -import original from "crypto-browserify/index.js"; -import { secretMaterial } from "./keys.js"; -const BaseTransform = - Transform as unknown as typeof import("node:stream").Transform; -const handles = new WeakMap(); -class CipherTransform extends BaseTransform { - constructor( - decrypt: boolean, +import type { + Transform as NodeTransform, + TransformOptions, + TransformCallback, +} from "node:stream"; +import { KeyObject, createSecretKey } from "./keys.js"; +import { + validateObject, + validateString, + validateUint32, +} from "../net/validators.js"; +import { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE } from "../net/errors.js"; +import { ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE } from "./sign-errors.js"; +import { isAnyBuffer as isAnyArrayBuffer } from "./validation.js"; +import { Buffer } from "node:buffer"; +const isArrayBufferView = ArrayBuffer.isView; + +export interface AADOptions { + plaintextLength?: number; + encoding?: string; +} + +const kHandle = Symbol("kHandle"); + +const CipherMode = { + CIPHER: 0, + DECIPHER: 1, +}; + +export interface Cipheriv extends NodeTransform { + [kHandle]: CipherHandle; + update( + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding?: string, + ): Buffer; + update( + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding: string | undefined, + outputEncoding: string, + ): string; + update( + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding?: string, + outputEncoding?: string, + ): string | Buffer; + final(): Buffer; + final(outputEncoding: string): string; + final(outputEncoding?: string): string | Buffer; + setAAD( + buffer: string | ArrayBuffer | ArrayBufferView, + options?: AADOptions, + ): this; + setAutoPadding(autoPadding?: boolean): this; + getAuthTag(): Buffer; +} + +export interface Decipheriv extends NodeTransform { + [kHandle]: CipherHandle; + update( + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding?: string, + ): Buffer; + update( + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding: string | undefined, + outputEncoding: string, + ): string; + update( + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding?: string, + outputEncoding?: string, + ): string | Buffer; + final(): Buffer; + final(outputEncoding: string): string; + final(outputEncoding?: string): string | Buffer; + setAAD( + buffer: string | ArrayBuffer | ArrayBufferView, + options?: AADOptions, + ): this; + setAutoPadding(autoPadding?: boolean): this; + setAuthTag(buffer: ArrayBuffer | ArrayBufferView): this; +} + +export interface CipherOptions extends TransformOptions { + authLengthTag?: number; +} + +function getSecretKey(key: unknown): unknown { + if (typeof CryptoKey !== "undefined" && key instanceof CryptoKey) + key = KeyObject.from(key); + if (key instanceof KeyObject) { + if (key.type !== "secret") + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); + return key; + } + if (isAnyArrayBuffer(key) || isArrayBufferView(key)) + return createSecretKey(key); + if (typeof key === "string") return createSecretKey(key, "utf8"); + throw new ERR_INVALID_ARG_TYPE( + "key", + ["string", "Buffer", "TypedArray", "DataView", "KeyObject", "CryptoKey"], + key, + ); +} + +function getIv( + iv: string | ArrayBuffer | ArrayBufferView | null, +): ArrayBuffer | ArrayBufferView { + if (isAnyArrayBuffer(iv) || isArrayBufferView(iv)) { + return iv; + } else if (typeof iv === "string") { + return Buffer.from(iv, "utf8"); + } + + throw new ERR_INVALID_ARG_TYPE( + "iv", + ["string", "Buffer", "TypedArray", "DataView", "null"], + iv, + ); +} + +export const Cipheriv = function ( + this: Cipheriv, + algorithm: string, + key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey, + iv: string | ArrayBuffer | ArrayBufferView | null, + options: CipherOptions = {}, +) { + validateString(algorithm, "algorithm"); + const secretKey = getSecretKey(key); + const ivBuf = getIv(iv); + + if (options.authLengthTag !== undefined) { + validateUint32(options.authLengthTag, "options.authLengthTag"); + } + this[kHandle] = cryptoImpl.newHandle( + CipherMode.CIPHER, + algorithm, + secretKey, + ivBuf, + options.authLengthTag, + ); + + Transform.call(this, options as TransformOptions); +} as unknown as { + new ( algorithm: string, - key: unknown, - iv: unknown, - options?: any, - ) { - super(options); - handles.set( - this, - (decrypt ? original.createDecipheriv : original.createCipheriv)( - algorithm, - secretMaterial(key), - iv, - options, - ), + key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey, + iv: string | ArrayBuffer | ArrayBufferView | null, + options?: CipherOptions, + ): Cipheriv; +}; +Object.setPrototypeOf(Cipheriv.prototype, Transform.prototype); +Object.setPrototypeOf(Cipheriv, Transform); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype.update = function ( + this: Cipheriv, + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding?: string, + outputEncoding?: string, +): string | Buffer { + let ret: ArrayBuffer; + if (typeof data === "string") { + if (inputEncoding === undefined) { + throw new ERR_INVALID_ARG_VALUE( + "inputEncoding", + inputEncoding, + "If inputEncoding is not provided then the data must be a Buffer", + ); + } + ret = this[kHandle].update(Buffer.from(data, inputEncoding)); + } else if (isAnyArrayBuffer(data)) { + ret = this[kHandle].update(data); + } else if (isArrayBufferView(data)) { + ret = this[kHandle].update(data); + } else { + throw new ERR_INVALID_ARG_TYPE( + "data", + ["string", "Buffer", "TypedArray", "DataView"], + data, ); } - update(data: unknown, inputEncoding?: string, outputEncoding?: string): any { - return handles.get(this).update(data, inputEncoding, outputEncoding); + + if (outputEncoding === undefined) { + return Buffer.from(ret); } - final(encoding?: string): any { - return handles.get(this).final(encoding); + + return Buffer.from(ret).toString(outputEncoding); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype.final = function ( + this: Cipheriv, + outputEncoding?: string, +): string | Buffer { + const ret = this[kHandle].final(); + if (outputEncoding === undefined) { + return Buffer.from(ret); } - setAutoPadding(value?: boolean): this { - handles.get(this).setAutoPadding(value); - return this; + return Buffer.from(ret).toString(outputEncoding); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype.setAAD = function ( + this: Cipheriv, + buffer: string | ArrayBuffer | ArrayBufferView, + options: AADOptions = {}, +): Cipheriv { + validateObject(options, "options"); + const { plaintextLength, encoding } = options; + if (plaintextLength !== undefined) { + validateUint32(plaintextLength, "options.plaintextLength"); } - setAAD(data: unknown, options?: any): this { - handles.get(this).setAAD(data, options); - return this; + if (encoding !== undefined) { + validateString(encoding, "options.encoding"); } - getAuthTag(): any { - return handles.get(this).getAuthTag(); + + if (typeof buffer === "string") { + this[kHandle].setAAD(Buffer.from(buffer, encoding), plaintextLength); + } else if (isAnyArrayBuffer(buffer) || isArrayBufferView(buffer)) { + this[kHandle].setAAD(buffer, plaintextLength); } - setAuthTag(data: unknown): this { - handles.get(this).setAuthTag(data); - return this; + return this; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype.setAutoPadding = function ( + this: Cipheriv, + autoPadding?: boolean, +): Cipheriv { + this[kHandle].setAutoPadding(autoPadding ? true : false); + return this; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype.getAuthTag = function (this: Cipheriv): Buffer | undefined { + const ret = this[kHandle].getAuthTag(); + if (ret === undefined) return undefined; + return Buffer.from(ret); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype._transform = function ( + this: Cipheriv, + chunk: string | Buffer | ArrayBufferView, + encoding: string, + callback: TransformCallback, +): void { + if (typeof chunk === "string") { + chunk = Buffer.from(chunk, encoding); } - override _transform(chunk: any, encoding: any, callback: any) { - try { - this.push(this.update(chunk, encoding)); - callback(); - } catch (error) { - callback(error); - } + this.push(Buffer.from(this[kHandle].update(chunk))); + callback(); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Cipheriv.prototype._flush = function ( + this: Cipheriv, + callback: TransformCallback, +): void { + this.push(Buffer.from(this[kHandle].final())); + callback(); +}; + +export const Decipheriv = function ( + this: Decipheriv, + algorithm: string, + key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey, + iv: string | ArrayBuffer | ArrayBufferView | null, + options: CipherOptions = {}, +) { + validateString(algorithm, "algorithm"); + const secretKey = getSecretKey(key); + const ivBuf = getIv(iv); + + if (options.authLengthTag !== undefined) { + validateUint32(options.authLengthTag, "options.authLengthTag"); } - override _flush(callback: any) { - try { - this.push(this.final()); - callback(); - } catch (error) { - callback(error); + + this[kHandle] = cryptoImpl.newHandle( + CipherMode.DECIPHER, + algorithm, + secretKey, + ivBuf, + options.authLengthTag, + ); + + Transform.call(this, options as TransformOptions); + return this; +} as unknown as { + new ( + algorithm: string, + key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey, + iv: string | ArrayBuffer | ArrayBufferView | null, + options?: CipherOptions, + ): Decipheriv; +}; + +Object.setPrototypeOf(Decipheriv.prototype, Transform.prototype); +Object.setPrototypeOf(Decipheriv, Transform); + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype.update = function ( + this: Decipheriv, + data: string | ArrayBuffer | ArrayBufferView, + inputEncoding?: string, + outputEncoding?: string, +): string | Buffer { + let ret: ArrayBuffer; + if (typeof data === "string") { + if (inputEncoding === undefined) { + throw new ERR_INVALID_ARG_VALUE( + "inputEncoding", + inputEncoding, + "If inputEncoding is not provided then the data must be a Buffer", + ); } + ret = this[kHandle].update(Buffer.from(data, inputEncoding)); + } else if (isAnyArrayBuffer(data)) { + ret = this[kHandle].update(data); + } else if (isArrayBufferView(data)) { + ret = this[kHandle].update(data); + } else { + throw new ERR_INVALID_ARG_TYPE( + "data", + ["string", "Buffer", "TypedArray", "DataView"], + data, + ); } -} -export class Cipheriv extends CipherTransform { - constructor(algorithm: string, key: unknown, iv: unknown, options?: any) { - super(false, algorithm, key, iv, options); + + if (outputEncoding === undefined) { + return Buffer.from(ret); } -} -export class Decipheriv extends CipherTransform { - constructor(algorithm: string, key: unknown, iv: unknown, options?: any) { - super(true, algorithm, key, iv, options); + + return Buffer.from(ret).toString(outputEncoding); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype.final = function ( + this: Decipheriv, + outputEncoding?: string, +): string | Buffer { + const ret = this[kHandle].final(); + if (outputEncoding === undefined) { + return Buffer.from(ret); } -} + return Buffer.from(ret).toString(outputEncoding); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype.setAAD = function ( + this: Decipheriv, + buffer: string | ArrayBuffer | ArrayBufferView, + options: AADOptions = {}, +): Decipheriv { + validateObject(options, "options"); + const { plaintextLength, encoding } = options; + if (plaintextLength !== undefined) { + validateUint32(plaintextLength, "options.plaintextLength"); + } + if (encoding !== undefined) { + validateString(encoding, "options.encoding"); + } + + if (typeof buffer === "string") { + this[kHandle].setAAD(Buffer.from(buffer, encoding), plaintextLength); + } else if (isAnyArrayBuffer(buffer) || isArrayBufferView(buffer)) { + this[kHandle].setAAD(buffer, plaintextLength); + } + return this; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype.setAutoPadding = function ( + this: Decipheriv, + autoPadding?: boolean, +): Decipheriv { + this[kHandle].setAutoPadding(autoPadding ? true : false); + return this; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype.setAuthTag = function ( + this: Decipheriv, + buffer: ArrayBuffer | ArrayBufferView, +): Decipheriv { + this[kHandle].setAuthTag(buffer); + return this; +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype._transform = function ( + this: Decipheriv, + chunk: string | Buffer | ArrayBufferView, + encoding: string, + callback: TransformCallback, +): void { + if (typeof chunk === "string") { + chunk = Buffer.from(chunk, encoding); + } + this.push(Buffer.from(this[kHandle].update(chunk))); + callback(); +}; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +Decipheriv.prototype._flush = function ( + this: Decipheriv, + callback: TransformCallback, +): void { + this.push(Buffer.from(this[kHandle].final())); + callback(); +}; + export function createCipheriv( algorithm: string, - key: unknown, - iv: unknown, - options?: any, -) { + key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey, + iv: string | ArrayBuffer | ArrayBufferView | null, + options: CipherOptions = {}, +): Cipheriv { return new Cipheriv(algorithm, key, iv, options); } + export function createDecipheriv( algorithm: string, - key: unknown, - iv: unknown, - options?: any, -) { + key: string | ArrayBuffer | ArrayBufferView | KeyObject | CryptoKey, + iv: string | ArrayBuffer | ArrayBufferView | null, + options: CipherOptions = {}, +): Decipheriv { return new Decipheriv(algorithm, key, iv, options); } diff --git a/src/compat/crypto/upstream-cipher.fixture.mjs b/src/compat/crypto/upstream-cipher.fixture.mjs new file mode 100644 index 00000000..72291c94 --- /dev/null +++ b/src/compat/crypto/upstream-cipher.fixture.mjs @@ -0,0 +1,678 @@ +// Copyright (c) 2017-2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { + createCipheriv, + createDecipheriv, + randomBytes, + createSecretKey, + publicDecrypt, + publicEncrypt, + privateDecrypt, + privateEncrypt, + createPublicKey, + createPrivateKey, + getCiphers, + getCipherInfo, + createCipher, + createDecipher, + Cipher, + Decipher, +} from "./index.js"; + +import { strictEqual, deepStrictEqual, throws, ok } from "node:assert"; + +const tests = [ + { name: "aes-128-cbc", size: 16, iv: 16 }, + { name: "aes-192-cbc", size: 24, iv: 16 }, + { name: "aes-256-cbc", size: 32, iv: 16 }, + { name: "aes-128-ctr", size: 16, iv: 16 }, + { name: "aes-192-ctr", size: 24, iv: 16 }, + { name: "aes-256-ctr", size: 32, iv: 16 }, + { name: "aes-128-ecb", size: 16, iv: 0 }, + { name: "aes-192-ecb", size: 24, iv: 0 }, + { name: "aes-256-ecb", size: 32, iv: 0 }, + { name: "aes-128-ofb", size: 16, iv: 16 }, + { name: "aes-192-ofb", size: 24, iv: 16 }, + { name: "aes-256-ofb", size: 32, iv: 16 }, +]; + +const authTagTests = [ + { name: "aes-128-gcm", size: 16, iv: 16 }, + { name: "aes-192-gcm", size: 24, iv: 16 }, + { name: "aes-256-gcm", size: 32, iv: 16 }, + { name: "chacha20-poly1305", size: 32, iv: 12 }, +]; + +export const cipheriv = { + async test() { + tests.forEach((test) => { + const key = createSecretKey(Buffer.alloc(test.size)); + const iv = Buffer.alloc(test.iv); + + const cipher = createCipheriv(test.name, key, iv); + const decipher = createDecipheriv(test.name, key, iv); + + let data = ""; + data += decipher.update(cipher.update("Hello World", "utf8")); + data += decipher.update(cipher.final()); + data += decipher.final(); + strictEqual(data, "Hello World"); + }); + + // Test that the streams API works also + await Promise.all( + tests.map(async (test) => { + const { promise, resolve, reject } = Promise.withResolvers(); + + const key = createSecretKey(Buffer.alloc(test.size)); + const iv = Buffer.alloc(test.iv); + + const cipher = createCipheriv(test.name, key, iv); + const decipher = createDecipheriv(test.name, key, iv); + decipher.setEncoding("utf8"); + + cipher.end("Hello World"); + cipher.on("data", (chunk) => { + decipher.write(chunk); + }); + cipher.on("end", () => { + decipher.end(); + }); + + cipher.on("error", reject); + decipher.on("error", reject); + + let res = ""; + decipher.on("data", (chunk) => { + res += chunk; + }); + decipher.on("end", resolve); + + await promise; + strictEqual(res, "Hello World", test.name); + }), + ); + + authTagTests.forEach((test) => { + const key = createSecretKey(Buffer.alloc(test.size)); + const iv = Buffer.alloc(test.iv); + + const cipher = createCipheriv(test.name, key, iv); + + let data = ""; + cipher.setAAD(Buffer.from("hello")); + data += cipher.update("Hello World", "utf8", "hex"); + data += cipher.final("hex"); + + const tag = cipher.getAuthTag(); + + //tag[1] = 0xbb; + + const decipher = createDecipheriv(test.name, key, iv); + decipher.setAuthTag(tag); + decipher.setAAD(Buffer.from("hello")); + let res = ""; + res += decipher.update(data, "hex"); + res += decipher.final(); + strictEqual(res, "Hello World"); + }); + }, +}; + +export const largeData = { + async test() { + const { promise, resolve, reject } = Promise.withResolvers(); + + const key = createSecretKey(Buffer.alloc(16)); + const iv = Buffer.alloc(16); + const cipher = createCipheriv("aes-128-cbc", key, iv); + const chunks = [ + randomBytes(1024), + randomBytes(2048), + randomBytes(4096), + randomBytes(8192), + randomBytes(16304), + ]; + chunks.forEach((chunk) => cipher.write(chunk)); + cipher.end(); + + const decipher = createDecipheriv("aes-128-cbc", key, iv); + cipher.pipe(decipher); + + const output = []; + decipher.on("data", (chunk) => output.push(chunk)); + + decipher.on("end", () => { + const inputCombined = Buffer.concat(chunks); + const outputCombined = Buffer.concat(output); + deepStrictEqual(inputCombined, outputCombined); + resolve(); + }); + + decipher.on("error", reject); + + await promise; + }, +}; + +export const publicEncryptPrivateDecrypt = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + const pvt = createPrivateKey(env["rsa_private.pem"]); + + pub.oaepLabel = "test"; + pub.oaepHash = "sha256"; + pub.padding = 4; + pub.encoding = "utf8"; + + pvt.oaepLabel = "test"; + pvt.oaepHash = "sha256"; + pvt.padding = 4; + pvt.encoding = "utf8"; + + strictEqual( + privateDecrypt(pvt, publicEncrypt(pub, "hello")).toString(), + "hello", + ); + }, +}; + +export const publicEncryptPrivateDecryptDer = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + const pvt = createPrivateKey(env["rsa_private.pem"]); + + const pubDer = { + key: pub.export({ type: "pkcs1", format: "der" }), + format: "der", + type: "pkcs1", + }; + + const pvtDer = { + key: pvt.export({ type: "pkcs8", format: "der" }), + format: "der", + type: "pkcs8", + }; + + strictEqual( + privateDecrypt(pvtDer, publicEncrypt(pubDer, "hello")).toString(), + "hello", + ); + }, +}; + +export const publicEncryptPrivateDecryptPem = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + const pvt = createPrivateKey(env["rsa_private.pem"]); + + const pubPem = { + key: pub.export({ type: "pkcs1", format: "pem" }), + format: "pem", + type: "pkcs1", + }; + + const pvtPem = { + key: pvt.export({ type: "pkcs8", format: "pem" }), + format: "pem", + type: "pkcs8", + }; + + strictEqual( + privateDecrypt(pvtPem, publicEncrypt(pubPem, "hello")).toString(), + "hello", + ); + }, +}; + +export const publicEncryptPrivateDecryptPem2 = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + const pvt = createPrivateKey(env["rsa_private.pem"]); + + const pubPem = pub.export({ type: "pkcs1", format: "pem" }); + const pvtPem = pvt.export({ type: "pkcs8", format: "pem" }); + + strictEqual( + privateDecrypt(pvtPem, publicEncrypt(pubPem, "hello")).toString(), + "hello", + ); + }, +}; + +export const publicEncryptPrivateDecryptPem3 = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + const pvt = createPrivateKey(env["rsa_private.pem"]); + + const pubPem = pub.export({ type: "spki", format: "pem" }); + const pvtPem = pvt.export({ type: "pkcs8", format: "pem" }); + + strictEqual( + privateDecrypt(pvtPem, publicEncrypt(pubPem, "hello")).toString(), + "hello", + ); + }, +}; + +export const privateEncryptPublicDecrypt = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + const pvt = createPrivateKey(env["rsa_private.pem"]); + + pub.oaepLabel = "test"; + pub.oaepHash = "sha256"; + pub.padding = 3; + pub.encoding = "utf8"; + + pvt.oaepLabel = "test"; + pvt.oaepHash = "sha256"; + pvt.padding = 3; + pvt.encoding = "utf8"; + + const input = "a".repeat(256); + strictEqual( + publicDecrypt(pub, privateEncrypt(pvt, input)).toString(), + input, + ); + }, +}; + +export const missingArgChecks = { + test() { + throws(() => publicEncrypt(), { + code: "ERR_MISSING_ARGS", + }); + throws(() => publicDecrypt(), { + code: "ERR_MISSING_ARGS", + }); + throws(() => privateEncrypt(), { + code: "ERR_MISSING_ARGS", + }); + throws(() => privateDecrypt(), { + code: "ERR_MISSING_ARGS", + }); + throws(() => publicEncrypt("key"), { + code: "ERR_MISSING_ARGS", + }); + throws(() => publicDecrypt("key"), { + code: "ERR_MISSING_ARGS", + }); + throws(() => privateEncrypt("key"), { + code: "ERR_MISSING_ARGS", + }); + throws(() => privateDecrypt("key"), { + code: "ERR_MISSING_ARGS", + }); + }, +}; + +export const known_ciphers_info = { + test() { + const ciphers = getCiphers(); + ciphers.forEach((i) => { + const info = getCipherInfo(i); + ok(info); + strictEqual(typeof info, "object"); + strictEqual(typeof info.name, "string"); + strictEqual(typeof info.nid, "number"); + strictEqual(typeof info.blockSize, "number"); + strictEqual(typeof info.ivLength, "number"); + strictEqual(typeof info.keyLength, "number"); + strictEqual(typeof info.mode, "string"); + }); + }, +}; + +export const test_cipher_info = { + test() { + const info = getCipherInfo("aes-128-cbc", { + ivLength: 16, + keyLength: 16, + }); + ok(info); + strictEqual(typeof info, "object"); + strictEqual(typeof info.name, "string"); + strictEqual(typeof info.nid, "number"); + strictEqual(typeof info.blockSize, "number"); + strictEqual(typeof info.ivLength, "number"); + strictEqual(typeof info.keyLength, "number"); + strictEqual(typeof info.mode, "string"); + }, +}; + +export const test_cipher_info2 = { + test() { + const info = getCipherInfo("aes-128-cbc", { + ivLength: 17, + keyLength: 15, + }); + strictEqual(info, undefined); + }, +}; + +export const test_cipher_info3 = { + test() { + const info = getCipherInfo("aes-128-cbc", { + ivLength: -1, + keyLength: 16, + }); + strictEqual(info, undefined); + }, +}; + +export const test_cipher_info4 = { + test() { + const info = getCipherInfo("aes-128-cbc", { + ivLength: 16, + keyLength: -1, + }); + strictEqual(info, undefined); + }, +}; + +// Verify that modifying a buffer after passing it to a cipher API does not +// affect the cipher output. Buffer data (IV, AAD, auth tag) is copied into +// C++-owned memory at the API boundary, matching Node.js behavior. These tests +// compare output against a reference encryption performed with unmodified buffers. + +// Helper: encrypt with chacha20-poly1305 (AEAD path) and return {ciphertext, tag}. +function chachaEncrypt(key, iv, plaintext, aad) { + const cipher = createCipheriv("chacha20-poly1305", key, iv); + if (aad) cipher.setAAD(aad); + const ct = cipher.update(plaintext); + cipher.final(); + return { ciphertext: ct, tag: cipher.getAuthTag() }; +} + +// Helper: encrypt with aes-256-gcm (CipherHandle path) and return {ciphertext, tag}. +function gcmEncrypt(key, iv, plaintext) { + const cipher = createCipheriv("aes-256-gcm", key, iv); + const ct = cipher.update(plaintext); + cipher.final(); + return { ciphertext: ct, tag: cipher.getAuthTag() }; +} + +export const modifiedIvChaCha20 = { + test() { + // ChaCha20-Poly1305 uses the AEAD (EVP_AEAD) path. + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + // Reference encryption with plain buffers. + const ref = chachaEncrypt(key, plainIv, plaintext); + + // Modify IV buffer after cipher creation -- must not affect output. + const ivBuffer = new ArrayBuffer(12, { maxByteLength: 16 }); + new Uint8Array(ivBuffer).fill(0x42); + const cipher = createCipheriv( + "chacha20-poly1305", + key, + new Uint8Array(ivBuffer), + ); + ivBuffer.resize(0); + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const modifiedIvAesGcm = { + test() { + // AES-256-GCM uses the CipherHandle (EVP_CIPHER) path. + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(16, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const ref = gcmEncrypt(key, plainIv, plaintext); + + const ivBuffer = new ArrayBuffer(16, { maxByteLength: 32 }); + new Uint8Array(ivBuffer).fill(0x42); + const cipher = createCipheriv("aes-256-gcm", key, new Uint8Array(ivBuffer)); + ivBuffer.resize(0); + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const modifiedIvGrowChaCha20 = { + test() { + // Growing the IV buffer after cipher creation must not change the output. + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const ref = chachaEncrypt(key, plainIv, plaintext); + + const ivBuffer = new ArrayBuffer(12, { maxByteLength: 32 }); + new Uint8Array(ivBuffer).fill(0x42); + const cipher = createCipheriv( + "chacha20-poly1305", + key, + new Uint8Array(ivBuffer), + ); + ivBuffer.resize(32); // grow past original IV length + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const modifiedAadChaCha20 = { + test() { + // Modifying AAD buffer after setAAD must not change the cipher output. + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const plainAad = Buffer.alloc(16, 0xaa); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const ref = chachaEncrypt(key, plainIv, plaintext, plainAad); + + const aadBuffer = new ArrayBuffer(16, { maxByteLength: 32 }); + new Uint8Array(aadBuffer).fill(0xaa); + const cipher = createCipheriv("chacha20-poly1305", key, plainIv); + cipher.setAAD(new Uint8Array(aadBuffer)); + aadBuffer.resize(0); + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const modifiedAuthTagDecrypt = { + test() { + // Modifying auth tag buffer after setAuthTag must still allow correct decryption. + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const { ciphertext, tag } = chachaEncrypt(key, plainIv, plaintext); + + // Copy tag into a buffer, then modify it after setAuthTag. + const tagBuffer = new ArrayBuffer(tag.length, { maxByteLength: 32 }); + new Uint8Array(tagBuffer).set(tag); + + const decipher = createDecipheriv("chacha20-poly1305", key, plainIv); + decipher.setAuthTag(new Uint8Array(tagBuffer)); + tagBuffer.resize(0); + + const pt = decipher.update(ciphertext); + decipher.final(); + deepStrictEqual(pt, plaintext); + }, +}; + +export const invalidAuthTagDecrypt = { + test() { + // Decryption with an invalid auth tag results in a meaningful error message + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const { ciphertext, tag } = chachaEncrypt(key, plainIv, plaintext); + // Modify first byte of tag + tag[0] ^= 1; + + const decipher = createDecipheriv("chacha20-poly1305", key, plainIv); + decipher.setAuthTag(new Uint8Array(tag)); + + throws( + () => { + decipher.update(ciphertext); + }, + { + name: "OperationError", + message: + "Decryption failed: Incorrect key/IV or corrupted ciphertext/authentication tag provided.", + }, + ); + }, +}; + +export const transferredIvChaCha20 = { + test() { + // Transferring IV buffer after cipher creation must not affect output. + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const ref = chachaEncrypt(key, plainIv, plaintext); + + const ivBuffer = new ArrayBuffer(12); + new Uint8Array(ivBuffer).fill(0x42); + const cipher = createCipheriv( + "chacha20-poly1305", + key, + new Uint8Array(ivBuffer), + ); + structuredClone(ivBuffer, { transfer: [ivBuffer] }); + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const transferredIvAesGcm = { + test() { + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(16, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const ref = gcmEncrypt(key, plainIv, plaintext); + + const ivBuffer = new ArrayBuffer(16); + new Uint8Array(ivBuffer).fill(0x42); + const cipher = createCipheriv("aes-256-gcm", key, new Uint8Array(ivBuffer)); + structuredClone(ivBuffer, { transfer: [ivBuffer] }); + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const transferredAadChaCha20 = { + test() { + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const plainAad = Buffer.alloc(16, 0xaa); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const ref = chachaEncrypt(key, plainIv, plaintext, plainAad); + + const aadBuffer = new ArrayBuffer(16); + new Uint8Array(aadBuffer).fill(0xaa); + const cipher = createCipheriv("chacha20-poly1305", key, plainIv); + cipher.setAAD(new Uint8Array(aadBuffer)); + structuredClone(aadBuffer, { transfer: [aadBuffer] }); + const ct = cipher.update(plaintext); + cipher.final(); + + deepStrictEqual(ct, ref.ciphertext); + deepStrictEqual(cipher.getAuthTag(), ref.tag); + }, +}; + +export const transferredAuthTagDecrypt = { + test() { + const plainKey = Buffer.alloc(32, 0x01); + const plainIv = Buffer.alloc(12, 0x42); + const key = createSecretKey(plainKey); + const plaintext = Buffer.from("hello world"); + + const { ciphertext, tag } = chachaEncrypt(key, plainIv, plaintext); + + const tagBuffer = new ArrayBuffer(tag.length); + new Uint8Array(tagBuffer).set(tag); + + const decipher = createDecipheriv("chacha20-poly1305", key, plainIv); + decipher.setAuthTag(new Uint8Array(tagBuffer)); + structuredClone(tagBuffer, { transfer: [tagBuffer] }); + + const pt = decipher.update(ciphertext); + decipher.final(); + deepStrictEqual(pt, plaintext); + }, +}; + +// Regression: AUTOVULN-CLOUDFLARE-WORKERD-76 +// publicEncrypt/privateDecrypt with non-OAEP padding + oaepLabel must +// throw without leaking the label buffer. +export const oaepLabelWithNonOaepPaddingThrows = { + test(_, env) { + const pub = createPublicKey(env["rsa_public.pem"]); + pub.padding = 1; // RSA_PKCS1_PADDING (not OAEP=4) + pub.oaepLabel = Buffer.alloc(1024); + pub.encoding = "utf8"; + + for (let i = 0; i < 5; i++) { + throws(() => publicEncrypt(pub, Buffer.from("test")), { + message: /Failed to set the OAEP label/, + }); + } + + const pvt = createPrivateKey(env["rsa_private.pem"]); + pvt.padding = 1; // RSA_PKCS1_PADDING (not OAEP=4) + pvt.oaepLabel = Buffer.alloc(1024); + pvt.encoding = "utf8"; + + for (let i = 0; i < 5; i++) { + throws(() => privateDecrypt(pvt, Buffer.from("test")), { + message: /Failed to set the OAEP label/, + }); + } + }, +}; + +export const testUnimplemented = { + async test() { + strictEqual(typeof Cipher, "function"); + strictEqual(typeof Decipher, "function"); + strictEqual(typeof createCipher, "function"); + strictEqual(typeof createDecipher, "function"); + }, +}; diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index 09abb3a6..06fb2492 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -62,7 +62,19 @@ synchronous extraction of its opaque key material is unavailable. RSA, EC, Ed255 bundled OpenSSL 3.5.9 WebAssembly; RSA/EC streaming and one-shot signatures are compared with Workers and independent test signatures. This covers selected algorithms and operations, not the whole crypto module. Additional key-generation -options and remaining error contracts still need work. `getCipherInfo` returns pinned Workers metadata; a successful lookup does not establish local availability of the named encryption algorithm. +options and remaining error contracts still need work. Cipheriv/Decipheriv use the same pinned BoringSSL revision as Workers. Their +frontend preserves Workers' `authLengthTag` option spelling, AEAD's one-update +limit, early authentication-tag requirements, tag consumption and buffer copies. +The listed classic and AEAD cipher names have shared algorithm/state contracts; +metadata lookup still does not certify every option or the entire crypto module. +The separate cipher Wasm and provenance are in `crypto/native-cipher.*`. +Rebuild with `node scripts/build-native-cipher.mjs` after checking out the pinned +BoringSSL commit into `artifacts/upstream/boringssl` and installing Emscripten +4.0.20 at the pinned SDK revision. The script uses CMake 4.2.0 and Ninja 1.13.0 +from a Python venv at `artifacts/toolchains/boringssl-build` (install with +`python3 -m venv artifacts/toolchains/boringssl-build` then that venv's +`pip install cmake==4.2.0 ninja==1.13.0`). `--link-only` requires libraries +previously built with those settings. No FIPS certification is claimed. X509Certificate parses PEM/DER, exposes certificate fields and legacy objects, extracts RSA/EC public keys, checks names and issuers, and verifies signatures with certificate public keys. The pinned Workers runtime returns false for diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index 5e854259..ff787dcc 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,193 @@ export const eventCases = { + cryptoCipherAlgorithms: async function ({ crypto }) { + const results = []; + for (const name of [ + ...new Set( + crypto.getCiphers().flatMap((name) => [name, name.toUpperCase()]), + ), + ].sort()) + for (const authLengthTag of [undefined, 16, 8, 4, 28]) { + const info = crypto.getCipherInfo(name), + result = { name, authLengthTag: authLengthTag ?? null }; + let stage = "construct"; + try { + const key = new Uint8Array(info.keyLength), + iv = new Uint8Array(info.ivLength); + const cipher = crypto.createCipheriv(name, key, iv, { + authLengthTag, + }); + stage = "aad"; + try { + cipher.setAAD("aad", { plaintextLength: 5 }); + result.aad = true; + } catch (error) { + result.aad = error.message; + } + stage = "update"; + const first = cipher.update("hello", "utf8"); + stage = "final"; + const last = cipher.final(); + stage = "tag"; + const tag = cipher.getAuthTag(); + const ciphertext = new Uint8Array(first.length + last.length); + ciphertext.set(first); + ciphertext.set(last, first.length); + const hex = (value) => + Array.from(value, (byte) => + byte.toString(16).padStart(2, "0"), + ).join(""); + result.data = name.toLowerCase().includes("randnonce") + ? ciphertext.length + : hex(ciphertext); + result.tag = name.toLowerCase().includes("randnonce") + ? tag.length + : hex(tag); + result.secondTag = cipher.getAuthTag().length; + stage = "decrypt"; + const decipher = crypto.createDecipheriv(name, key, iv, { + authLengthTag, + }); + if (tag.length) decipher.setAuthTag(tag); + if (result.aad === true) + decipher.setAAD("aad", { plaintextLength: 5 }); + result.plain = + decipher.update(ciphertext).toString() + + decipher.final().toString(); + if (tag.length) { + const invalid = crypto.createDecipheriv(name, key, iv, { + authLengthTag, + }); + const damaged = new Uint8Array(tag); + damaged[0] ^= 1; + invalid.setAuthTag(damaged); + if (result.aad === true) + invalid.setAAD("aad", { plaintextLength: 5 }); + try { + invalid.update(ciphertext); + invalid.final(); + result.tamper = "accepted"; + } catch (error) { + result.tamper = { + name: error.name, + code: error.code ?? null, + message: error.message.replace( + /internal error; reference = .*/, + "internal error", + ), + }; + } + } + } catch (error) { + result.error = { + stage, + name: error.name, + code: error.code ?? null, + message: error.message.replace( + /internal error; reference = .*/, + "internal error", + ), + }; + } + results.push(result); + } + return results; + }, + cryptoCipherStates: async function ({ crypto }) { + const attempt = (operation) => { + try { + const result = operation(); + return result?.byteLength ?? typeof result; + } catch (error) { + return { + name: error.name, + code: error.code ?? null, + message: error.message.replace( + /internal error; reference = .*/, + "internal error", + ), + }; + } + }; + const result = []; + for (const name of [ + "aes-128-cbc", + "aes-128-gcm", + "chacha20-poly1305", + "aes-128-eax", + ]) { + const info = crypto.getCipherInfo(name), + key = new Uint8Array(info.keyLength), + iv = new Uint8Array(info.ivLength); + const cipher = crypto.createCipheriv(name, key, iv, { + authLengthTag: 16, + }); + const row = { + name, + before: attempt(() => cipher.getAuthTag()), + stringInput: attempt(() => cipher.update("abc")), + aad: attempt(() => cipher.setAAD(new Uint8Array([1, 2, 3]))), + }; + row.update = attempt(() => cipher.update(new Uint8Array([1, 2, 3]))); + row.secondUpdate = attempt(() => cipher.update(new Uint8Array([4]))); + row.final = attempt(() => cipher.final()); + row.tag = attempt(() => cipher.getAuthTag()); + row.tagAgain = attempt(() => cipher.getAuthTag()); + row.finalAgain = attempt(() => cipher.final()); + row.updateAfterFinal = attempt(() => cipher.update(new Uint8Array())); + row.aadAfterFinal = attempt(() => cipher.setAAD(new Uint8Array())); + row.paddingAfterFinal = attempt(() => cipher.setAutoPadding()); + const decipher = crypto.createDecipheriv(name, key, iv, { + authLengthTag: 16, + }); + row.stringTag = attempt(() => decipher.setAuthTag("00", "hex")); + row.missingTag = attempt(() => + decipher.update(new Uint8Array([1, 2, 3])), + ); + row.setTag = attempt(() => decipher.setAuthTag(new Uint8Array(16))); + row.setTagAgain = attempt(() => decipher.setAuthTag(new Uint8Array(16))); + row.decryptFinal = attempt(() => decipher.final()); + result.push(row); + } + result.push({ + invalidNames: [ + "aes_128_eax", + "aes-128-eax\0suffix", + "AES-128-EAX", + "aes-128-cbc\0suffix", + "unknown", + ].map((name) => [ + name, + attempt(() => + crypto.createCipheriv(name, new Uint8Array(16), new Uint8Array(16), { + authLengthTag: 16, + }), + ), + ]), + }); + const changingKey = new Uint8Array(16); + const keyCopy = crypto.createCipheriv( + "aes-128-cbc", + changingKey, + new Uint8Array(16), + { + get authLengthTag() { + changingKey[0] = 1; + return 16; + }, + }, + ); + result.push({ + keyCopiedBeforeOptions: + keyCopy.update("hello", "utf8", "hex") + keyCopy.final("hex"), + }); + result.push({ + deprecated: ["Cipher", "Decipher", "createCipher", "createDecipher"].map( + (name) => [name, attempt(() => crypto[name]())], + ), + }); + return result; + }, + cryptoPbkdfContracts: async function ({ crypto }) { const attempt = (fn) => { try { diff --git a/vitest.config.ts b/vitest.config.ts index 7d74a2a4..02af7a83 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -34,7 +34,12 @@ export default defineConfig({ { name: "test-wasm-binary", async load(id) { - if (!/\/(?:brotli_dec_wasm_bg|native-crypto)\.wasm$/.test(id)) return; + if ( + !/\/(?:brotli_dec_wasm_bg|native-crypto|native-cipher)\.wasm$/.test( + id, + ) + ) + return; const data = (await readFile(id)).toString("base64"); return `export default Uint8Array.from(atob(${JSON.stringify(data)}), c => c.charCodeAt(0));`; }, @@ -68,6 +73,7 @@ export default defineConfig({ "src/browser/fetch/scope.spec.ts", "src/compat/net/sockets.spec.ts", "src/compat/tls/upgrade.spec.ts", + "src/compat/crypto/cipher.spec.ts", "src/compat/crypto/hash.spec.ts", "src/compat/crypto/hash-types.spec.ts", "src/compat/fs/writable.spec.ts", From c72a9d3bdf0bce63a84559c214db3d33403cc662 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 02:34:59 +0900 Subject: [PATCH 05/30] feat: align key generation and scrypt limits with Workers --- src/compat/crypto/asymmetric.ts | 194 +++-- src/compat/crypto/kdf-limits.spec.ts | 63 ++ src/compat/crypto/key-errors.ts | 23 + src/compat/crypto/key-options.ts | 98 +++ src/compat/crypto/keys.ts | 2 + src/compat/crypto/native-cipher.build.json | 4 +- src/compat/crypto/native-cipher.c | 26 + src/compat/crypto/native-cipher.ts | 47 ++ src/compat/crypto/native-cipher.wasm | Bin 140935 -> 291907 bytes src/compat/crypto/native-crypto.build.json | 4 +- src/compat/crypto/native-crypto.c | 13 +- src/compat/crypto/native-crypto.ts | 13 + src/compat/crypto/native-crypto.wasm | Bin 2294746 -> 2295031 bytes src/compat/crypto/scrypt.ts | 12 +- src/compat/crypto/upstream-keygen.fixture.mjs | 690 ++++++++++++++++++ .../crypto/upstream-scrypt-limits.fixture.mjs | 39 + src/compat/crypto/upstream-scrypt.fixture.mjs | 257 +++++++ src/compat/expansion/README.md | 13 +- src/compat/expansion/index.spec.mjs | 229 ++++++ vitest.config.ts | 1 + 20 files changed, 1677 insertions(+), 51 deletions(-) create mode 100644 src/compat/crypto/kdf-limits.spec.ts create mode 100644 src/compat/crypto/key-errors.ts create mode 100644 src/compat/crypto/key-options.ts create mode 100644 src/compat/crypto/upstream-keygen.fixture.mjs create mode 100644 src/compat/crypto/upstream-scrypt-limits.fixture.mjs create mode 100644 src/compat/crypto/upstream-scrypt.fixture.mjs diff --git a/src/compat/crypto/asymmetric.ts b/src/compat/crypto/asymmetric.ts index bcb99fb3..c7357637 100644 --- a/src/compat/crypto/asymmetric.ts +++ b/src/compat/crypto/asymmetric.ts @@ -1,3 +1,9 @@ +import { curveName, generateDh, deriveDh } from "./native-cipher.js"; +import { validateExportOptions } from "./key-options.js"; +import { + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_MISSING_OPTION, +} from "./key-errors.js"; import { Buffer } from "node:buffer"; import { NativeKey, nativeTimingEqual } from "./native-crypto.js"; import { importJwk } from "./key-formats.js"; @@ -8,13 +14,14 @@ import { PublicKeyObject, } from "./keys.js"; import { keyStates } from "./key-state.js"; -import { bytes, validateInteger } from "./validation.js"; +import { bytes, validateInteger, isAnyBuffer } from "./validation.js"; import { validateObject, validateString, validateOneOf, validateFunction, validateUint32, + validateInt32, } from "../net/validators.js"; import { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE } from "../net/errors.js"; function handle(key: KeyObject): NativeKey { @@ -104,11 +111,7 @@ export function createPublicKey(value: any): PublicKeyObject { if (value.type !== "private") throw new ERR_INVALID_ARG_TYPE("key", "PrivateKeyObject", value); return create("public", { - asymmetric: NativeKey.parse( - handle(value).export(false, false, "spki"), - false, - false, - ), + asymmetric: handle(value), }) as PublicKeyObject; } return create("public", { @@ -118,47 +121,133 @@ export function createPublicKey(value: any): PublicKeyObject { export function generateKeyPairSync(type: string, options: any = {}) { validateOneOf(type, "type", ["rsa", "ec", "ed25519", "x25519", "dh"]); validateObject(options, "options"); + const { + modulusLength, + publicExponent = 65537, + namedCurve, + prime, + primeLength, + generator, + group, + groupName, + paramEncoding = "named", + publicKeyEncoding, + privateKeyEncoding, + } = options; + if (publicKeyEncoding !== undefined) + validateExportOptions( + publicKeyEncoding, + "public", + "options.publicKeyEncoding", + ); + if (privateKeyEncoding !== undefined) + validateExportOptions( + privateKeyEncoding, + "private", + "options.privateKeyEncoding", + type, + ); let key: NativeKey; if (type === "rsa") { - validateUint32(options.modulusLength, "options.modulusLength"); - validateUint32(options.publicExponent ?? 65537, "options.publicExponent"); - key = NativeKey.generate( - "RSA", - options.modulusLength, - undefined, - options.publicExponent ?? 65537, - ); + validateUint32(modulusLength, "options.modulusLength"); + validateUint32(publicExponent, "options.publicExponent"); + if (modulusLength > 16384) + throw new RangeError("RSA modulusLength exceeds maximum (16384)"); + if (modulusLength < 256) throw new Error("Failed to set modulus length"); + if (modulusLength < 512 || publicExponent < 3 || publicExponent % 2 === 0) + throw new Error("Failed to generate key"); + try { + key = NativeKey.generate( + "RSA", + modulusLength & ~127, + undefined, + publicExponent, + ); + } catch { + throw new Error("Failed to generate key"); + } } else if (type === "ec") { - validateString(options.namedCurve, "options.namedCurve"); - validateOneOf(options.paramEncoding ?? "named", "options.paramEncoding", [ + validateString(namedCurve, "options.namedCurve"); + validateOneOf(paramEncoding, "options.paramEncoding", [ "named", "explicit", ]); - key = NativeKey.generate("EC", 0, options.namedCurve); + key = NativeKey.generate("EC", 0, curveName(namedCurve)); + key.ecEncoding = paramEncoding === "named" ? 1 : 0; } else if (type === "dh") { - const groups: Record = { - modp1: "modp_768", - modp2: "modp_1024", - modp5: "modp_1536", - modp14: "modp_2048", - modp15: "modp_3072", - modp16: "modp_4096", - modp17: "modp_6144", - modp18: "modp_8192", - }; - const group = options.group ?? options.groupName; - validateString(group, "options.group"); - key = NativeKey.generate("DH", 0, groups[group] ?? group); + if (generator != null) validateInt32(generator, "options.generator", 0); + if (group != null || groupName != null) { + for (const [name, value] of [ + ["prime", prime], + ["primeLength", primeLength], + ["generator", generator], + ]) + if (value != null) + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", name); + const selected = group || groupName; + validateString(selected, "options.group"); + const groups: Record = Object.assign( + Object.create(null), + { + modp5: "modp_1536", + modp14: "modp_2048", + modp15: "modp_3072", + modp16: "modp_4096", + modp17: "modp_6144", + modp18: "modp_8192", + }, + ); + const nativeGroup = groups[selected.toLowerCase()]; + if (!nativeGroup) throw new Error("Invalid or unsupported group"); + key = NativeKey.generate("DH", 0, nativeGroup); + } else { + if (prime != null) { + if (primeLength != null) + throw new ERR_INCOMPATIBLE_OPTION_PAIR("prime", "primeLength"); + if (!ArrayBuffer.isView(prime) && !isAnyBuffer(prime)) + throw new ERR_INVALID_ARG_TYPE( + "options.prime", + ["Buffer", "TypedArray", "ArrayBuffer"], + prime, + ); + } else if (primeLength != null) + validateInt32(primeLength, "options.primeLength", 0); + else + throw new ERR_MISSING_OPTION( + "At least one of the group, prime, or primeLength options", + ); + if (prime) { + const data = bytes(prime, "options.prime"); + if (data.byteLength > 1024) + throw new RangeError("DH prime exceeds maximum size"); + const components = generateDh(data, generator ?? 2); + try { + key = NativeKey.generateDh(data, generator ?? 2, components); + } finally { + components.fill(0); + } + } else + throw new Error( + "Generating DH keys from a prime length is not yet implemented", + ); + } } else key = NativeKey.generate(type.toUpperCase()); const privateKey = create("private", { asymmetric: key }) as PrivateKeyObject; - const publicKey = createPublicKey(privateKey); + // Workers retains the generated DH key in both wrappers. Imported public + // keys still contain only public material. + const publicKey = + type === "dh" + ? (create("public", { asymmetric: key }) as PublicKeyObject) + : createPublicKey(privateKey); return { - publicKey: options.publicKeyEncoding - ? publicKey.export(options.publicKeyEncoding) - : publicKey, - privateKey: options.privateKeyEncoding - ? privateKey.export(options.privateKeyEncoding) - : privateKey, + publicKey: + publicKeyEncoding !== undefined + ? publicKey.export(publicKeyEncoding) + : publicKey, + privateKey: + privateKeyEncoding !== undefined + ? privateKey.export(privateKeyEncoding) + : privateKey, }; } export function generateKeyPair( @@ -166,10 +255,6 @@ export function generateKeyPair( options: any, callback: any, ): void { - if (typeof options === "function") { - callback = options; - options = {}; - } validateFunction(callback, "callback"); let result: ReturnType | undefined, failure: unknown; @@ -184,6 +269,11 @@ export function generateKeyPair( : callback(null, result!.publicKey, result!.privateKey), ); } +Object.defineProperty( + generateKeyPair, + Symbol.for("nodejs.util.promisify.custom.args"), + { value: ["publicKey", "privateKey"], enumerable: false }, +); function optionsOf(value: any) { return value && typeof value === "object" && @@ -303,6 +393,28 @@ export function diffieHellman(options: any): Buffer { // DHPointer::stateless argument. Preserve its observable failure rather than // silently substituting standalone Node behavior. try { + if (publicHandle.type === "dh") { + for (const name of ["p", "g"]) + if ( + !Buffer.from(publicHandle.parameter(name)).equals( + Buffer.from(privateHandle.parameter(name)), + ) + ) + throw new Error("Failed to derive shared diffie-hellman secret"); + const secret = publicHandle.parameter("priv"); + try { + return Buffer.from( + deriveDh( + publicHandle.parameter("p"), + publicHandle.parameter("g"), + secret, + privateHandle.parameter("pub"), + ), + ); + } finally { + secret.fill(0); + } + } return Buffer.from(publicHandle.derive(privateHandle)); } catch { throw new Error("Failed to derive shared diffie-hellman secret"); diff --git a/src/compat/crypto/kdf-limits.spec.ts b/src/compat/crypto/kdf-limits.spec.ts new file mode 100644 index 00000000..78f0e88a --- /dev/null +++ b/src/compat/crypto/kdf-limits.spec.ts @@ -0,0 +1,63 @@ +import { test, expect } from "vitest"; +import { generateKeyPairSync } from "./asymmetric.js"; +// @ts-expect-error Upstream JavaScript fixture. +import * as scrypt from "./upstream-scrypt.fixture.mjs"; +// @ts-expect-error Upstream JavaScript fixture. +import * as limits from "./upstream-scrypt-limits.fixture.mjs"; +for (const [name, fixture] of Object.entries({ ...scrypt, ...limits })) + test("pinned Workers scrypt: " + name, () => + (fixture as { test: () => unknown }).test(), + ); +test("RSA rejects excessive modulus size before key generation", () => { + expect(() => generateKeyPairSync("rsa", { modulusLength: 16385 })).toThrow( + new RangeError("RSA modulusLength exceeds maximum (16384)"), + ); +}); + +// @ts-expect-error Upstream JavaScript fixture. +import * as generation from "./upstream-keygen.fixture.mjs"; +for (const [name, fixture] of Object.entries(generation)) + test("pinned Workers key generation: " + name, () => + (fixture as { test: () => unknown }).test(), + ); + +test("RSA generation errors follow Workers stages", () => { + for (const modulusLength of [0, 1, 16, 255]) + expect(() => generateKeyPairSync("rsa", { modulusLength })).toThrow( + new Error("Failed to set modulus length"), + ); + for (const modulusLength of [256, 511]) + expect(() => generateKeyPairSync("rsa", { modulusLength })).toThrow( + new Error("Failed to generate key"), + ); + for (const publicExponent of [0, 1, 2, 4]) + expect(() => + generateKeyPairSync("rsa", { modulusLength: 512, publicExponent }), + ).toThrow(new Error("Failed to generate key")); +}); +test("DH generation accepts Workers small primes", () => { + for (const p of [5, 23, 37, 255]) { + const pair = generateKeyPairSync("dh", { + prime: new Uint8Array([p]), + generator: 2, + }); + expect(pair.publicKey.asymmetricKeyType).toBe("dh"); + expect(() => + pair.privateKey.export({ type: "pkcs8", format: "der" }), + ).toThrow(new Error("Failed to export key")); + } +}); +test("EC SEC1 parameter flags match Workers", () => { + for (const [paramEncoding, length] of [ + ["named", 109], + ["explicit", 121], + ]) { + const pair = generateKeyPairSync("ec", { + namedCurve: "P-256", + paramEncoding, + }); + expect(pair.privateKey.export({ type: "sec1", format: "der" }).length).toBe( + length, + ); + } +}); diff --git a/src/compat/crypto/key-errors.ts b/src/compat/crypto/key-errors.ts new file mode 100644 index 00000000..31c2d192 --- /dev/null +++ b/src/compat/crypto/key-errors.ts @@ -0,0 +1,23 @@ +// From pinned workerd internal_errors.ts (Apache-2.0 / Node MIT). +import { NodeError } from "../net/error-base.js"; +export class ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS extends NodeError { + constructor(type: string, reason: string) { + super( + "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS", + `The selected key encoding ${type} ${reason}.`, + ); + } +} +export class ERR_INCOMPATIBLE_OPTION_PAIR extends NodeError { + constructor(a: string, b: string) { + super( + "ERR_INCOMPATIBLE_OPTION_PAIR", + `The options "${a}" and "${b}" are mutually exclusive.`, + ); + } +} +export class ERR_MISSING_OPTION extends NodeError { + constructor(name: string) { + super("ERR_MISSING_OPTION", name); + } +} diff --git a/src/compat/crypto/key-options.ts b/src/compat/crypto/key-options.ts new file mode 100644 index 00000000..fb0e4aed --- /dev/null +++ b/src/compat/crypto/key-options.ts @@ -0,0 +1,98 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT ORs +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { Buffer } from "node:buffer"; +import { validateObject, validateString } from "../net/validators.js"; +import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +import { ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS } from "./key-errors.js"; +import { isUint8Array } from "../util/types/index.js"; +export function validateExportOptions( + options: any, + type: string, + name = "options", + asymmetricKeyType?: string, +) { + validateObject(options, name); + // Yes, converting to any is a bit of a cheat, but it allows us to check + // each option individually without having to do a bunch of type guards. + const opts = options; + if (opts.format !== undefined) { + validateString(opts.format, `${name}.format`); + } else { + options.format = "buffer"; + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if ("type" in opts && opts.type !== undefined) { + validateString(opts.type, `${name}.type`); + } + if (type === "private") { + if ( + "type" in opts && + opts.type === "pkcs1" && + asymmetricKeyType !== undefined && + asymmetricKeyType !== "rsa" + ) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + opts.type, + "can only be used for RSA keys", + ); + } + if ( + "type" in opts && + opts.type === "sec1" && + asymmetricKeyType !== undefined && + asymmetricKeyType !== "ec" && + (asymmetricKeyType as string) !== "ecdh" + ) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + opts.type, + "can only be used for EC keys", + ); + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if ("cipher" in opts && opts.cipher !== undefined) { + validateString(opts.cipher, `${name}.cipher`); + if ( + opts.format === "der" && + (opts.type === "pkcs1" || opts.type === "sec1") + ) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + opts.type, + "does not support encryption", + ); + } + if (typeof opts.passphrase === "string") { + opts.passphrase = Buffer.from(opts.passphrase, opts.encoding); + } + if (!isUint8Array(opts.passphrase)) { + throw new ERR_INVALID_ARG_TYPE( + `${name}.passphrase`, + ["string", "Uint8Array"], + opts.passphrase, + ); + } + } + } +} diff --git a/src/compat/crypto/keys.ts b/src/compat/crypto/keys.ts index 4b70f2ee..a1e27f05 100644 --- a/src/compat/crypto/keys.ts +++ b/src/compat/crypto/keys.ts @@ -75,6 +75,8 @@ export abstract class KeyObject { options.passphrase === undefined ? undefined : bytes(options.passphrase, "options.passphrase"); + if (current.asymmetric.type === "dh") + throw new Error("Failed to export key"); const result = current.asymmetric.export( current.type === "private", options.format === "pem", diff --git a/src/compat/crypto/native-cipher.build.json b/src/compat/crypto/native-cipher.build.json index 41c16afd..f81a14fc 100644 --- a/src/compat/crypto/native-cipher.build.json +++ b/src/compat/crypto/native-cipher.build.json @@ -21,6 +21,6 @@ "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" ], "libraries": ["libdecrepit.a", "libcrypto.a"], - "bindingSha256": "c9595546b91a40c78879f0eaad4ab3b8ac0cfb46faf0b9c948ad738dc20022a0", - "sha256": "67e892b2244c2fdd66a3106187cefa9f71aeb119d9bdbb84668490b46dc38e15" + "bindingSha256": "e19810fb59e71f3de0a7b87885779c56dcec1fb5cb5aa841996d9fd9d680f368", + "sha256": "c3e35a8accc7db4c8ba1080ffe774bb3c72f7690910d1e829422371443c50a5d" } diff --git a/src/compat/crypto/native-cipher.c b/src/compat/crypto/native-cipher.c index 5a8e0c7a..56e2f43c 100644 --- a/src/compat/crypto/native-cipher.c +++ b/src/compat/crypto/native-cipher.c @@ -2,6 +2,10 @@ #include #include #include +#include +#include +#include +#include #include #include #define API EMSCRIPTEN_KEEPALIVE @@ -36,3 +40,25 @@ API const char *cipher_reason(unsigned long code){return ERR_reason_error_string API int cipher_info(const EVP_CIPHER *cipher,int kind){return kind==0?EVP_CIPHER_key_length(cipher):kind==1?EVP_CIPHER_iv_length(cipher):kind==2?EVP_CIPHER_block_size(cipher):EVP_CIPHER_mode(cipher);} API int cipher_bad_decrypt(void){uint32_t code=ERR_peek_last_error();int result=ERR_GET_LIB(code)==ERR_LIB_CIPHER && ERR_GET_REASON(code)==CIPHER_R_BAD_DECRYPT;ERR_clear_error();return result;} + +API intptr_t cipher_curve_name(const char *name){ + int nid=EC_curve_nist2nid(name);if(nid==NID_undef)nid=OBJ_sn2nid(name); + if(nid==NID_undef)return 0;EC_GROUP *group=EC_GROUP_new_by_curve_name(nid);if(!group)return -1; + EC_GROUP_free(group);return (intptr_t)OBJ_nid2sn(nid); +} + +API int cipher_dh_generate(const unsigned char *prime,int length,unsigned long generator,unsigned char *out){ + DH *dh=DH_new();BIGNUM *p=BN_bin2bn(prime,length,NULL),*g=BN_new();int ok=0; + if(!dh || !p || !g || !BN_set_word(g,generator) || !DH_set0_pqg(dh,p,NULL,g))goto done; + p=NULL;g=NULL;if(!DH_generate_key(dh))goto done; + const BIGNUM *pub,*priv;DH_get0_key(dh,&pub,&priv); + ok=BN_bn2bin_padded(out,length,pub) && BN_bn2bin_padded(out+length,length,priv); + done: BN_free(p);BN_free(g);DH_free(dh);return ok; +} +API int cipher_dh_derive(const unsigned char *prime,int plen,const unsigned char *generator,int glen,const unsigned char *priv,int xlen,const unsigned char *pub,int ylen,unsigned char *out){ + DH *dh=DH_new();BIGNUM *p=BN_bin2bn(prime,plen,NULL),*g=BN_bin2bn(generator,glen,NULL),*x=BN_bin2bn(priv,xlen,NULL),*y=BN_bin2bn(pub,ylen,NULL);int result=-1; + if(!dh || !p || !g || !x || !y || !DH_set0_pqg(dh,p,NULL,g))goto done; + p=NULL;g=NULL;if(!DH_set0_key(dh,NULL,x))goto done;x=NULL; + result=DH_compute_key_padded(out,y,dh); + done: BN_free(p);BN_free(g);BN_clear_free(x);BN_free(y);DH_free(dh);return result; +} diff --git a/src/compat/crypto/native-cipher.ts b/src/compat/crypto/native-cipher.ts index 1280af3f..9520018b 100644 --- a/src/compat/crypto/native-cipher.ts +++ b/src/compat/crypto/native-cipher.ts @@ -75,3 +75,50 @@ export function output(api: API, pointer: number, length: number): Uint8Array { export function size(api: API, pointer: number): number { return new DataView(api.memory.buffer).getUint32(pointer, true); } + +// Match Workers' curve lookup before handing generation to the owned key backend. +export function curveName(name: string): string { + return inputs([name], (api, [p]) => { + const pointer = api.cipher_curve_name(p); + if (pointer === 0) throw new Error("Invalid or unsupported curve"); + if (pointer === -1) throw new Error("Failed to initialize key"); + const memory = new Uint8Array(api.memory.buffer); + let end = pointer; + while (memory[end]) end++; + return new TextDecoder().decode(memory.subarray(pointer, end)); + }); +} + +export function generateDh(prime: Uint8Array, generator: number): Uint8Array { + return inputs([prime, prime.length * 2], (api, [p, out]) => { + if (!api.cipher_dh_generate(p, prime.length, generator, out)) + throw new Error("Failed to generate key"); + return output(api, out, prime.length * 2); + }); +} +export function deriveDh( + prime: Uint8Array, + generator: Uint8Array, + privateKey: Uint8Array, + publicKey: Uint8Array, +): Uint8Array { + return inputs( + [prime, generator, privateKey, publicKey, prime.length], + (api, [p, g, x, y, out]) => { + const length = api.cipher_dh_derive( + p, + prime.length, + g, + generator.length, + x, + privateKey.length, + y, + publicKey.length, + out, + ); + if (length < 0) + throw new Error("Failed to derive shared diffie-hellman secret"); + return output(api, out, length); + }, + ); +} diff --git a/src/compat/crypto/native-cipher.wasm b/src/compat/crypto/native-cipher.wasm index 426709de17f01b86e6f74600f9536a98947f97b9..d869b39cd6fae07f731ad255ebb8f77c232e1981 100755 GIT binary patch literal 291907 zcmeFa3!G+WUFUgj@AbS@^;UN!sdN>|d5=Ax>C7ZUW>=DcOsLaKE}aBn9A%hYKc6I$ zMpG4%)P)A!*xfPRp;1Or(M3fO25E*dPDWvtL0qH8T}Qm&2&;a^3w}flq7v~IHEZ_! z`~RPF-nVX@>Lj3_T~g`yob#OL@_+vK=l@(zbo=^i<0y*aPsfY5r6*3Dh;GaHJrSR{ zEsAc7xY%QKTViQ3?_<4eDU)tgh6}HwkQm+8wwFo{chPNKPg1To>kGYG3&J+t=^Bb^Y$!?_K}8dp2&p zckSr0JCDBppZC{pzx$4RUVH1;9^HsqyX5J<=8jumeb+tfN2AU;a=d=+&W)o{yi1M_ zb*vvfx)SY6-;=yMNjhe#pmvy?#5++wJz$bU&NR;%+x? zwfRdrtyZto>a=@#KGo`z+MDX;{eBeXgG}{A?Orz?q)8m}&;M`tI(pjaFNwQ@c9Q7g zKXDQRP%lX$Qj;u;dcA16H<*sHIPT?9#x>8Qs2z8Qvoja;qYG&{9$Z9|^ASH6ALvF? z@nEJqGw7!JFZTbqPR0E`mp)ZQ?WC1;TJ0Y2_FB}??|1r1H*L3D{Z4P8)!{$7n6}fb z6W<$u8-Fdj@IZ3%WYX@!u+_<5>*bv{W|y>`!bz0B`p$d5? w;#Rzj;K8|@pS9m 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

i& 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 delta 23903 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 diff --git a/src/compat/crypto/native-crypto.build.json b/src/compat/crypto/native-crypto.build.json index b8bcb416..b186dda4 100644 --- a/src/compat/crypto/native-crypto.build.json +++ b/src/compat/crypto/native-crypto.build.json @@ -35,6 +35,6 @@ "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" ], "libraries": ["libssl.a", "libcrypto.a"], - "bindingSha256": "af44c4b1f55853e3ca8f2431c299f830d2de32ca42b250e929835022e0597ab4", - "sha256": "0dd424c77484fc471212e8d6cf2006ca39fd13be728fd3f015bae844c71e549b" + "bindingSha256": "92baac21ba01df72107bcfaa1d2520a672f7677975cef4d3c3b09835a1c11e3e", + "sha256": "a29aa2dc870b72ff2268a0d57958d94460654e19cdbbd54e3c9fe8f92dc4532c" } diff --git a/src/compat/crypto/native-crypto.c b/src/compat/crypto/native-crypto.c index 2c35de21..2d8bb165 100644 --- a/src/compat/crypto/native-crypto.c +++ b/src/compat/crypto/native-crypto.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,7 @@ API void wk_key_free(EVP_PKEY *key) { EVP_PKEY_free(key); } API const char *wk_key_type(EVP_PKEY *key) { return EVP_PKEY_get0_type_name(key); } API int wk_key_bits(EVP_PKEY *key) { return EVP_PKEY_get_bits(key); } API int wk_key_equal(EVP_PKEY *a,EVP_PKEY *b) { return EVP_PKEY_eq(a,b); } -API int wk_key_export(EVP_PKEY *key,int private_key,int pem,int type,const char *cipher_name,const char *password,int password_length) { +API int wk_key_export(EVP_PKEY *key,int private_key,int pem,int type,const char *cipher_name,const char *password,int password_length,int ec_encoding) { ERR_clear_error();BIO *bio=BIO_new(BIO_s_mem());if(!bio)return 0; const EVP_CIPHER *cipher=cipher_name && *cipher_name?EVP_get_cipherbyname(cipher_name):NULL; if(cipher_name && *cipher_name && !cipher){BIO_free(bio);return 0;} @@ -62,7 +63,7 @@ API int wk_key_export(EVP_PKEY *key,int private_key,int pem,int type,const char if(private_key) { if(type==0) ok=pem?PEM_write_bio_PKCS8PrivateKey(bio,key,cipher,(char *)password,password_length,NULL,NULL):i2d_PKCS8PrivateKey_bio(bio,key,cipher,(char *)password,password_length,NULL,NULL); else if(type==1) {RSA *rsa=EVP_PKEY_get1_RSA(key);if(rsa){ok=pem?PEM_write_bio_RSAPrivateKey(bio,rsa,cipher,(const unsigned char *)password,password_length,NULL,NULL):i2d_RSAPrivateKey_bio(bio,rsa);RSA_free(rsa);}} - else if(type==2) {EC_KEY *ec=EVP_PKEY_get1_EC_KEY(key);if(ec){ok=pem?PEM_write_bio_ECPrivateKey(bio,ec,cipher,(const unsigned char *)password,password_length,NULL,NULL):i2d_ECPrivateKey_bio(bio,ec);EC_KEY_free(ec);}} + else if(type==2) {EC_KEY *ec=EVP_PKEY_get1_EC_KEY(key);if(ec){if(ec_encoding>=0)EC_KEY_set_enc_flags(ec,ec_encoding);ok=pem?PEM_write_bio_ECPrivateKey(bio,ec,cipher,(const unsigned char *)password,password_length,NULL,NULL):i2d_ECPrivateKey_bio(bio,ec);EC_KEY_free(ec);}} } else { if(type==1) {RSA *rsa=EVP_PKEY_get1_RSA(key);if(rsa){ok=pem?PEM_write_bio_RSAPublicKey(bio,rsa):i2d_RSAPublicKey_bio(bio,rsa);RSA_free(rsa);}} else ok=pem?PEM_write_bio_PUBKEY(bio,key):i2d_PUBKEY_bio(bio,key); @@ -78,6 +79,14 @@ API EVP_PKEY *wk_key_generate(const char *type,int bits,const char *curve,unsign if(ok)ok=EVP_PKEY_generate(ctx,&key)>0; EVP_PKEY_CTX_free(ctx);if(!ok){EVP_PKEY_free(key);return NULL;}return key; } +API EVP_PKEY *wk_key_generate_dh(const unsigned char *prime,int length,unsigned long generator,const unsigned char *components) { + ERR_clear_error();BIGNUM *p=BN_bin2bn(prime,length,NULL),*g=BN_new(),*pub=BN_bin2bn(components,length,NULL),*priv=BN_bin2bn(components+length,length,NULL); + DH *dh=DH_new();EVP_PKEY *key=NULL; + if(!p || !g || !pub || !priv || !dh || !BN_set_word(g,generator) || !DH_set0_pqg(dh,p,NULL,g))goto done; + p=NULL;g=NULL;if(!DH_set0_key(dh,pub,priv))goto done;pub=NULL;priv=NULL; + key=EVP_PKEY_new();if(!key || !EVP_PKEY_assign_DH(key,dh)){EVP_PKEY_free(key);key=NULL;goto done;}dh=NULL; + done: DH_free(dh);BN_free(p);BN_free(g);BN_free(pub);BN_clear_free(priv);return key; +} static int signature_width(EVP_PKEY *key) { if(EVP_PKEY_is_a(key,"EC"))return (EVP_PKEY_get_bits(key)+7)/8; if(EVP_PKEY_is_a(key,"DSA")){BIGNUM *q=NULL;if(!EVP_PKEY_get_bn_param(key,OSSL_PKEY_PARAM_FFC_Q,&q))return 0;int size=BN_num_bytes(q);BN_free(q);return size;} diff --git a/src/compat/crypto/native-crypto.ts b/src/compat/crypto/native-crypto.ts index 670ddbd2..0056d3bd 100644 --- a/src/compat/crypto/native-crypto.ts +++ b/src/compat/crypto/native-crypto.ts @@ -123,6 +123,7 @@ const released = new FinalizationRegistry((pointer) => library().wk_key_free(pointer), ); export class NativeKey { + ecEncoding = -1; checkCertificate(cert: number, operation: number) { return library().wk_x509_compare(cert, this.handle(), operation) === 1; } @@ -206,6 +207,17 @@ export class NativeKey { ), ); } + static generateDh( + prime: Uint8Array, + generator: number, + components: Uint8Array, + ) { + return inputs([prime, components], (api, [p, c]) => { + const pointer = api.wk_key_generate_dh(p, prime.byteLength, generator, c); + if (!pointer) throw new Error("Failed to generate key"); + return new NativeKey(pointer); + }); + } static generate(type: string, bits = 0, curve?: string, exponent = 65537) { return inputs( [type, curve], @@ -238,6 +250,7 @@ export class NativeKey { c, p, password?.length ?? 0, + this.ecEncoding, ) ) throw error(); diff --git a/src/compat/crypto/native-crypto.wasm b/src/compat/crypto/native-crypto.wasm index 0ab580118d5a449d9dc3dc5c03e00a7520c09420..4a90a25a027b4f0dd82a62e0c5219b51448418e7 100755 GIT binary patch delta 525731 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

d)!UZNL+hb|T*Sy@Z`p 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 f@Ry6fJp47sUkm)@v_ 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%0AcyzR)0%Vtx`2Pps>@lj9^mn9=p))D#!C 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≫KZ~5RMQM9=y7-4mczu2o`vxseufF2(N+SkwiK@sIGoZJb;~i`fK85 zV^cf-nz*VSc9h@1Q_)vcmTs7RfF>Z!U^>nH49G(VPO3*=7wssP#v;q%JFmkCTdtPA zE*dsIhT`CHXhsG#gv+4ORu7`7H3|V}gVjT$MBNk`gX6iqI87WsN_3abltXtg*DZ4T z&y2z*by{UAOhJWuNQnoNoo#1RvOS{{7TFdhGTnFkoOk}wqFGY|2f8B|9D-nDU`f_e z2tiu@;SL40*i$pq1Ea+$RMPtm(G@?F-w-p9M6l-=DB^FZhsQvqJg1G|0opOKkxd*U zYD515X@|2Y6eB*gFQ^Z@#EvndbvrKD0Qn%|criY`$tm@7Z$93sjm!+?1;m};lthhx zN)aCauDWTg=$$eulV&^E6fvzU=NuH7l`UVL3HD@ z-+5j)L8RdsBWJ!Jogf-V!q=N9ZbK!-6S1!yQj;c9yHQ_H)c%8?CyEnLv35=pEsLmg zYWaeRPkQJ2LGM~~=cWWAbH$4|b>ID>rn+-7l(SOx=gHdJGkdal3MU2greNP5rS6y_ zuGELRID$Pv-lM08t80Kfq0Sb87Hbp#i*^m9+(xSI~TePZ9Rz%aBUj6tB zehvt8_rDD}qFl{Ox3iLJPi^;g=#ZR{2R$8KAr{} z^hUKAKV61}@{_<0lUV`Hh1M7PU@KiFwB1Le`CLok=Z!J68-|~UsgBdJ1UITXr;D4L zFU`n+_{N6;vF<~WzmG2E-1-Ap ziq0mW!1cnzn1a5ACE>z4*SUf2*-> zHnjH{!*`Lth5Zul)BM2=c5JO5e{>%^>&N|0>?0w6;@`m_JFLIuVfj{@Ek5-nkIRJT z+C;JhkpE%r2M}b>s77-zb-$<^=7^_o&xiK(*obH)s$&RsRO{xz^fOomJ`}gL0T<-^ z4Ork~9e-tHlnUqt0;^6^9A&HsTI#0^{qlzphzF{-Kh%-6-+d?=T;(9!i_rO5RbyhX z>r-OI$Qu`gF$QQAg6yq`lt~X-mybkKtVoM432L#r2)24UN)g2qQsX~@#63+-{|KvQ zv|9ZU+^~aH`A4E-dzkIFRmzV}8JLks&ReP}tcKas?Oku=+OhTuL|{XCgLLuK$KqP@ z85#wGe}Lc%g3@6?-Jx7@|3n0k%9;yr({XkGTx~5IK38O*sW6Ms%K+NIPXHn@N>-?a zbH)8wMmc4oRxb^MDZq7x3}eD~SWmRWhKJ(eRf7y1ZT*JuT8ep$&f14#tjR`-jgay% z|0W7*SQ%Djh5D>aT+LvVqmX=&p5WspTFr{MmPloNB69C?Ao{~)<3enMxTQBcZJN=O zO!fvJ2Gm?^GO^5{q|*?wMZ}N1gAsa!n&t+fPN#pLKF0s1XX`Aj!t|OtiQX*C}DpuBg;va=Q{B1t)Dv}np2)g-Kr(Wz=<8N|y0kl7TO+CnHxG*v9&v4q1*;?O zT|VUJz4tRwqrrwm9sh4sdCUOqm`J#ki9H8itB_Teg2#=Jr-HAkKFyLtNnj=PS(`4Z zMW2bhl*vix^VnEs7#$xAZ`qO0L|Xp7M4H<25W$L$+0$|D&pN{nbA2#Cf=#SfMf0Jx zyrHg{FX~xuX9j~$L;<QF&B7kULdk@gA+yyqoj3heu_wQvrx%}qApVHlgA`s z1z+_2R(wY-|5*shexbs_kpet@x==K{!W2p{b3R$~#&NRb<3+?B!e4 zJIh7QY7^jPMT`|r$gEusyBNYZR>0IaNHtyo$4R+*X@&TA>;JZAPzH+pFZt}(r^MUC zXf5uv5)+4@i5GcM9{XVC5tVArs36;D`G=)l=PrXm6ZmUEK3Y8FD93d34iWUgf zh*hFXt_v8GQzyKN;3}bd{)c83YZ#5>M88Bi3*mwrzC4-dA9DpI2R0 zKkqA1a5q*O*3`sF&YHp>oNTm`jW2LR z3skGs5VlIyeXB(cp3nglNO*+_4owQ{W_p)`G1sT@AiRCBn!H*xse23&WgME}+MSI* zH3}PsM=06`7{O12)v49uZf-_1SH|0ziG2&S&=a|3KCwpBl0V^QQN0jR^CwfVPf^!i zBeL3{9?YRz5B`j-$FCd3dT6r(S;2`PV18FW($XAX13Esg(!Unn{S|3y-K!#0fc=9J z%809i+JmHHz>Iwi(Lgwn&LhK75JAR%E!wAiu4QP@bG&_&OXSC|VJT)rntt^gB}?*g zjxBiq9+Re+F$_ue&;e*ZFfCPGz5zcPtRDRap#)IIz7d6tQD&JHe*4Wuj`$arH{c+F zIFmpMih`vB?L>d$VSJ<+04E$lfryMWu}eW1(%lxU_pmV=RHTa)Y%M@tgLv{1g7zaM z7etsdu*ogGrAldU`{Gtf8$L`!oB~RSuti|SMikx|Q8<9Ahj=o^duw4{__*xES~1#} z`gM#}n>9AN9Zsq@zZHeN;gO^9Llqj!hc2HXwQK1hK#Dgj#pcbze*Vh3TiLv!m!uSDiMt?yu`S%X4^~W|E!ILk(^4q)LxyUa3-bRtmi+|m$tb|R{Hf7F>qf{lr0Fw`+n&`5xu=WOx;4g;-8_SUmHU zP8+57Hxq)7d2kNG9l~*JZI;DVpRkd-IIs%6@$xdCk(-Tvi#(s=8RZB~rBT7#K&8$q zWCK2SQxnZTSQ*M{G_}n zyYw^M6`=NS5uN3XBw9Iel^6y^s|L7fv+Y)KSKKllfU)MPFVCr^lAoT2Kiy z`o%y-UcFV^g9{akwu$T8FA8D4kXn~qB`SD`18b-2j{H_>FZOqa_yF4CpacHY#~Ijd zKG+76(s$~sZP04;5;LPg>mn^8&TweEKawD6BQt50h5v6BtZ<3%6dXP{OA4!OQbgFL z?xK2RJFG(M)SKHyoo-mU5f*RS%g~|(97D&UXncsyjYnHtq{unD4r&CDGTtcyu}ath znffPHv_sVVzhf{@?GWj_!Hfeiu?l*Ro-{w@+SR%PI2P(>k`vB9;-r`D5OuDD#Wq%t zJR&zkI1d+HY#m6}WaYO09e7gqYgQeMR33}DA0pTZ42UOgeo!rUifbF2CgH7Hgbac# z4DnM4f<_Bqhki8KALYY>U!~sJ34733wSA{(T+Pux@HVtd)cNzQMC{EN)HvMefXm|X zFC^y|k(0?sb0mMQRf*a;#WC1hWeku7+f=Jv?Rg30FDnvZ^NzD7!BD8j?GU2?C+-qO zDNrG(+~GYfYDd)fyC5(jKy?>1z0oRjx0b$d+l}cTtcLCunIYqd+~$K}Q!hA_Dp#NE z#>w>q%C`sL2UPn#(3;BCANOF-9Ha*C5q2@$jG%9r5k^lZC*uPS0Gka3=85Br-Rwn} z(RYoi0+4ag3o;1%7C`2>Ajo&^T-Js!?;>HbD}ByJhHG=9J3f48vLxaPo>u-+bJtJxlB0z``Og|QVz zEifWNz3Kz!8@Nxj_8nI9_la8?!r=!U2@{>?1Mi4|B=>73Y%7Hg+krDe4bLNZDpfUDcgQjFmj4nnLg@cG_fu+)6BRR2SwSuF<& zpCDe3L%bGud!+WnM1qG5Q0oqfP~reM>)k0-M-Pea#gT9Hq%N9~cj-y};eTW3DoyIx z1EQANcUW|Yn6Od*8>Do}34^_flSMizoA|5P@2iKY(&k=!-!DjM`(C_VpIKfpyf9pH zeIQ99UN*q;n|%~P%bUx@ZgVKZlON5p@i?xM1a4p=KP1?Pi6#sa%A#5*KY9OT>yf_kdg zrGo_=B_1LUB@rg-<7hxkfN~RI=VR@v!3hympOlN_%Qs+0xo*Iza(L=jsFo*nr4OFe z9UE~H`s`L!c2eZ|w+eORDIB9eby8${4_BNL{ZZwF$;)%~{SSCSfWzT zh#Czk-C_v2UGv+O<{L74G2$T#$vr3!9RD^Bip^4W>lx7`7BqptLe=Nm8X0QnxwM1; zPnErY26AGl+I|Leb5@-PT;YhUJZ5Pi<~_^fZOBIDCh)}I1d##bO%L3wpgktXDO zK6~i%TDomxdkOAetPr_f9aLoz0J!u+WAcP_oDc$!K=0%8Kc#Bn3A%>0NlUX#jtT@ZVD_N#?Yj_r81Iw#%I3ztTGE?`&_b@!HOg*8h5 zy@G!SC-|vIG}Ld_`>{@v{BrFbV|9dBx(*Q20ea*FKn%v95#v`Q#%}~-SfOyvRv&LV z!R#F*Kh6EH?N1pU3l7sFAUj|Gl`x{yPh<17#$_MjA5>zYSVvo%5gOp=u^iHu1NNJx z+7}tITy#zlRN=ig^Fy%xH=qjGfkO z1bu`aZtZl-EkB^;5AS=f^^NsT)iAEZv^++9W84% z-Uwun#O%R`DaYhP)}mpbRwbCA>=M640K>>J(5gXij&u1e~|IAj{icYY8Nl-`OjynKgY}ZDM&*U zhkG#}&jV=%a3RTrcv1W@ybbgxSKx)^oi+B&b)$dX)# zWzlF--MSE)?=o;U6AaA*9asr8-YShlijqNbd9af5C*AyHn4e7jlke3FT<`-=d;SVN zYUpzq8HmZk93bYOHNNkIOCm2hH>96t<4KKAko9X~o34hP1*BFD|BuBUkCDQqhp1<# z+M6J+C|;F7f=J92x#_vAeyQ_}_{Q8^fW})cgv7OwSsEf@EU`s;T@8An_QxdM_xm@~5Xb8zbT19Pla|(vSP!D` z7kjtW`I~kt_iy&o{W}#~*|x-pLHo(80$A;0q+4@4qwmj3$j#)&oRFJN=97Sh#WZ;g zsdWB^PzKhG3Gzi!%5Rmhwv5adt*1RIH#Ilp+<$;`RJ;m7x2f2yE!ddNZRQPLZ{4z&;NHS~?9O_zm&5nqCU8=}kXb|NXlDJ5~QZO8=cg2Y43qakJ5eE4Ut9US-I8q(;f;iYg1))oPPK*RW@$@rN)azsVO%%DHn7I5!Vty&o)@EGHxB-z1E2U*1~flO@;%?UP(0BmWxgQyI95iTsE z)J^DLR^&q&pP;@;lNq8)kyfCoGiXmkuRY;P3<_3qMMNcSP@;1UndO_Hde@MdcUSq` zu4)_53_lvN%QV96b%?~Wf&4%h=bg$ zzC98u4Iw495d+K4$0% z5F*88C}`1KFV<^0(VelnW9(^2t+7JwdQ4Wodu}#^(&+dFEY3_rHNf^dJjW!)nX)mC zsW?`jaYSEl)4;A;6@S#F@Zb0U0l%}5s9ur$4XHk&xg18+^x?h7Ws_oRo>~TCb6eXN z`a5c;*IMnsLjSDsIlN z*Fgopg9}7raP`WO8UF(;#?`=%0BjoYcG2`=2mVHujXQ9B)<`w-&sa0_Ee(W-NjUHq-%Z534wlor(~|YR&JJ% z0yN!3qX0}C*7xd(Tv_AFE%<;HgTO@h!zR1|3E%?256`frhSio$ z)j=Sr`2s?e^iB?)F*Sypg#+Ib;hi~ovKF-2j(PG*{QNynwoU=OPvhIjFpHr2V1W!F zQQ5jY*^ovx)&xR-{!M3zx>+(*dcG`l0T9mM0UUD4^^Sbm)c;eK8s1vgDB7JByc34R z+?eZ(v`e%&3OMnMvzA;lrCO0MpYaVPLG6(V~h3i9|UqeH<{wHYG2wk+yoYDJGDE5*#}I$>^!Hd35!d&!FYs$`-{Ys;Ql zSQwWfpInkpyJ{ueL`cV~NgeseCa|a@Y6n8Q_E$p=I-bY_fEajA2lzg?KH314gv`HO zEvh3QKp16VUCA>rH`bLs@H4iqY~QF{Yk>f66om-H{l0!}@r%%^V@POWgnCwEoq##X zI;NtFU~xI3dKSsrX-BiPZ3vPWeAN0hDJFf28jNqSYral=X-fcNRfk$B-x)Ds50Oay;I&1&r6My5~_UFfAvK-D609%!z(3VmrpKSj3QqBXkE@04Wh>uK^+Rjf*?&Gu)omkl($2#( zf;kBa($^7$S#nNyse9VUo^2*KhMj^cH@4C!+hTT!7(3i0&+2Fg&fDPz+f?BP>@ptt` zR(3|h-z{xrcdef@b;JJ|w>hLhUMD||nw&h0^n z`_v=tW$n5pH9`7e*2*?KA8l``sLEPDZjbtRs`B=-VcR|2X6PEkIz$CA)?NuCtUGN`IaviCd4 zdUA*4nnAmL8g?^m4(cEfbNe0{t(^q^&VGQ{rwu;`(*frprFGU^-)-vDfPHWvdx}+{ z;T`@ThHqa99`QDn;VW$^xy7hpWNf7pA5<-|8bOHQ)Vc{0(?1xvj$rUEhpbSmf1nrt}Zyzg|8}b3%K9LCGH-NIT}&UYr)LU zvgQpA;*${omCn8y%)m!-8W|hGw<;ovcsN70ENJRAA6rS}rp~b_4P4v<^^h&wRd+UY zrflJ!u8$C}QDwF)qCp2`v3T6hW8%#yp0csTOI3p|nm51MMP}7pq{U$F%{jpi5ew=Q zTDD3&z{0EW{Z7b;W=-N)KUSsL71&vuLU*(^5)vt1;`x*xS0z|4r{a)q9$E)hz&!ae zSIN4lztL6lo)A}X8&vvR}DKM0~HStd;Oe zO>^|_bHDOkQ$3{O?vz6H`8F*kMpK}=?`qldUI)w9K70SA#42q>#7zRr;`G7;>j2BF zG!1OShRc*zN3NDR6v~Xf%1nYQl(FE>%N4Y{%A5Yj9{OHUYr0D9fhMvuFoIqr)%v^1 zzf%L6?~7z@W&nnDlWi_5S##uEyWZLXM$wqT4_{H~b0E}znF$+!8c z>oqb3bAH=3@&+2Zx>L_^ro0YF>|?yo!~1zL{`lwg{R>EFpNNptMBAbz9<400o-ZrB zRzB;mJ_K=y*XT3Iw$-g)9^#ObfMRZUnN#hU2E7(i-uCXYZ|0yRokk6waGYre^-qko zC&k*6d0y;~J!JP(N`%CWkPiG7B8Fo@57{twB2RX1RloL-|AZ{|NKaW0dClntC^V); zus=}gTRmlmL?e1?B+siuJ!ONdh9v9rK=dr*b*`2=>1oUjSH;I7ECsJx9wH-p+Oi>@ zmECi_-0zEi9tZB!bJf!`)crR~D`yB(RO|Udw<#gRMv9SO89JSY-6)^LogwFLl()$8 zM368yc8brWWt=O0pqG5Bkj-UKCcnUskptvAa~5#wEOi!5zwB>1Mh6-GA<8>Z%gO(Al!@hIAwy+-x?>v{G4E%GXm zMZddcb=CA%84FqQ=-slGn%hdI;;in&x5}H)r+K%^Zq2||AcZ;t!_HUY?TRQ{O{k(Z zYSD?jCxA>>&2GcQIUG{6N1f?=je6`hS(FWq03@J{DHV>M6=%1>s2CRc!ENA;)6_S& zNu3jk)t&L#XD(WIiMPw9Hyc#UNk7G8#%g2#h_Rx+#*>yYJh&1-a4v+(7(1>XX8_kv zaC;`+g_kI%hTksl?&bh&j0A8RXz+r>K-D*daczkJ-Rv|$HNHbO^UT<+5p4bi_2eC} z^o>=if0y;+`SRN+7&zwNA=9%=Bt;r!;{henU0}TO+lyenqvG}}a8(*o!zI-fcgmt$ zogu{~bncKs_jQL9T4cc0BH_5uCUn-@mR1IBm#1K>>v5{(06OH*_%0c2<$mTKbr}pL zzlR~z2nDabbk(MdxfjK zMrUa9$-Tt1sfw!boqEgQr4aY-eKF#oe`pR8EXq($*h~$h280m1zoy$wJM(ovclMSU zzUAt4Z`rJ=RxU50RBjs-PZ?wEMKZ?S_sYWjDV7m*3DwTtdco0ryj#i>{i4vZy+HFXXZ1s>yw_Uj0=y5w&6e$UKJoe8D{JM@K=d zoi$Un=d%sB3bGb8;68aHg$LqYg+#qBbAs8+0Q_V&8ec z?1Y%L(f317AF5W~FY850Fqdz46?;_eKC)BW@c8>LG5)SGl&Qj;nbDRY+LhItePq*A zhqZGfb6EGzKHyFx)tNr>zUv~fV{Ty>fHUEjbNSdQJzolOn?Aw-RJtzSPWN1O&Wi%$ zs;h_H=BqzwC&veWl%W82CUwp{r#^`1EAR{-T3W2ZulTZ-NouWKN?6ip1ODenBX&gq z1+GL%;}&z?0)HB#Ky(&JGSRRvDhv?{fC19QRb8b#pnWvDS?%$%;RE4jcEmFsRB@%} zb-#7HqQR22Cx8)4eSmEIq83~MIH5!Lk z7w%?r-AIveUpvLC$A;h5xOf<}BPk3KipKtf?2ZrWF6(ySie~K>VudsDz9>ADBzE{4 zT*9(QSVIE#u!-^Z6(H?j9>jVdt788wo2Pn(Fmbw&#>Jxt-n3N0)dfOIuHV?#aV;-g z2(#!ysNvK8gY23U~U4J*#kfw23}mvSIDMd}y_LP2-!0Jsz$G*RPm z`*m4B(2}XJ_#v5T&5CdUd`}0!jE7{;X0vFvbNdpo-cN>`$i+Y^ zuP>G+Bu6gI>Z;cxnp*WsuAV0z(VK&Q=EA`$%<@mjF_^J(Jj@YGaJ1=?#$1c*$OS?h zy>%K1WY$*OM>T=6s_5{ldMg51w706_D!mRbt4gokWfh8>9Y#WLF1*T>-i4ywl@(GY zeZ2vQUdv^oS;PD=0%tjC9F<<{MWq=;we#7`@8sm*FDRUt?e-boZa7RnD(ZD5&S=K z&tBBLIBe#6zyISCvSr@qsopZ4k7b-=msMTHpFAP&NB*rwPs!#u{nqnI*@89=CqovG z7c=(sN!fsJA*c8>kI-+wdQ#S=UUYOb%|^3b%^rska$S)k=_#1lJ>-9Q3dB&V{)3;6 z9tmWD8$3KtT0-79)=hTolwW;z&d2UVF?&LM4f+1#$_S~9G3q(joqI< z7meT>9xC!Crf?4uT=hDVHexC+9tY5)p)$zZ}Mi;+r_OxsOSL3};%m3hK%hU2U{51WC?7(?PC6F^eDsct_mu+G2 z4ebs6hdb|HwLAVH8&LdY(?0OqKVReyD11iV`MUt>8H5*OIuW!_vWT2dV_mn6Wr`EU zS`J3pYjj9HW>*7OGR2#@)w4?Aqg8<`+u>n`nTNf^Bz^r zXJt+Q-jKTI-!i8Yt+fa{h2utpGx_@75G4vG9pm3%8(|3MohX@C1OD3BzIIAKIGfb7 zvY~IUTJo%{18>s7XQ6CNQ4OAxe=_yOS$mXvPF@X=ZO_4xyH^zzBc5@ux~T+e)R1C% zV^X-fIN;k?EFUwU8>Q?Gu^C8dfarG%--eZ79sHo)F2Q++De6QC7~d3CufLolRW_Tf z`wv!tLyXoNWrzEtqP)rQ`A22iqbEVKvA-FLyR^*cUNia;eixrT4CozjrK;88cqO8LjX%y45t#N3W zjmJUc9cM2QXaIAK_V$mQQs4wK`t5R1UBd`LAwp1_|ITG3MDWS-8Ymam$M^;>9DNKP z*Ub8EsF%gso9*UYPqLnxz8YPrnmoGt8ZtO6bneJ^(k@%p~X-(;Y?$!ih5}evw zevy5n^~=)s9V+|R%Musp>Nwqr0S&#A2F?dZy9SPI^~Go%VRLXa!aDY<)Hh^S%4ETK z2yj+=(b8(xWYz8sV-#j%{PxF2`qah_Oq6EEs^kq>55qR|4S1)1P~W^E`}EqIt*;ET zVX4!`IwYdZ4yIEdh0#mDSrpa9vfr9lU;$Le8-za)wvi4ci?)AL3@28!IJbTtpjbi5Id}5yCK!kClyz^>IOZC#?4^5Uq6`*wa~E_LsFN*dRwS zLo;xPP|V~ZmlM#tAjt?x8+NC)<7ACuclM$=S6WELjMU)lIT3q$@|Jw6 zCoi?qd(PcOkZTR(Menyoyw@vw5xUvMO1FQCK<#}%F4O16%k+oC1XJQrl2<8WN=<`7 zTP23Jbo0FT9wbiBd5|wfKzi>{+xhYGJ_rpX4+JyS4HIMsBn!5JYF)>SkXrqwd3-z}tvORJGelSHg#v$QdQ{?9Ka`?RZRVT@o1`ccZ8Mx83Yk;GdL5XHPkW^o z6d)LNnru`s+^{qqq-vXSytiENIS}notxL9Dlij@iSx*#XCp6kFvi*Mi^JVW68HKQ_>4( zn)QMnZV$n(DhS^F9RwJx&q||ja;FP4MP)-vnAltyh?9@`De_;9o7%^9FI{mA)*gf-DkqC_nP|iOnC#BHoLPd#{L00Aa$&v7yk3jOgY#EfCqHe zdpbI=jd$Um>i3Rp;UAu(#StL<{2kbb^-qleqW;yfcVuICZjsq;B`&4Z$(~{`MPB-^ z&hN?=E)b5}R|5Lc*|*fI@5=f;oTkpb43KtFGm(H3YQ%1Rb{SX||DN37*1Gy~Kxf~R zS*@K~zr75Qn+UEqO*6b9fYpA_sJI(v$q~L!)y`S6Rh`coBe`O3LeO}W39{G22dB2% z&Gzp@#hR?1dSAA48&~eZG23z*SXdmPY1R8Cs+q{nQ~~l3#?6-Y9ZnAr>{MxRT}(P* z15TCoWlBOwr#c|biHRff7cWyzO_(hU+-BlbeywYQjqtQG6O&z5RiFAjLs3-+ftQjx2Itu6JMFFh}OPFE_a_pPeK1 zJ@Lb*#)F;Nzs_@75I%J|e~xU-VuXN6L_?_4b7TX?rSatvw-^{EqCj+(X{}YZvG+OC zf_^X$r@jCnDj)HK5d*o$ZOUIhlug|BEp}hd{!r$)FA*hZ`n>%^S*O^G%R{5^AA=uE zoSrv>sC14$${0W5Vf@gx56<0h2yip2F1+Hrp~h_#AX+~q32p%p;9{I0e-=IK7G*@B zTaz00k*w*mg#OWCil{T)7 zfODIO?x~R<$qcuZUq`@rt<0V)^T^)A!KNlS)!rU$_HduQ>7+?4;TWlCSAsO4<%K;S zP-H|Y0&+;YCVCU{u=B+DQTu1FL^wqBfhYn|E(%mC=RE0hN4#>_54atK8~m7Q%Lxyn zlF^PB5kISP%Vem7+4X(cM987|Pak9ID5<<>tkChew$T|}E4=DghT}cXj9zk^m{}&b z^kv&fO_4#On{b?IU6j4dwSz=%&6;mA`Zhm+aqSApFtxA!mmV(hvM3IiRVwNk<9_`_ z)+FO0(AFwI$i}1Bh^qgoyfR{nb%ts0L2&Zh{Lb=!vg>vd*`IoDv;^Yda=Mq~L8~CN)e&5|LylsGC z=vrfuOypAm+>Cv0_3D@9@`l>@QWNGEd|6)0LC`oU%Wj4jpPjg@r1Wk#wxu6@9(b)UgB;Ec?ImGTDkIB}Jn zU036oOo&ep#G+7&gJ(YnYsvzz{d-rzv9VqC`bxf9N0+$*-^b11_i9d&A6R6@evX?S zjB2aptsIOHwjO+WZ4o=8r>5U(*}k#<@&%jw`cQr`J&=(0@#?L0+|11@VaKGv&a)J@ z*1f61HBh{^t37LEOHy6PZjK^nS8^IO{`z0b9&PbK+Ea*Z;^%Cpp)O&+#aH9> zD>Fi=_S<|_rfE=@fB$RwJn*;t24}LitH<%feh7OOTl@7Pj+5@;C*Qz!yIr07Mm8aJ zTXum;{Rv^gJ=e+&c%Q#cj^zLgY>p^cdY1GXqem+hG9X?VD&s4)Z=HOLz^V54tYye| zY_kEjkop6!zWR>Cq)GSvZ{dvEURLp~e8-21rhTW|vHv^S0Z+9z$Yvz9bbA~z{anFn zP48>Kvx`?_E`(c>W{)S-w)5dv)$PrM8W`~^gs4Njnpn;;Hm#EO7i`s(Rct6*#g1+w z)Y;elq(9x#Z5w2L4qhr$RN_2AXgzqD8*#1zUo3k&8&E!jGtOqJ7dFZ!=*OoU<#ikp z3sMOHB3qb(WaxMmu3OdnUjB_!o{Ccp_)@ZwV`bKfW$)svIZN0m^D4#O%~zYeSL*xk z<)5zC<@fT-ySlugYzrZdRZynbKU77Tj9}`@D2Hs4-O-Kpo8)BtJiS?Vj6(%Md#zs` z`WjTSWiw76Z&#^XTX8BFuOO?Kg;XZFzWoBp}rxUcEPjP= zYF+k3uLhmxWSUorc3ZqTyfVRgqZVwFO^6r_ld#k0xD!8WJN&8J zRoCruo^Si89kM=`aDp8Npd-JLdFXa^*da#)YU2*Mfmjmkoox92Q7&6hpYN1^U@x)V z5dVvsqz#oPz`9A`U%Z8S%`W+XL9>f!Ca-d-LoM7TyYmZ72>i0}L${Y@HFqaX-p+#GY)AA>5W>#GXJJ z|K&%+R6vCMa$pQG=ylO*zwFC0kS5uSqh57t?tb{gwyWSznk`-RlYHFt=|f^LOpfHO zmi#1pu))>rkHcT$^a_o)=knD{FM8v6<LaNv-!#pke-#rKg%(! zlj*?mWvIub)RPAg9K2mkJ|M3lK&<^b0bbYS#H!V~19B1oCLEOQ@U!)xY-pOelvVt8 zkW*!557slj+UCuKYW$0Qfka~2L3~*B0$Zqg=Bi)hMj)GXNM6J5DRy;yKeL{b8)Tgo zhvc8|z0YCUkyRgt4IEtgSJ{KmNp>edR~&Hpo+|lO%PJRsg@1m#y735EtQvbnK1}Fj z8>dTgRTJw*6w-E;b`;Kp?dqAMvJHOb9M#b0kIKKYTS+jL0er5WJI*(!b3R|0Y0@Mx z`Zw8!V}-3Z{6)7r@t7O~q|=VcnfST+xFOL5dl^aeg$rCs$!x>y;~K8V3B6sdJE5N* zE0=e(4e>~54)E7CxMA6G_E^@m;9HK69>GK9NGJqR;3Pbb+tn2(4 zxvNb5^$g^S_tdU4<_z5na2GF!UFaeKxv)aEqS{OVD8U|pCm=svfzx`k)SDIZX5UP8 zv;temCY6UvcztWi9ylw9`XK!tJ}2ief(S?;{mya~X&(QGcJ$l@=W(LuJ+<|`>5pS6%F=)zg>{|zHgMeAls$D!hpLIARglzbLZ{G%Obpl zo5p*ICxhubV!{ox8WY07UcWlj)wkv2p`qt}z-ReGIXoE2cKYmJok%_k?6g;@Yw+0} zi#UgBXW~CB;C{!>fC%V}7=}Ra$X!|1oJ;P0zKXkW?=`$Q`cHL3#jUQzV1EK90 zxw3?q(4)TYMCqsvzzj2)K3RcIxRx$fgb3#e7pV_!>>+G4)F0-kg|VT|I+C9TA&_IE zsAoK_?c0-aRP)wH`dmHxNwc`n6%QG0Uh5;3&Mr752VctVVKT7M5aF})`r7w9hKNfQ zfpy-D^s2Z}0YVQB$A$8`V?G?%1+Ep&h0``b_QLu$$2i)NXx{+S=Us=+^YEL2>aO@u zv!wMo!MouqrdDN@DHR{O1~b1iKGdp3xOW(qpNvazNT4a-Uw$OMc1B{l-$Cq+ zMEK1>^>jk$ik27&Mu+H;SWFVr-Oj^!LW1GR+?9wbp*1Ym*ER}QPQlV=eO0zSA+*GY z`*kNLhB{)6?@kQekdEJ`wL@+FzICc^-B5cZiI`qDbYBR+aqUB`bb5Q-ISmVp>#Jm* zVt;2;wDP4$cb?x^1GC((BL+l%rHRcrGV^HY#c`Zhw11M>4;VLKqvcS-Jzpl2^t#%c zbWNgtWE9;=125tG4{C4U(t=rZ65#*THD|h)Qa@&0V9O z=n~4$aaVg7dY}^yhFL$NUhW){YH^p4tcms~K$o&sq#=|AW9H)5S=f$r357t}sog^9 zD*LKXf~CN@m~`8ZyvqBp z3O$U)-s|d6sM;1>)`kUVH&r9MhH9iCE1TPxO+Gy$cr#Efz&G{5)uBub=WhI+$tZjg ztn1S5qA0foThzI$Lj_(793%jDkaX`FO7gn3+3l7ChAXflyXB4k7LIk7E-vVo%9)rf^o4D&( z=$shHKU^0oQ2&R$FM*GuNc*0inVd5N19TT5iDV`uoZ%D^JSjy`yl>ZaUEN)G72RER zUH5p^00}oJC{U=Nk<$wS5u>P_f?NV}D~Bj}fn17!a((}&s(YrVXF4VrcE9iUz49|X zQ&Y!NPd#@%UG-LXeFG_3s)Km?h#BTq*W0&VKjC`;(o+T=FV zZEdvJq&vjHMZS;;Zrl1uOpd{XaucSu_1{b^w+0qHdg^uQ)hLD20?HBcATKcY3cYP6 zGD6`n0mSEe>ep)Ti2XhF78gTv(p6_LnoN0)d;z%?=ivw8r&<{Yv=Vs9@M$Z_fl^Up z+Xc5>u6LFPY7^Z{`|O~p4nCvJp?Kf%d{7l+Cnn0LA*>d%2#MFe$G! zsZJyqFS1e8yEeSYoGGAk9m8drdbFKfdLSW!^hZatIf>1@yf@AV7K?#35YwS6^rrPz zF=@TwA`fXpA%C*242S?TKLw(4zdHWO;PxiTX>OXOd#cT0dJ-PXXH z01F%d+i&OqusnUeUXlPyRn6_c`O!R%7u8wt2K`)ZqPX-1{j#eq^uTqn;S&x|!Z$mv zGo?*#^_hv2pEKwPVO**1fs?uY20c$3A&%Xk|Ne5TZ#uIf4h6CuARCZVAA^fh)0piW z9S3IhPaZV&`|^$Yr1bIX2#XtULZbd=@$gOhJxHk8ev^J_U7VgFNe0!i^s~g-H|uxD z#z(H1Z8qsh>&N&R@$u!n*UkDfS6UPDB}|A;v&yp1hwihB_vb@e ze>yM}>yrFq>hhyo^n0(dx=ezR@LD4fL2%up*<%0AdLuFES9-l%h(hzMVLE?K<3)Tf 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 0xffffffff) + throw new TypeError( + "Value out of range. Must be less than or equal to 4294967295.", + ); + // Pinned workerd IsolateLimitEnforcer::checkScryptCost (2^20). + if (N > 1048576 || r > 1048576 || p > 1048576 || N * r * p > 1048576) + throw new RangeError("Scrypt failed: cost exceeds maximum (1048576)."); if ( N < 2 || (N & (N - 1)) !== 0 || + N >= 2 ** (16 * r) || + r * p >= 2 ** 30 || r < 1 || p < 1 || 128 * N * r + 128 * r * p + 256 * r > maxmem ) { - throw new Error("Invalid scrypt params"); + throw new Error("Scrypt failed"); } return new Uint8Array(syncScrypt(password, salt, N, r, p, length)).buffer; }, diff --git a/src/compat/crypto/upstream-keygen.fixture.mjs b/src/compat/crypto/upstream-keygen.fixture.mjs new file mode 100644 index 00000000..00c02376 --- /dev/null +++ b/src/compat/crypto/upstream-keygen.fixture.mjs @@ -0,0 +1,690 @@ +// Copyright (c) 2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { deepStrictEqual, strictEqual, ok, rejects, throws } from "node:assert"; +import { + KeyObject, + SecretKeyObject, + PrivateKeyObject, + createSecretKey, + createPrivateKey, + createPublicKey, + createHmac, + generateKey, + generateKeySync, + generateKeyPair, + generateKeyPairSync, + sign, + verify, +} from "./index.js"; +import { Buffer } from "node:buffer"; +import { promisify } from "../util/index.js"; + +// Generation/export subset of the pinned upstream file; import/key-object coverage remains separate. +export const generate_hmac_secret_key = { + async test() { + // Length is intentionally not a multiple of 8 + const key = generateKeySync("hmac", { length: 33 }); + strictEqual(key.type, "secret"); + strictEqual(key.symmetricKeySize, 4); + + const { promise, resolve, reject } = Promise.withResolvers(); + generateKey("hmac", { length: 33 }, (err, key) => { + if (err) { + reject(err); + return; + } + strictEqual(key.type, "secret"); + strictEqual(key.symmetricKeySize, 4); + resolve(); + }); + await promise; + + throws(() => generateKeySync("hmac", { length: 0 }), { + message: + 'The value of "options.length" is out of range. It must ' + + "be >= 8 && <= 65536. Received 0", + }); + throws(() => generateKeySync("hmac", { length: 65537 }), { + message: + 'The value of "options.length" is out of range. It must ' + + "be >= 8 && <= 65536. Received 65537", + }); + + const h = createHmac("sha256", key); + ok(h.update("test").digest()); + }, +}; + +export const generate_aes_secret_key = { + async test() { + const key = generateKeySync("aes", { length: 128 }); + strictEqual(key.type, "secret"); + strictEqual(key.symmetricKeySize, 16); + + const { promise, resolve, reject } = Promise.withResolvers(); + generateKey("aes", { length: 128 }, (err, key) => { + if (err) { + reject(err); + return; + } + strictEqual(key.type, "secret"); + strictEqual(key.symmetricKeySize, 16); + resolve(); + }); + await promise; + + throws(() => generateKeySync("aes", { length: 0 }), { + message: + "The property 'options.length' must be one of: 128, 192, " + + "256. Received 0", + }); + }, +}; + +export const generate_key_pair_arg_validation = { + async test() { + throws(() => generateKeyPairSync("invalid type"), { + message: + "The argument 'type' must be one of: 'rsa', " + + "'ec', 'ed25519', 'x25519', 'dh'. Received " + + "'invalid type'", + }); + + throws(() => generateKeyPairSync("rsa", { modulusLength: -1 }), { + message: + 'The value of "options.modulusLength" is out of range. ' + + "It must be >= 0 && < 4294967296. Received -1", + }); + + throws(() => generateKeyPairSync("rsa", { modulusLength: "foo" }), { + message: + 'The "options.modulusLength" property must be of type number. ' + + "Received type string ('foo')", + }); + + throws( + () => + generateKeyPairSync("rsa", { + modulusLength: 512, + publicExponent: "foo", + }), + { + message: + 'The "options.publicExponent" property must be of type number. ' + + "Received type string ('foo')", + }, + ); + + // TODO(later): BoringSSL does not currently support rsa-pss key generation + // in the way Node.js does. Uncomment these later when it does. + // throws( + // () => + // generateKeyPairSync('rsa-pss', { + // modulusLength: 512, + // hashAlgorithm: false, + // }), + // { + // message: + // 'The "options.hashAlgorithm" property must be of type string. ' + + // 'Received type boolean (false)', + // } + // ); + + // throws( + // () => + // generateKeyPairSync('rsa-pss', { + // modulusLength: 512, + // mgf1HashAlgorithm: false, + // }), + // { + // message: + // 'The "options.mgf1HashAlgorithm" property must be of type ' + + // 'string. Received type boolean (false)', + // } + // ); + + // throws( + // () => + // generateKeyPairSync('rsa-pss', { + // modulusLength: 512, + // saltLength: 'foo', + // }), + // { + // message: + // 'The "options.saltLength" property must be of type number. ' + + // "Received type string ('foo')", + // } + // ); + + // TODO(later): BoringSSL currently does not support dsa key generation + // in the same way Node.js does. Uncomment this when it does. + // throws( + // () => + // generateKeyPairSync('dsa', { + // modulusLength: 512, + // divisorLength: 'foo', + // }), + // { + // message: + // 'The "options.divisorLength" property must be of type number. ' + + // "Received type string ('foo')", + // } + // ); + + throws(() => generateKeyPairSync("dh", { prime: "foo" }), { + message: + 'The "options.prime" property must be an instance of Buffer, ' + + "TypedArray, or ArrayBuffer. Received type string ('foo')", + }); + + throws(() => generateKeyPairSync("dh", { primeLength: "foo" }), { + message: + 'The "options.primeLength" property must be of type number. ' + + "Received type string ('foo')", + }); + + throws(() => generateKeyPairSync("dh", { primeLength: -1 }), { + message: + 'The value of "options.primeLength" is out of range. It ' + + "must be >= 0 && <= 2147483647. Received -1", + }); + + throws( + () => generateKeyPairSync("dh", { primeLength: 10, generator: -1 }), + { + message: + 'The value of "options.generator" is out of range. It must ' + + "be >= 0 && <= 2147483647. Received -1", + }, + ); + + throws(() => generateKeyPairSync("dh", { groupName: 123 }), { + message: + 'The "options.group" property must be of type string. ' + + "Received type number (123)", + }); + + throws( + () => + generateKeyPairSync("ec", { namedCurve: "foo", paramEncoding: "foo" }), + { + message: + "The property 'options.paramEncoding' must be one of: " + + "'named', 'explicit'. Received 'foo'", + }, + ); + + throws(() => generateKeyPairSync("ed25519", { publicKeyEncoding: "foo" }), { + message: + 'The "options.publicKeyEncoding" property must be of type ' + + "object. Received type string ('foo')", + }); + + throws(() => generateKeyPairSync("x25519", { privateKeyEncoding: "foo" }), { + message: + 'The "options.privateKeyEncoding" property must be of type ' + + "object. Received type string ('foo')", + }); + + // ==== + async function wrapped(...args) { + const { promise, resolve, reject } = Promise.withResolvers(); + generateKeyPair(...args, (err) => { + if (err) { + reject(err); + return; + } + resolve(); + }); + await promise; + } + + await rejects(wrapped("invalid type", {}), { + message: + "The argument 'type' must be one of: 'rsa', " + + "'ec', 'ed25519', 'x25519', 'dh'. Received " + + "'invalid type'", + }); + + await rejects(wrapped("rsa", { modulusLength: -1 }), { + message: + 'The value of "options.modulusLength" is out of range. ' + + "It must be >= 0 && < 4294967296. Received -1", + }); + + await rejects(wrapped("rsa", { modulusLength: "foo" }), { + message: + 'The "options.modulusLength" property must be of type number. ' + + "Received type string ('foo')", + }); + + await rejects( + wrapped("rsa", { modulusLength: 512, publicExponent: "foo" }), + { + message: + 'The "options.publicExponent" property must be of type number. ' + + "Received type string ('foo')", + }, + ); + + // TODO(later): BoringSSL currently does not support rsa-pss key generation + // in the same way Node.js does. Uncomment these later when it does. + // await rejects( + // wrapped('rsa-pss', { modulusLength: 512, hashAlgorithm: false }), + // { + // message: + // 'The "options.hashAlgorithm" property must be of type string. ' + + // 'Received type boolean (false)', + // } + // ); + + // await rejects( + // wrapped('rsa-pss', { modulusLength: 512, mgf1HashAlgorithm: false }), + // { + // message: + // 'The "options.mgf1HashAlgorithm" property must be of type ' + + // 'string. Received type boolean (false)', + // } + // ); + + // await rejects( + // wrapped('rsa-pss', { modulusLength: 512, saltLength: 'foo' }), + // { + // message: + // 'The "options.saltLength" property must be of type number. ' + + // "Received type string ('foo')", + // } + // ); + + // TODO(later): BoringSSL currently does not support dsa key generation + // in the same way Node.js does. Uncomment this later when it does. + // await rejects( + // wrapped('dsa', { modulusLength: 512, divisorLength: 'foo' }), + // { + // message: + // 'The "options.divisorLength" property must be of type number. ' + + // "Received type string ('foo')", + // } + // ); + + await rejects(wrapped("dh", { prime: "foo" }), { + message: + 'The "options.prime" property must be an instance of Buffer, ' + + "TypedArray, or ArrayBuffer. Received type string ('foo')", + }); + + await rejects(wrapped("dh", { primeLength: "foo" }), { + message: + 'The "options.primeLength" property must be of type number. ' + + "Received type string ('foo')", + }); + + await rejects(wrapped("dh", { primeLength: -1 }), { + message: + 'The value of "options.primeLength" is out of range. It ' + + "must be >= 0 && <= 2147483647. Received -1", + }); + + await rejects(wrapped("dh", { primeLength: 10, generator: -1 }), { + message: + 'The value of "options.generator" is out of range. It must ' + + "be >= 0 && <= 2147483647. Received -1", + }); + + await rejects(wrapped("dh", { groupName: 123 }), { + message: + 'The "options.group" property must be of type string. ' + + "Received type number (123)", + }); + + await rejects(wrapped("ec", { namedCurve: "foo", paramEncoding: "foo" }), { + message: + "The property 'options.paramEncoding' must be one of: " + + "'named', 'explicit'. Received 'foo'", + }); + + await rejects(wrapped("ed25519", { publicKeyEncoding: "foo" }), { + message: + 'The "options.publicKeyEncoding" property must be of type ' + + "object. Received type string ('foo')", + }); + + await rejects(wrapped("x25519", { privateKeyEncoding: "foo" }), { + message: + 'The "options.privateKeyEncoding" property must be of type ' + + "object. Received type string ('foo')", + }); + }, +}; + +export const generate_rsa_key_pair_modulus_cap = { + test() { + throws(() => generateKeyPairSync("rsa", { modulusLength: 16385 }), { + name: "RangeError", + }); + const { publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048 }); + strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2048); + }, +}; + +export const generate_dh_key_pair_prime_size_cap = { + test() { + // 1025 bytes > kMaxPrimeBits / 8 (1024 bytes) + throws(() => generateKeyPairSync("dh", { prime: Buffer.alloc(1025) }), { + name: "RangeError", + }); + }, +}; + +export const generate_rsa_key_pair = { + test() { + const { publicKey, privateKey } = generateKeyPairSync("rsa", { + modulusLength: 2048, + }); + strictEqual(publicKey.type, "public"); + strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2048); + strictEqual(publicKey.asymmetricKeyDetails.publicExponent, 65537n); + + strictEqual(privateKey.type, "private"); + strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2048); + strictEqual(privateKey.asymmetricKeyDetails.publicExponent, 65537n); + }, +}; + +export const generate_rsa_key_pair_enc = { + test() { + const { publicKey, privateKey } = generateKeyPairSync("rsa", { + modulusLength: 2048, + publicKeyEncoding: { + format: "der", + type: "pkcs1", + }, + privateKeyEncoding: { + format: "pem", + type: "pkcs8", + }, + }); + + const pubImported = createPublicKey({ + key: publicKey, + format: "der", + type: "pkcs1", + }); + strictEqual(pubImported.type, "public"); + strictEqual(pubImported.asymmetricKeyDetails.modulusLength, 2048); + strictEqual(pubImported.asymmetricKeyDetails.publicExponent, 65537n); + + const imported = createPrivateKey(privateKey); + strictEqual(imported.type, "private"); + strictEqual(imported.asymmetricKeyDetails.modulusLength, 2048); + strictEqual(imported.asymmetricKeyDetails.publicExponent, 65537n); + }, +}; + +export const generate_ec_key_pair = { + test() { + const { privateKey, publicKey } = generateKeyPairSync("ec", { + namedCurve: "P-256", + }); + strictEqual(publicKey.type, "public"); + strictEqual(publicKey.asymmetricKeyDetails.namedCurve, "prime256v1"); + strictEqual(privateKey.type, "private"); + strictEqual(privateKey.asymmetricKeyDetails.namedCurve, "prime256v1"); + }, +}; + +export const generate_ed25519_key_pair = { + test() { + const { privateKey, publicKey } = generateKeyPairSync("ed25519", {}); + strictEqual(publicKey.type, "public"); + strictEqual(privateKey.type, "private"); + strictEqual(publicKey.asymmetricKeyType, "ed25519"); + strictEqual(privateKey.asymmetricKeyType, "ed25519"); + }, +}; + +export const generate_x25519_key_pair = { + test() { + const { privateKey, publicKey } = generateKeyPairSync("x25519", {}); + strictEqual(publicKey.type, "public"); + strictEqual(privateKey.type, "private"); + strictEqual(publicKey.asymmetricKeyType, "x25519"); + strictEqual(privateKey.asymmetricKeyType, "x25519"); + }, +}; + +// TODO(later): BoringSSL with fips does not support generating DH keys +// this way. Uncomment this later when it does. +// export const generate_dh_key_pair = { +// test() { +// const { privateKey, publicKey } = generateKeyPairSync('dh', { +// group: 'modp14', +// }); +// strictEqual(publicKey.type, 'public'); +// strictEqual(privateKey.type, 'private'); +// strictEqual(publicKey.asymmetricKeyType, 'dh'); +// strictEqual(privateKey.asymmetricKeyType, 'dh'); + +// const res = diffieHellman({ privateKey, publicKey }); +// ok(res instanceof Buffer); +// strictEqual(res.byteLength, 256); +// }, +// }; + +// TODO(later): BoringSSL with fips does not support generating DH keys +// this way. Uncomment this later when it does. +// export const generate_dh_from_fixed_prime = { +// test() { +// const prime = generatePrimeSync(1024); + +// const { privateKey: privateKey1, publicKey: publicKey1 } = +// generateKeyPairSync('dh', { +// prime, +// }); +// strictEqual(publicKey1.type, 'public'); +// strictEqual(privateKey1.type, 'private'); +// strictEqual(publicKey1.asymmetricKeyType, 'dh'); +// strictEqual(privateKey1.asymmetricKeyType, 'dh'); + +// const { privateKey: privateKey2, publicKey: publicKey2 } = +// generateKeyPairSync('dh', { +// prime, +// }); +// strictEqual(publicKey2.type, 'public'); +// strictEqual(privateKey2.type, 'private'); +// strictEqual(publicKey2.asymmetricKeyType, 'dh'); +// strictEqual(privateKey2.asymmetricKeyType, 'dh'); + +// ok(!publicKey1.equals(publicKey2)); +// ok(!privateKey1.equals(privateKey2)); + +// // Once we generate the keys, let's make sure they are usable. + +// const res1 = diffieHellman({ +// privateKey: privateKey2, +// publicKey: publicKey1, +// }); +// ok(res1 instanceof Buffer); +// strictEqual(res1.byteLength, 128); + +// const res2 = diffieHellman({ +// privateKey: privateKey2, +// publicKey: publicKey1, +// }); +// ok(res2 instanceof Buffer); +// strictEqual(res2.byteLength, 128); + +// deepStrictEqual(res1, res2); +// // It's actual data and not just zeroes right? +// notDeepStrictEqual(res1, Buffer.alloc(128, 0)); + +// // Keys generated from different prime groups aren't compatible and should throw. +// const prime2 = generatePrimeSync(1024); +// const { privateKey: privateKey3, publicKey: publicKey3 } = +// generateKeyPairSync('dh', { +// prime: prime2, +// }); +// strictEqual(publicKey3.type, 'public'); +// strictEqual(privateKey3.type, 'private'); +// strictEqual(publicKey3.asymmetricKeyType, 'dh'); +// strictEqual(privateKey3.asymmetricKeyType, 'dh'); + +// throws( +// () => diffieHellman({ publicKey: publicKey1, privateKey: privateKey3 }), +// { +// message: 'Failed to derive shared diffie-hellman secret', +// } +// ); +// }, +// }; + +export const generate_dh_key_pair_by_length = { + test() { + throws( + () => + generateKeyPairSync("dh", { + primeLength: 1048, + }), + { + message: + "Generating DH keys from a prime length is not yet implemented", + }, + ); + }, +}; + +export const generate_ed_keypair_promisified = { + async test() { + const promisifiedGenKeyPair = promisify(generateKeyPair); + const { publicKey, privateKey } = await promisifiedGenKeyPair( + "ed25519", + {}, + ); + strictEqual(publicKey.asymmetricKeyType, "ed25519"); + strictEqual(privateKey.asymmetricKeyType, "ed25519"); + }, +}; + +// Regression test: privateKey.export() with cipher and passphrase must +// produce an encrypted PEM (BEGIN ENCRYPTED PRIVATE KEY), not plaintext. +export const export_encrypted_private_key = { + test() { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 }); + + const plainPem = privateKey.export({ format: "pem", type: "pkcs8" }); + const encryptedPem = privateKey.export({ + format: "pem", + type: "pkcs8", + cipher: "aes-256-cbc", + passphrase: "test-passphrase", + }); + + // Encrypted PEM must differ from unencrypted PEM. + ok(plainPem !== encryptedPem); + + // Encrypted PEM must have the ENCRYPTED header. + ok( + encryptedPem.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----"), + "Expected encrypted PEM header", + ); + + // Re-import the encrypted PEM using the passphrase. + const reimported = createPrivateKey({ + key: encryptedPem, + format: "pem", + passphrase: "test-passphrase", + }); + + // The re-imported key must produce the same unencrypted export. + strictEqual(reimported.export({ format: "pem", type: "pkcs8" }), plainPem); + }, +}; + +// Verify encrypted export also works for EC keys. +export const export_encrypted_ec_private_key = { + test() { + const { privateKey } = generateKeyPairSync("ec", { + namedCurve: "P-256", + }); + + const encryptedPem = privateKey.export({ + format: "pem", + type: "pkcs8", + cipher: "aes-128-cbc", + passphrase: "ec-passphrase", + }); + + ok( + encryptedPem.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----"), + "Expected encrypted PEM header for EC key", + ); + + const reimported = createPrivateKey({ + key: encryptedPem, + format: "pem", + passphrase: "ec-passphrase", + }); + + strictEqual( + reimported.export({ format: "pem", type: "pkcs8" }), + privateKey.export({ format: "pem", type: "pkcs8" }), + ); + }, +}; + +// Regression test for AUTOVULN-CLOUDFLARE-WORKERD-30: +// createSecretKey() must correctly copy DataView key material. +// Previously, Buffer.from(dataView) produced an empty Buffer because +// DataView has no .length property, causing the key material to be lost. +export const regression_create_secret_key_dataview = { + test() { + // Create a 16-byte ArrayBuffer filled with 0x41 ('A') + const ab = new ArrayBuffer(16); + new Uint8Array(ab).fill(0x41); + + // Create a secret key from a DataView over the full buffer + const dvFull = new DataView(ab); + const keyFull = createSecretKey(dvFull); + const exported = keyFull.export(); + + // The exported key must contain exactly the 16 bytes, not be empty + strictEqual(exported.length, 16, "DataView key material must not be empty"); + strictEqual( + exported.toString(), + "A".repeat(16), + "DataView key material must match the original bytes", + ); + strictEqual(keyFull.symmetricKeySize, 16); + + // Also verify a DataView over a sub-range of the buffer works correctly + const abLarge = new ArrayBuffer(32); + const u8 = new Uint8Array(abLarge); + u8.fill(0x42); // fill with 'B' + u8.fill(0x43, 8, 16); // bytes 8..15 = 'C' + + const dvSlice = new DataView(abLarge, 8, 8); + const keySlice = createSecretKey(dvSlice); + const exportedSlice = keySlice.export(); + + strictEqual( + exportedSlice.length, + 8, + "DataView sub-range key must have correct length", + ); + strictEqual( + exportedSlice.toString(), + "C".repeat(8), + "DataView sub-range key must contain the correct bytes", + ); + strictEqual(keySlice.symmetricKeySize, 8); + + // Verify the DataView-created key matches a Uint8Array-created key + // over the same bytes + const keyFromUint8 = createSecretKey(Buffer.from("A".repeat(16))); + ok( + keyFull.equals(keyFromUint8), + "DataView key must equal Buffer key with same bytes", + ); + }, +}; diff --git a/src/compat/crypto/upstream-scrypt-limits.fixture.mjs b/src/compat/crypto/upstream-scrypt-limits.fixture.mjs new file mode 100644 index 00000000..ec81656a --- /dev/null +++ b/src/compat/crypto/upstream-scrypt-limits.fixture.mjs @@ -0,0 +1,39 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { strictEqual, throws } from "node:assert"; +import { scrypt, scryptSync } from "./index.js"; + +export const scrypt_cost_limit_sync = { + test() { + // N=1024, r=1, p=32768 → cost=33,554,432, exceeds 2^20 + throws(() => scryptSync("p", "s", 64, { N: 1024, r: 1, p: 32768 }), { + name: "RangeError", + }); + + // N=2, r=1, p=1048577 → cost just over 2^20 + throws(() => scryptSync("p", "s", 64, { N: 2, r: 1, p: 1048577 }), { + name: "RangeError", + }); + + // N=1024, r=1, p=1 → cost=1024, within limit + const result = scryptSync("p", "s", 64, { N: 1024, r: 1, p: 1 }); + strictEqual(result.length, 64); + }, +}; + +export const scrypt_cost_limit_async = { + async test() { + const { promise, resolve, reject } = Promise.withResolvers(); + scrypt("p", "s", 64, { N: 1024, r: 1, p: 32768 }, (err) => { + if (err) { + resolve(err); + } else { + reject(new Error("Expected error")); + } + }); + const err = await promise; + strictEqual(err.constructor.name, "RangeError"); + }, +}; diff --git a/src/compat/crypto/upstream-scrypt.fixture.mjs b/src/compat/crypto/upstream-scrypt.fixture.mjs new file mode 100644 index 00000000..a3db32ab --- /dev/null +++ b/src/compat/crypto/upstream-scrypt.fixture.mjs @@ -0,0 +1,257 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +import { strictEqual, throws, rejects } from "node:assert"; + +import { scrypt, scryptSync } from "./index.js"; + +import { mock } from "node:test"; + +const good = [ + // Zero-length key is legal, functions as a parameter validation check. + { + pass: "", + salt: "", + keylen: 0, + N: 16, + p: 1, + r: 1, + expected: "", + }, + // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that + // should pass. Note that the test vector with N=1048576 is omitted + // because it takes too long to complete and uses over 1 GiB of memory. + { + pass: "", + salt: "", + keylen: 64, + N: 16, + p: 1, + r: 1, + expected: + "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + + "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", + }, + { + pass: "password", + salt: "NaCl", + keylen: 64, + N: 1024, + p: 16, + r: 8, + expected: + "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + + "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", + }, + { + pass: "pleaseletmein", + salt: "SodiumChloride", + keylen: 64, + N: 16384, + p: 1, + r: 8, + expected: + "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + + "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", + }, + { + pass: "", + salt: "", + keylen: 64, + cost: 16, + parallelization: 1, + blockSize: 1, + expected: + "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442" + + "fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", + }, + { + pass: "password", + salt: "NaCl", + keylen: 64, + cost: 1024, + parallelization: 16, + blockSize: 8, + expected: + "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162" + + "2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", + }, + { + pass: "pleaseletmein", + salt: "SodiumChloride", + keylen: 64, + cost: 16384, + parallelization: 1, + blockSize: 8, + expected: + "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2" + + "d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", + }, +]; + +// Test vectors that should fail. +const bad = [ + { N: 1, p: 1, r: 1 }, // N < 2 + { N: 3, p: 1, r: 1 }, // Not power of 2. +]; + +// Test vectors where 128*N*r exceeds maxmem. +const toobig = [ + { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) + { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r + { N: 2 ** 20, p: 1, r: 8 }, + { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, +]; + +const badargs = [ + { + args: [], + expected: { code: "ERR_INVALID_ARG_TYPE", message: /"password"/ }, + }, + { + args: [null], + expected: { code: "ERR_INVALID_ARG_TYPE", message: /"password"/ }, + }, + { + args: [""], + expected: { code: "ERR_INVALID_ARG_TYPE", message: /"salt"/ }, + }, + { + args: ["", null], + expected: { code: "ERR_INVALID_ARG_TYPE", message: /"salt"/ }, + }, + { + args: ["", ""], + expected: { code: "ERR_INVALID_ARG_TYPE", message: /"keylen"/ }, + }, + { + args: ["", "", null], + expected: { code: "ERR_INVALID_ARG_TYPE", message: /"keylen"/ }, + }, + { + args: ["", "", 0.42], + expected: { code: "ERR_OUT_OF_RANGE", message: /"keylen"/ }, + }, + { + args: ["", "", -42], + expected: { code: "ERR_OUT_OF_RANGE", message: /"keylen"/ }, + }, + { + args: ["", "", 2 ** 31], + expected: { code: "ERR_OUT_OF_RANGE", message: /"keylen"/ }, + }, + { + args: ["", "", 2147485780], + expected: { code: "ERR_OUT_OF_RANGE", message: /"keylen"/ }, + }, + { + args: ["", "", 2 ** 32], + expected: { code: "ERR_OUT_OF_RANGE", message: /"keylen"/ }, + }, +]; + +export const goodTests = { + async test() { + for (const options of good) { + const { pass, salt, keylen, expected } = options; + const actual = scryptSync(pass, salt, keylen, options); + strictEqual(actual.toString("hex"), expected); + const { promise, resolve } = Promise.withResolvers(); + const fn = mock.fn((err, actual) => { + strictEqual(actual.toString("hex"), expected); + resolve(); + }); + scrypt(pass, salt, keylen, options, fn); + await promise; + strictEqual(fn.mock.calls.length, 1); + } + }, +}; + +export const badTests = { + async test() { + for (const options of bad) { + const { promise, reject } = Promise.withResolvers(); + const fn = mock.fn((err, actual) => { + if (err) reject(err); + }); + scrypt("pass", "salt", 1, options, fn); + await rejects(promise); + throws(() => scryptSync("pass", "salt", 1, options)); + } + throws(() => scryptSync("pass", "salt", 1, { N: 1, cost: 1 })); + throws(() => scryptSync("pass", "salt", 1, { p: 1, parallelization: 1 })); + throws(() => scryptSync("pass", "salt", 1, { r: 1, blockSize: 1 })); + }, +}; + +export const tooBigTests = { + async test() { + for (const options of toobig) { + const { promise, reject } = Promise.withResolvers(); + const fn = mock.fn((err, actual) => { + if (err) reject(err); + }); + scrypt("pass", "salt", 1, options, fn); + await rejects(promise); + strictEqual(fn.mock.calls.length, 1); + + throws(() => scryptSync("pass", "salt", 1, options)); + } + }, +}; + +export const defaultsTest = { + async test() { + const defaults = { N: 16384, p: 1, r: 8 }; + const expected = scryptSync("pass", "salt", 1, defaults); + const actual = scryptSync("pass", "salt", 1); + strictEqual(actual.toString("hex"), expected.toString("hex")); + const { promise, resolve } = Promise.withResolvers(); + const fn = mock.fn((err, actual) => { + strictEqual(actual.toString("hex"), expected.toString("hex")); + resolve(); + }); + scrypt("pass", "salt", 1, fn); + await promise; + strictEqual(fn.mock.calls.length, 1); + }, +}; + +export const badArgsTest = { + test() { + for (const { args, expected: _expected } of badargs) { + throws(() => scrypt(...args)); + throws(() => scryptSync(...args)); + } + + throws(() => scrypt("", "", 42, null)); + throws(() => scrypt("", "", 42, {}, null)); + throws(() => scrypt("", "", 42, {})); + throws(() => scrypt("", "", 42, {}, {})); + }, +}; diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index 06fb2492..3b1758f2 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -41,7 +41,8 @@ processing. Streaming output is not cumulatively capped by maxOutputLength. The pinned source revisions and artifact hashes accompany the codecs; rebuild with scripts/build-native-codecs.mjs or scripts/build-native-zlib.mjs and the pinned Emscripten SDK. Gzip/deflate and Brotli/Zstd streams use the pinned Workers stream lifecycle. The comparative cases include constructor validation, callback timing, reset, close, byte accounting, dictionaries and output limits; whole-module acceptance remains a separate gate. Scrypt uses a synchronous JavaScript backend, including for callback completion, -so large parameters can block the WebView. +so large parameters can block the WebView. Cost and maxmem validation follow the +pinned Workers limits, including its 1048576 cost ceiling. Hash and HMAC use the owned OpenSSL backend, including incremental input, Hash copying, stream finalization and repeated-digest behavior. `getHashes()` @@ -61,8 +62,14 @@ HMAC, HKDF and symmetric ciphers. KeyObject.from can retain a host CryptoKey, bu synchronous extraction of its opaque key material is unavailable. RSA, EC, Ed25519 and X25519 key generation and PEM/DER/JWK conversion use bundled OpenSSL 3.5.9 WebAssembly; RSA/EC streaming and one-shot signatures are compared with Workers and independent test signatures. This covers selected -algorithms and operations, not the whole crypto module. Additional key-generation -options and remaining error contracts still need work. Cipheriv/Decipheriv use the same pinned BoringSSL revision as Workers. Their +algorithms and operations, not the whole crypto module. Generation validates encoding +options before allocating keys, preserves RSA's 16384-bit limit and 128-bit size +rounding, and matches EC curve aliases and SEC1 parameter flags. DH custom-prime +generation and shared-secret calculation use the pinned BoringSSL backend, +including small primes and rejection of mismatched parameters. Workers' DH key +export rejection is preserved. The upstream key-generation and scrypt fixtures +run with `npx vitest run src/compat/crypto/kdf-limits.spec.ts`. +Remaining key import/export and other crypto error contracts still need work. Cipheriv/Decipheriv use the same pinned BoringSSL revision as Workers. Their frontend preserves Workers' `authLengthTag` option spelling, AEAD's one-update limit, early authentication-tag requirements, tag consumption and buffer copies. The listed classic and AEAD cipher names have shared algorithm/state contracts; diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index ff787dcc..021697a6 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,233 @@ export const eventCases = { + cryptoWorkLimits: async function ({ crypto }) { + const attempt = (fn) => { + try { + return fn(); + } catch (error) { + return { + name: error.name, + code: error.code ?? null, + message: error.message, + }; + } + }; + const options = [ + { N: 1024, r: 1, p: 32768 }, + { N: 2, r: 1, p: 1048577 }, + { N: 65536, r: 1, p: 1 }, + { N: 16, r: 1, p: 1, maxmem: 1 }, + { N: 16, r: 1, p: 1, maxmem: 4294967296 }, + { N: 16, r: 1, p: 1, maxmem: 4294967295 }, + { N: 16, r: 1, p: 1 }, + ]; + const result = { + scrypt: options.map((options) => + attempt(() => crypto.scryptSync("p", "s", 8, options).toString("hex")), + ), + rsa: attempt(() => + crypto.generateKeyPairSync("rsa", { modulusLength: 16385 }), + ), + dh: attempt(() => + crypto.generateKeyPairSync("dh", { prime: new Uint8Array(1025) }), + ), + }; + const order = []; + result.callback = await new Promise((resolve) => { + crypto.scrypt("p", "s", 8, options[0], (error) => { + order.push("callback"); + resolve({ + name: error.name, + code: error.code ?? null, + message: error.message, + }); + }); + order.push("returned"); + }); + result.order = order; + return result; + }, + cryptoKeygenContracts: async function ({ crypto, util }) { + const attempt = (fn) => { + try { + return fn(); + } catch (error) { + return { + name: error.name, + code: error.code ?? null, + message: error.message, + }; + } + }; + const errors = [ + ["dh", {}], + ["dh", { prime: "p" }], + ["dh", { primeLength: -1 }], + ["dh", { primeLength: 512 }], + ["dh", { generator: -1 }], + ["dh", { group: "modp14", primeLength: 512 }], + ["dh", { group: "modp14", prime: new Uint8Array([3]) }], + ["dh", { group: "modp14", generator: 2 }], + ["dh", { prime: new Uint8Array([3]), primeLength: 512 }], + ["dh", { group: "modp1" }], + ["dh", { group: "unknown" }], + ["ed25519", { publicKeyEncoding: "pem" }], + ["x25519", { privateKeyEncoding: "pem" }], + ["ed25519", { privateKeyEncoding: { format: "pem", type: "pkcs1" } }], + [ + "rsa", + { + modulusLength: 1024, + privateKeyEncoding: { + format: "der", + type: "pkcs1", + cipher: "aes-128-cbc", + passphrase: "pw", + }, + }, + ], + [ + "ec", + { + namedCurve: "prime256v1", + privateKeyEncoding: { + format: "pem", + type: "pkcs8", + cipher: "aes-128-cbc", + }, + }, + ], + ].map(([type, options]) => + attempt(() => { + crypto.generateKeyPairSync(type, options); + return "accepted"; + }), + ); + const rsaBoundaries = [0, 1, 16, 255, 256, 511].map((modulusLength) => + attempt(() => { + crypto.generateKeyPairSync("rsa", { modulusLength }); + return "accepted"; + }), + ); + const rsaRounded = [513, 520, 639, 640, 1023].map( + (modulusLength) => + crypto.generateKeyPairSync("rsa", { modulusLength }).privateKey + .asymmetricKeyDetails.modulusLength, + ); + const rsaExponents = [0, 1, 2, 4].map((publicExponent) => + attempt(() => { + crypto.generateKeyPairSync("rsa", { + modulusLength: 512, + publicExponent, + }); + return "accepted"; + }), + ); + const smallDh = [0, 1, 2, 3, 5, 23, 37, 255].map((p) => + attempt(() => { + const { publicKey, privateKey } = crypto.generateKeyPairSync("dh", { + prime: new Uint8Array([p]), + generator: 2, + }); + return [ + publicKey.asymmetricKeyType, + privateKey.asymmetricKeyType, + crypto.createPublicKey(privateKey).type, + attempt( + () => publicKey.export({ type: "spki", format: "der" }).length > 0, + ), + attempt( + () => + privateKey.export({ type: "pkcs8", format: "der" }).length > 0, + ), + ]; + }), + ); + const curves = [ + "P-224", + "P-256", + "P-384", + "P-521", + "prime256v1", + "prime256v1\0suffix", + "secp256r1", + "secp256k1", + "PRIME256V1", + ].map((namedCurve) => [ + namedCurve, + attempt( + () => + crypto.generateKeyPairSync("ec", { namedCurve }).publicKey + .asymmetricKeyDetails, + ), + ]); + const ecEncoding = ["named", "explicit"].map((paramEncoding) => { + const { privateKey } = crypto.generateKeyPairSync("ec", { + namedCurve: "P-256", + paramEncoding, + }); + const data = privateKey.export({ type: "sec1", format: "der" }); + return { + paramEncoding, + length: data.length, + curveParameter: data + .toString("hex") + .includes("a00a06082a8648ce3d030107"), + }; + }); + const alice = crypto.generateKeyPairSync("dh", { group: "MODP14" }); + const prime = crypto.getDiffieHellman("modp14").getPrime(), + padded = new Uint8Array(prime.length + 4); + padded.set(prime, 2); + const bob = crypto.generateKeyPairSync("dh", { + prime: new DataView(padded.buffer, 2, prime.length), + generator: 2, + }); + const a = crypto.diffieHellman({ + privateKey: alice.privateKey, + publicKey: bob.publicKey, + }), + b = crypto.diffieHellman({ + privateKey: bob.privateKey, + publicKey: alice.publicKey, + }); + const mismatchedDh = [ + crypto.generateKeyPairSync("dh", { group: "modp5" }), + crypto.generateKeyPairSync("dh", { prime, generator: 5 }), + ].map((peer) => + attempt( + () => + crypto.diffieHellman({ + publicKey: alice.publicKey, + privateKey: peer.privateKey, + }).length, + ), + ); + const clonedDh = crypto + .diffieHellman({ + publicKey: crypto.createPublicKey(bob.privateKey), + privateKey: alice.privateKey, + }) + .equals(a); + const pair = await util.promisify(crypto.generateKeyPair)("ed25519", {}); + return { + errors, + curves, + ecEncoding, + rsaBoundaries, + rsaExponents, + rsaRounded, + smallDh, + mismatchedDh, + clonedDh, + dh: { + type: alice.privateKey.asymmetricKeyType, + length: a.length, + equal: a.toString("hex") === b.toString("hex"), + }, + promisify: [pair.publicKey.type, pair.privateKey.type], + }; + }, + cryptoCipherAlgorithms: async function ({ crypto }) { const results = []; for (const name of [ diff --git a/vitest.config.ts b/vitest.config.ts index 02af7a83..89608599 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -73,6 +73,7 @@ export default defineConfig({ "src/browser/fetch/scope.spec.ts", "src/compat/net/sockets.spec.ts", "src/compat/tls/upgrade.spec.ts", + "src/compat/crypto/kdf-limits.spec.ts", "src/compat/crypto/cipher.spec.ts", "src/compat/crypto/hash.spec.ts", "src/compat/crypto/hash-types.spec.ts", From 2b14cc4f0262772f1a673032c7a4b0a54217e4de Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 02:45:34 +0900 Subject: [PATCH 06/30] feat: align random byte and fill APIs with Workers --- src/compat/crypto/index.ts | 27 +- src/compat/crypto/random.spec.ts | 48 ++ src/compat/crypto/random.ts | 162 +++++- src/compat/crypto/upstream-random.fixture.mjs | 540 ++++++++++++++++++ src/compat/expansion/README.md | 10 + src/compat/expansion/index.spec.mjs | 117 ++++ vitest.config.ts | 1 + 7 files changed, 889 insertions(+), 16 deletions(-) create mode 100644 src/compat/crypto/random.spec.ts create mode 100644 src/compat/crypto/upstream-random.fixture.mjs diff --git a/src/compat/crypto/index.ts b/src/compat/crypto/index.ts index f521b118..7d995842 100644 --- a/src/compat/crypto/index.ts +++ b/src/compat/crypto/index.ts @@ -37,24 +37,27 @@ export * from "./metadata.js"; // below are separately verified against the pinned Workers profile. // @ts-expect-error crypto-browserify has no declarations import original from "crypto-browserify/index.js"; +const originalSupported = { ...original }; +for (const name of ["prng", "rng", "listCiphers", "createCredentials"]) + delete originalSupported[name]; import { hkdf, hkdfSync } from "./hkdf.js"; -import { randomInt, randomUUID } from "./random.js"; +import { + randomInt, + randomUUID, + randomBytes, + randomFill, + randomFillSync, +} from "./random.js"; +export { randomBytes, randomFill, randomFillSync }; +export const pseudoRandomBytes = randomBytes; import { scrypt, scryptSync } from "./scrypt.js"; export { hkdf, hkdfSync, randomInt, randomUUID, scrypt, scryptSync }; export const { - prng, - pseudoRandomBytes, - rng, - randomBytes, - listCiphers, DiffieHellmanGroup, createDiffieHellmanGroup, getDiffieHellman, createDiffieHellman, DiffieHellman, - randomFill, - randomFillSync, - createCredentials, constants, } = original; import { @@ -78,7 +81,7 @@ export const CryptoKey = globalThis.CryptoKey; export default { X509Certificate, Certificate, - ...original, + ...originalSupported, pbkdf2, pbkdf2Sync, Hash, @@ -114,6 +117,10 @@ export default { CryptoKey, randomInt, randomUUID, + pseudoRandomBytes, + randomBytes, + randomFill, + randomFillSync, hash, scrypt, scryptSync, diff --git a/src/compat/crypto/random.spec.ts b/src/compat/crypto/random.spec.ts new file mode 100644 index 00000000..4d123044 --- /dev/null +++ b/src/compat/crypto/random.spec.ts @@ -0,0 +1,48 @@ +import { test, expect, expectTypeOf } from "vitest"; +// @ts-expect-error Pinned upstream JavaScript fixture. +import * as fixtures from "./upstream-random.fixture.mjs"; +for (const [name, fixture] of Object.entries(fixtures)) + test("pinned Workers random: " + name, () => + (fixture as { test: () => unknown }).test(), + ); + +import { Buffer } from "node:buffer"; +import crypto, { + randomBytes, + randomFill, + randomFillSync, + pseudoRandomBytes, +} from "./index.js"; +test("random API declarations retain result and input brands", () => { + expectTypeOf(randomBytes(0)).toEqualTypeOf(); + expect(randomBytes(0).toString("hex")).toBe(""); + expectTypeOf(randomFillSync(new Int32Array(0))).toEqualTypeOf< + Int32Array + >(); + expectTypeOf( + randomFillSync(new DataView(new ArrayBuffer(0))), + ).toEqualTypeOf(); + expectTypeOf(randomFillSync(new ArrayBuffer(0))).toEqualTypeOf(); + expectTypeOf( + randomFillSync(new SharedArrayBuffer(0)), + ).toEqualTypeOf(); + randomFill(new ArrayBuffer(0), (_error, buffer) => { + expectTypeOf(buffer).toEqualTypeOf(); + }); + randomFill(new Uint16Array(0), 0, 0, (_error, buffer) => { + expectTypeOf(buffer).toEqualTypeOf | undefined>(); + }); + expect(pseudoRandomBytes).toBe(randomBytes); + expect(crypto.pseudoRandomBytes).toBe(randomBytes); +}); + +// Declaration-only invalid calls must be rejected without executing them. +function invalidRandomFillCalls() { + // @ts-expect-error A callback is required. + randomFill(new Uint8Array(0)); + // @ts-expect-error A callback is required after offset. + randomFill(new Uint8Array(0), 0); + // @ts-expect-error A callback is required after size. + randomFill(new Uint8Array(0), 0, 0); +} +void invalidRandomFillCalls; diff --git a/src/compat/crypto/random.ts b/src/compat/crypto/random.ts index 4b8cfa1f..adaef61e 100644 --- a/src/compat/crypto/random.ts +++ b/src/compat/crypto/random.ts @@ -26,13 +26,163 @@ // Adapted from workerd@5358e3d337336eaf696a48bc1563d808b10feba5. import { Buffer } from "node:buffer"; import type { RandomUUIDOptions } from "node:crypto"; -import { validateInteger } from "./validation.js"; -import { validateBoolean, validateObject } from "../net/validators.js"; -import { ERR_OUT_OF_RANGE } from "../net/errors.js"; -// Cache size stays below the Web Crypto per-call limit. -function randomFillSync(buffer: Uint8Array) { - return globalThis.crypto.getRandomValues(buffer as Uint8Array); +import { + validateInteger, + isAnyBuffer as isAnyArrayBuffer, +} from "./validation.js"; +import { + validateBoolean, + validateObject, + validateFunction, +} from "../net/validators.js"; +const isArrayBufferView = ArrayBuffer.isView; +const dataViewBuffer = Object.getOwnPropertyDescriptor( + DataView.prototype, + "buffer", +)!.get!; +function isDataView(value: unknown): value is DataView { + try { + dataViewBuffer.call(value); + return true; + } catch { + return false; + } +} +import { kMaxLength } from "../buffer/internal-buffer.js"; +import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE } from "../net/errors.js"; +export type RandomBytesCallback = (err: Error | null, buffer: Buffer) => void; +export function randomBytes(size: number, callback: RandomBytesCallback): void; +export function randomBytes(size: number): Buffer; +export function randomBytes( + size: number, + callback?: RandomBytesCallback, +): Buffer | undefined { + validateInteger(size, "size", 0, kMaxLength); + const buf = Buffer.alloc(size); + if (callback !== undefined) { + randomFill(buf, callback as RandomFillCallback); + return undefined; + } else { + randomFillSync(buf); + return buf; + } +} + +export type RandomFillBuffer = + | NodeJS.ArrayBufferView + | ArrayBuffer + | SharedArrayBuffer; +export type RandomFillResult = T extends + | ArrayBuffer + | SharedArrayBuffer + ? Buffer + : T extends DataView + ? Uint8Array + : T; +export function randomFillSync( + buffer: T, + offset?: number, + size?: number, +): RandomFillResult; +export function randomFillSync( + buffer: RandomFillBuffer, + offset?: number, + size?: number, +): NodeJS.TypedArray { + if (!isAnyArrayBuffer(buffer) && !isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["TypedArray", "DataView", "ArrayBuffer", "SharedArrayBuffer"], + buffer, + ); + } + // Use byteLength, not length — DataView has no .length property and + // TypedArray .length is element count, not bytes. + const maxLength = (buffer as Uint8Array).byteLength; + if (offset !== undefined) { + validateInteger(offset, "offset", 0, kMaxLength); + } else offset = 0; + if (size !== undefined) { + validateInteger(size, "size", 0, maxLength - offset); + } else size = maxLength - offset; + if (isAnyArrayBuffer(buffer)) { + buffer = Buffer.from(buffer as unknown as ArrayBuffer); + } else if (isDataView(buffer)) { + buffer = new Uint8Array( + (buffer as DataView).buffer, + (buffer as DataView).byteOffset, + buffer.byteLength, + ); + } + buffer = (buffer as Buffer).subarray(offset, offset + size); + return crypto.getRandomValues(buffer as Uint8Array); } + +export type RandomFillCallback = + (err: Error | null, buf?: T) => void; +export function randomFill( + buffer: T, + callback: RandomFillCallback, +): void; +export function randomFill( + buffer: T, + offset: number, + callback: RandomFillCallback, +): void; +export function randomFill( + buffer: T, + offset: number, + size: number, + callback: RandomFillCallback, +): void; +export function randomFill( + buffer: T, + offsetOrCallback?: number | RandomFillCallback, + sizeOrCallback?: number | RandomFillCallback, + callback?: RandomFillCallback, +): void { + if (!isAnyArrayBuffer(buffer) && !isArrayBufferView(buffer)) { + throw new ERR_INVALID_ARG_TYPE( + "buffer", + ["TypedArray", "DataView", "ArrayBuffer", "SharedArrayBuffer"], + buffer, + ); + } + + let offset = 0; + let size = 0; + // Use byteLength, not length — DataView has no .length property and + // TypedArray .length is element count, not bytes. + const maxLength = (buffer as Uint8Array).byteLength; + if (typeof callback === "function") { + validateInteger(offsetOrCallback, "offset", 0, maxLength); + offset = offsetOrCallback; + + validateInteger(sizeOrCallback, "size", 0, maxLength - offset); + size = sizeOrCallback; + } else if (typeof sizeOrCallback === "function") { + validateInteger(offsetOrCallback, "offset", 0, maxLength); + offset = offsetOrCallback; + size = maxLength - offset; + callback = sizeOrCallback; + } else if (typeof offsetOrCallback === "function") { + offset = 0; + size = maxLength; + callback = offsetOrCallback; + } + validateFunction(callback, "callback"); + + // We're currently not actually implementing the fill itself asynchronously, + // so we defer to randomFillSync here, but we invoke the callback asynchronously. + new Promise((res) => { + randomFillSync(buffer, offset, size); + res(); + }).then( + (): unknown => callback(null, buffer), + (err: unknown): unknown => callback(err as Error), + ); +} + const RAND_MAX = 0xffff_ffff_ffff; // Cache random data to use in randomInt. The cache size must be evenly // divisible by 6 because each attempt to obtain a random int uses 6 bytes. diff --git a/src/compat/crypto/upstream-random.fixture.mjs b/src/compat/crypto/upstream-random.fixture.mjs new file mode 100644 index 00000000..2ac92a42 --- /dev/null +++ b/src/compat/crypto/upstream-random.fixture.mjs @@ -0,0 +1,540 @@ +// Copyright (c) 2023 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +import { ok, rejects, strictEqual, throws } from "node:assert"; + +import { + generatePrime, + generatePrimeSync, + checkPrime, + checkPrimeSync, + timingSafeEqual, +} from "./index.js"; + +import { Buffer } from "node:buffer"; + +export const test = { + async test(ctrl, env, ctx) { + [1, "hello", {}, []].forEach((i) => { + throws(() => checkPrimeSync(i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + // prettier-ignore + for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) { + throws(() => checkPrimeSync(2n, { checks }), { + code: 'ERR_OUT_OF_RANGE', + message: /<= 2147483647/ + }); + } + + ok( + !checkPrimeSync(Buffer.from([0x1]), { + fast: true, + trialDivision: true, + checks: 10, + }), + ); + + ok(!checkPrimeSync(Buffer.from([0x1]))); + ok(checkPrimeSync(Buffer.from([0x2]))); + ok(checkPrimeSync(Buffer.from([0x3]))); + ok(!checkPrimeSync(Buffer.from([0x4]))); + + //////////////// + + ["hello", false, 123].forEach((i) => { + throws(() => generatePrimeSync(80, i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + for (const checks of ["hello", {}, []]) { + throws(() => checkPrimeSync(2n, { checks }), { + code: "ERR_INVALID_ARG_TYPE", + message: /checks/, + }); + } + + // ////////////////// + + ["hello", false, {}, []].forEach((i) => { + throws(() => generatePrime(i), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrimeSync(i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + ["hello", false, 123].forEach((i) => { + throws(() => generatePrime(80, i, {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrimeSync(80, i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + ["hello", false, 123].forEach((i) => { + throws(() => generatePrime(80, {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + [-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => { + throws(() => generatePrime(-1), { + code: "ERR_OUT_OF_RANGE", + message: />= 1 && <= 2147483647/, + }); + throws(() => generatePrimeSync(size), { + code: "ERR_OUT_OF_RANGE", + message: />= 1 && <= 2147483647/, + }); + }); + + // TODO: Fix and enable asynchronous tests + ["test", -1, {}, []].forEach((i) => { + throws(() => generatePrime(8, { safe: i }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrime(8, { rem: i }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrime(8, { add: i }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrimeSync(8, { safe: i }), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrimeSync(8, { rem: i }), { + code: "ERR_INVALID_ARG_TYPE", + }); + throws(() => generatePrimeSync(8, { add: i }), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + { + // Negative BigInts should not be converted to 0 silently. + throws(() => generatePrime(20, { add: -1n }, () => {}), { + code: "ERR_OUT_OF_RANGE", + }); + + throws(() => generatePrime(20, { rem: -1n }, () => {}), { + code: "ERR_OUT_OF_RANGE", + }); + throws(() => checkPrime(-1n, () => {}), { + code: "ERR_OUT_OF_RANGE", + }); + } + + { + const p = Promise.withResolvers(); + generatePrime(80, (err, prime) => { + ok(checkPrimeSync(prime)); + checkPrime(prime, (err, result) => { + ok(result); + p.resolve(); + }); + }); + await p.promise; + } + + ok(checkPrimeSync(generatePrimeSync(80))); + + { + const p = Promise.withResolvers(); + generatePrime(80, {}, (err, prime) => { + if (err) return p.reject(err); + ok(checkPrimeSync(prime)); + p.resolve(); + }); + await p.promise; + } + + ok(checkPrimeSync(generatePrimeSync(80, {}))); + + { + const p = Promise.withResolvers(); + generatePrime(32, { safe: true }, (err, prime) => { + if (err) return p.reject(err); + ok(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + const check = (val - 1) / 2; + buf.writeUInt32BE(check); + ok(checkPrimeSync(buf)); + p.resolve(); + }); + await p.promise; + } + + { + const prime = generatePrimeSync(32, { safe: true }); + ok(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + const check = (val - 1) / 2; + buf.writeUInt32BE(check); + ok(checkPrimeSync(buf)); + } + + const add = 12; + const rem = 11; + const add_buf = Buffer.from([add]); + const rem_buf = Buffer.from([rem]); + + { + const p = Promise.withResolvers(); + generatePrime(32, { add: add_buf, rem: rem_buf }, (err, prime) => { + if (err) return p.reject(err); + ok(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + strictEqual(val % add, rem); + p.resolve(); + }); + await p.promise; + } + + { + const prime = generatePrimeSync(32, { add: add_buf, rem: rem_buf }); + ok(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + strictEqual(val % add, rem); + } + + { + const prime = generatePrimeSync(32, { + add: BigInt(add), + rem: BigInt(rem), + }); + ok(checkPrimeSync(prime)); + const buf = Buffer.from(prime); + const val = buf.readUInt32BE(); + strictEqual(val % add, rem); + } + + { + const p = Promise.withResolvers(); + generatePrime( + 128, + { + bigint: true, + add: 5n, + }, + (err, prime) => { + // Fails because the add option is not a supported value + if (err) return p.reject(err); + }, + ); + await rejects(p.promise); + } + { + const p = Promise.withResolvers(); + generatePrime( + 128, + { + bigint: true, + safe: true, + add: 5n, + }, + (err, prime) => { + // Fails because the add option is not a supported value + if (err) return p.reject(err); + }, + ); + await rejects(p.promise); + } + + // This is impossible because it implies (prime % 2**64) == 1 and + // prime < 2**64, meaning prime = 1, but 1 is not prime. + for (const add of [2n ** 64n, 2n ** 65n]) { + throws( + () => { + generatePrimeSync(64, { add }); + }, + { + name: "RangeError", + }, + ); + } + + // Any parameters with rem >= add lead to an impossible condition. + for (const rem of [7n, 8n, 3000n]) { + throws( + () => { + generatePrimeSync(64, { add: 7n, rem }); + }, + { + name: "RangeError", + }, + ); + } + + // This is possible, but not allowed. It implies prime == 7, which means that + // we did not actually generate a random prime. + throws( + () => { + generatePrimeSync(3, { add: 8n, rem: 7n }); + }, + { + name: "RangeError", + }, + ); + + // We only allow specific values of add and rem + throws( + () => + generatePrimeSync(8, { + add: 7n, + rem: 1n, + }), + { + name: "RangeError", + }, + ); + throws( + () => + generatePrimeSync(8, { + add: 12n, + rem: 10n, + }), + { + name: "RangeError", + }, + ); + throws( + () => + generatePrimeSync(8, { + add: 12n, + }), + { + name: "RangeError", + }, + ); + + [1, "hello", {}, []].forEach((i) => { + throws(() => checkPrime(i), { + code: "ERR_INVALID_ARG_TYPE", + }); + }); + + for (const checks of ["hello", {}, []]) { + throws(() => checkPrime(2n, { checks }, () => {}), { + code: "ERR_INVALID_ARG_TYPE", + message: /checks/, + }); + throws(() => checkPrimeSync(2n, { checks }), { + code: "ERR_INVALID_ARG_TYPE", + message: /checks/, + }); + } + + // prettier-ignore + for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) { + throws(() => checkPrime(2n, { checks }, () => {}), { + code: 'ERR_OUT_OF_RANGE', + message: /<= 2147483647/ + }); + throws(() => checkPrimeSync(2n, { checks }), { + code: 'ERR_OUT_OF_RANGE', + message: /<= 2147483647/ + }); + } + + ok( + !checkPrimeSync(Buffer.from([0x1]), { + fast: true, + trialDivision: true, + checks: 10, + }), + ); + + throws( + () => { + generatePrimeSync(32, { bigint: "" }); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + throws( + () => { + generatePrime(32, { bigint: "" }, () => {}); + }, + { code: "ERR_INVALID_ARG_TYPE" }, + ); + + { + const prime = generatePrimeSync(3, { bigint: true }); + strictEqual(typeof prime, "bigint"); + strictEqual(prime, 7n); + ok(checkPrimeSync(prime)); + const p = Promise.withResolvers(); + checkPrime(prime, (err, result) => { + if (err) return p.reject(err); + p.resolve(result); + }); + await p.promise; + } + + { + const p = Promise.withResolvers(); + generatePrime(3, { bigint: true }, (err, prime) => { + if (err) return p.reject(err); + strictEqual(typeof prime, "bigint"); + strictEqual(prime, 7n); + ok(checkPrimeSync(prime)); + checkPrime(prime, (err, result) => { + if (err) return p.reject(err); + p.resolve(result); + }); + }); + await p.promise; + } + }, +}; + +export const timingSafeEqualTest = { + test() { + timingSafeEqual(new Uint8Array(1), new Uint8Array(1)); + }, +}; + +export const randomIntTest = { + async test() { + const { randomInt } = await import("./index.js"); + + // min === max should throw, not infinite-loop + throws(() => randomInt(5, 5), { code: "ERR_OUT_OF_RANGE" }); + + // min > max should throw + throws(() => randomInt(10, 5), { code: "ERR_OUT_OF_RANGE" }); + + // Valid range returns value in [min, max) + const val = randomInt(0, 10); + ok(val >= 0 && val < 10); + + // Single-arg form: randomInt(max) means [0, max) + strictEqual(randomInt(1), 0); + }, +}; + +export const randomFillSyncTest = { + async test() { + const { randomFillSync } = await import("./index.js"); + + // With offset but no size, should fill only buf.length - offset bytes + const buf = Buffer.alloc(10, 0); + randomFillSync(buf, 4); + + // First 4 bytes must be untouched (all zero) + for (let i = 0; i < 4; i++) { + strictEqual(buf[i], 0, `byte ${i} should be untouched`); + } + }, +}; + +// Regression: randomFillSync with DataView was silently filling zero bytes +// because DataView has no .length property (only .byteLength). +export const randomFillSyncDataView = { + async test() { + const { randomFillSync } = await import("./index.js"); + + // DataView without explicit offset/size — should fill all 8 bytes. + const ab = new ArrayBuffer(16); + const dv = new DataView(ab, 4, 8); + randomFillSync(dv); + + // Verify the DataView region was actually filled (not left as zeros). + // With 8 random bytes, P(all zero) = 2^-64 — negligible false-positive. + const filled = new Uint8Array(ab, 4, 8); + let allZero = true; + for (let i = 0; i < filled.length; i++) { + if (filled[i] !== 0) { + allZero = false; + break; + } + } + if (allZero) + throw new Error("DataView region was not filled with random data"); + + // Verify bytes outside the DataView window were NOT touched. + const before = new Uint8Array(ab, 0, 4); + const after = new Uint8Array(ab, 12, 4); + for (let i = 0; i < 4; i++) { + strictEqual( + before[i], + 0, + `byte before DataView at ${i} should be untouched`, + ); + strictEqual( + after[i], + 0, + `byte after DataView at ${i} should be untouched`, + ); + } + + // DataView with explicit offset and size args. + const ab2 = new ArrayBuffer(16); + const dv2 = new DataView(ab2); + randomFillSync(dv2, 2, 4); + const slice = new Uint8Array(ab2, 2, 4); + allZero = true; + for (let i = 0; i < slice.length; i++) { + if (slice[i] !== 0) { + allZero = false; + break; + } + } + if (allZero) throw new Error("DataView with offset/size was not filled"); + + // Bytes outside the fill region should be untouched. + strictEqual( + new Uint8Array(ab2, 0, 2).every((b) => b === 0), + true, + "bytes before fill region should be zero", + ); + strictEqual( + new Uint8Array(ab2, 6, 10).every((b) => b === 0), + true, + "bytes after fill region should be zero", + ); + }, +}; + +// Ref: https://github.com/cloudflare/workerd/issues/2716 +export const getRandomValuesIllegalInvocation = { + async test() { + const crypto = await import("./index.js"); + { + // The following assertion doesn't fail as of Node.js v20.17.0 + const getRandomValues = crypto.getRandomValues; + strictEqual(getRandomValues(new Uint8Array(6)).length, 6); + } + throws( + () => { + // This ensures that we are replicating Node.js behavior as of v20.17.0 + const getRandomValues = crypto.webcrypto.getRandomValues; + strictEqual(getRandomValues(new Uint8Array(6)).length, 6); + }, + { + name: "TypeError", + // The adapter exposes the host Crypto object; Node's host uses a + // different brand-error message. Both real WebViews are also tested. + message: /Illegal invocation|Value of "this" must be of type Crypto/, + }, + ); + strictEqual(crypto.getRandomValues(new Uint8Array(6)).length, 6); + }, +}; + +export const generate_prime_size_cap = { + test() { + throws(() => generatePrimeSync(8193), { name: "RangeError" }); + ok(generatePrimeSync(512).byteLength > 0); + }, +}; diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index 3b1758f2..9e7c0fca 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -57,6 +57,16 @@ Where absent, its input brand is unverified; equivalent half-float bytes are still tested as a byte-array input. This does not certify complete crypto input coverage on that engine. +Random bytes and fills use the pinned Workers frontend and host cryptographic +entropy. `pseudoRandomBytes` is the same function as `randomBytes`. Sync fills +return the selected subview; callback fills return the original input. Workers' +byte-length validation and typed-array element slicing are preserved, including +its host Web Crypto quota and rejection of floating-point arrays. Input brands +are checked with intrinsic accessors, not `Symbol.toStringTag`. Host Web Crypto +brand-error wording varies by engine; shared contracts verify the error class. +The upstream random/prime fixtures and consumer declaration checks run with +`npx vitest run src/compat/crypto/random.spec.ts`. + Adapter-created secret keys support copied raw/JWK export, equality, generation, HMAC, HKDF and symmetric ciphers. KeyObject.from can retain a host CryptoKey, but synchronous extraction of its opaque key material is unavailable. RSA, EC, Ed25519 and X25519 key generation and PEM/DER/JWK conversion use diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index 021697a6..578e6761 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,121 @@ export const eventCases = { + cryptoRandomContracts: async function ({ crypto }) { + const attempt = (fn) => { + try { + return fn(); + } catch (e) { + return { name: e.name, code: e.code ?? null, message: e.message }; + } + }; + const fills = [ + () => new Uint8Array(16), + () => new Uint16Array(8), + () => new Int32Array(4), + () => new BigUint64Array(2), + () => new DataView(new ArrayBuffer(24), 4, 16), + () => new ArrayBuffer(16), + ].map((make) => { + const input = make(); + const result = crypto.randomFillSync(input, 1, 1); + const whole = new Uint8Array( + input instanceof ArrayBuffer ? input : input.buffer, + ); + const start = (input.byteOffset ?? 0) + (input.BYTES_PER_ELEMENT ?? 1); + const end = start + (input.BYTES_PER_ELEMENT ?? 1); + return { + length: result.byteLength, + offset: result.byteOffset, + brand: Object.prototype.toString.call(result), + same: result === input, + outsideZero: whole.every((v, i) => (i >= start && i < end) || v === 0), + }; + }); + const errors = [ + () => crypto.randomBytes(-1), + () => crypto.randomBytes(1.5), + () => crypto.randomBytes("1"), + () => crypto.randomFillSync("x"), + () => crypto.randomFillSync(new Uint8Array(2), -1), + () => crypto.randomFillSync(new Uint8Array(2), 0, 3), + () => crypto.randomFill(new Uint8Array(2), 3, () => {}), + () => crypto.randomFill(new Uint8Array(2), null), + () => crypto.randomBytes(0, null), + ].map((fn) => attempt(fn)); + const brandChecks = [ + { [Symbol.toStringTag]: "ArrayBuffer", byteLength: 1 }, + { + [Symbol.toStringTag]: "DataView", + byteLength: 1, + buffer: new ArrayBuffer(1), + byteOffset: 0, + }, + Object.defineProperty(new ArrayBuffer(0), Symbol.toStringTag, { + value: "Object", + }), + Object.defineProperty( + new DataView(new ArrayBuffer(0)), + Symbol.toStringTag, + { value: "Object" }, + ), + ].map((input) => attempt(() => crypto.randomFillSync(input).byteLength)); + const beyond = crypto.randomFillSync(new Uint8Array(2), 3); + const hostErrors = [ + () => crypto.randomFillSync(new Float32Array(2)), + () => crypto.randomBytes(65537), + ].map((fn) => { + try { + fn(); + return "accepted"; + } catch (e) { + return e.name; + } + }); + const order = [], + input = new DataView(new ArrayBuffer(32), 8, 16); + const callback = await new Promise((resolve) => { + const result = crypto.randomFill(input, 2, 4, (error, value) => { + order.push("callback"); + resolve({ error: error?.name ?? null, same: value === input }); + }); + order.push("sync"); + if (result !== undefined) + throw new Error("randomFill must return undefined"); + }); + const empty = await new Promise((resolve) => + crypto.randomBytes(0, (error, value) => + resolve({ error: error?.name ?? null, length: value.length }), + ), + ); + let detachedName; + try { + const get = crypto.webcrypto.getRandomValues; + get(new Uint8Array(1)); + } catch (e) { + detachedName = e.name; + } + const get = crypto.getRandomValues; + return { + fills, + brandChecks, + errors, + hostErrors, + beyond: beyond.byteLength, + callback, + order, + empty, + detachedName, + boundLength: get(new Uint8Array(1)).length, + aliases: [ + crypto.pseudoRandomBytes === crypto.randomBytes, + crypto.default.pseudoRandomBytes === crypto.randomBytes, + ], + extraAliases: ["prng", "rng", "listCiphers", "createCredentials"].map( + (name) => [name in crypto, name in crypto.default], + ), + aliasError: attempt(() => crypto.pseudoRandomBytes(-1)), + }; + }, + cryptoWorkLimits: async function ({ crypto }) { const attempt = (fn) => { try { diff --git a/vitest.config.ts b/vitest.config.ts index 89608599..30ded4ce 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -74,6 +74,7 @@ export default defineConfig({ "src/compat/net/sockets.spec.ts", "src/compat/tls/upgrade.spec.ts", "src/compat/crypto/kdf-limits.spec.ts", + "src/compat/crypto/random.spec.ts", "src/compat/crypto/cipher.spec.ts", "src/compat/crypto/hash.spec.ts", "src/compat/crypto/hash-types.spec.ts", From a35a343d7e9add8315d200eb6f9ba5c54e66fefb Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 03:05:34 +0900 Subject: [PATCH 07/30] feat: implement Workers Diffie-Hellman and request-scoped generation --- .../node-compat/expansion-contracts.probe.mjs | 6 +- .../node-compat/expansion-native.mjs | 3 +- src/compat/crypto/asymmetric.ts | 14 +- src/compat/crypto/dh-handle.ts | 212 +++++++ src/compat/crypto/dh.spec.ts | 45 ++ src/compat/crypto/dh.ts | 227 ++++++++ src/compat/crypto/ecdh.ts | 46 +- src/compat/crypto/index.ts | 12 +- src/compat/crypto/native-cipher.build.json | 4 +- src/compat/crypto/native-cipher.c | 33 ++ src/compat/crypto/native-cipher.wasm | Bin 291907 -> 312272 bytes src/compat/crypto/upstream-dh.fixture.mjs | 547 ++++++++++++++++++ src/compat/expansion/README.md | 11 + src/compat/expansion/index.spec.mjs | 142 +++++ src/shared/request-context.spec.ts | 41 ++ src/shared/request-context.ts | 11 + src/shared/request-scope.ts | 7 +- vitest.config.ts | 2 + 18 files changed, 1329 insertions(+), 34 deletions(-) create mode 100644 src/compat/crypto/dh-handle.ts create mode 100644 src/compat/crypto/dh.spec.ts create mode 100644 src/compat/crypto/dh.ts create mode 100644 src/compat/crypto/upstream-dh.fixture.mjs create mode 100644 src/shared/request-context.spec.ts create mode 100644 src/shared/request-context.ts diff --git a/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs b/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs index 971aeb6b..eef7375d 100644 --- a/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs +++ b/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs @@ -74,10 +74,11 @@ const compiled = await build({ resolveDir: root, contents: `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { eventCases } from './src/compat/expansion/index.spec.mjs'; +import { runWithRequestScope as withRequestScope } from './dist/shared/request-scope.js'; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); -export const run = name => eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, +export const run = name => eventCases[name]({withRequestScope,process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, }, bundle: true, platform: "browser", @@ -96,10 +97,11 @@ await writeFile( entry, `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { eventCases } from ${JSON.stringify(fileURLToPath(new URL("../../../../src/compat/expansion/index.spec.mjs", import.meta.url)))}; +import { runWithRequestScope as withRequestScope } from ${JSON.stringify(fileURLToPath(new URL("../../../../dist/shared/request-scope.js", import.meta.url)))}; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); -export const run = name => eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, +export const run = name => eventCases[name]({withRequestScope,process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, ); const viteResult = await viteBuild({ configFile: false, diff --git a/demo/tests/experiments/node-compat/expansion-native.mjs b/demo/tests/experiments/node-compat/expansion-native.mjs index 4dbe1f71..99d7d369 100644 --- a/demo/tests/experiments/node-compat/expansion-native.mjs +++ b/demo/tests/experiments/node-compat/expansion-native.mjs @@ -105,7 +105,8 @@ import process from 'node:process'; fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); import {measureOperation,measurements} from ${JSON.stringify(root + "/demo/tests/experiments/node-compat/native-measurements.mjs")}; export {measurements}; -export const run = name => measureOperation(name,()=>eventCases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http}),name==='classicCodecs'?()=>zlib.gzipSync('x'.repeat(1024)):undefined); +import { runWithRequestScope as withRequestScope } from ${JSON.stringify(consumer + "/node_modules/cf-workers-native/dist/shared/request-scope.js")}; +export const run = name => measureOperation(name,()=>eventCases[name]({withRequestScope,process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http}),name==='classicCodecs'?()=>zlib.gzipSync('x'.repeat(1024)):undefined); export {createNetworkRelay, initializeWorkerNetwork} from ${JSON.stringify(consumer + "/node_modules/cf-workers-native/dist/browser/fetch/relay.js")}; export const runTls = async ({port,upgradePort}) => { const attempt = (servername,halfClose=false) => new Promise((resolve,reject)=> { diff --git a/src/compat/crypto/asymmetric.ts b/src/compat/crypto/asymmetric.ts index c7357637..c1a4116b 100644 --- a/src/compat/crypto/asymmetric.ts +++ b/src/compat/crypto/asymmetric.ts @@ -233,12 +233,14 @@ export function generateKeyPairSync(type: string, options: any = {}) { } } else key = NativeKey.generate(type.toUpperCase()); const privateKey = create("private", { asymmetric: key }) as PrivateKeyObject; - // Workers retains the generated DH key in both wrappers. Imported public - // keys still contain only public material. - const publicKey = - type === "dh" - ? (create("public", { asymmetric: key }) as PublicKeyObject) - : createPublicKey(privateKey); + // Workers constructs a separate public-only EC key. Other generated pairs + // retain the same key material under their public and private wrappers. + const publicKey = create("public", { + asymmetric: + type === "ec" + ? NativeKey.parse(key.export(false, false, "spki"), false, false) + : key, + }) as PublicKeyObject; return { publicKey: publicKeyEncoding !== undefined diff --git a/src/compat/crypto/dh-handle.ts b/src/compat/crypto/dh-handle.ts new file mode 100644 index 00000000..dfc72093 --- /dev/null +++ b/src/compat/crypto/dh-handle.ts @@ -0,0 +1,212 @@ +// Implements pinned workerd dh.c++ using its BoringSSL backend. +import { Buffer } from "node:buffer"; +import { inputs, library, output } from "./native-cipher.js"; +import { bytes } from "./validation.js"; +import { hasActiveRequest } from "../../shared/request-context.js"; +const released = new FinalizationRegistry((pointer) => + library().dh_free(pointer), +); +function integer(data: Uint8Array): bigint { + const hex = Buffer.from(data).toString("hex"); + return hex ? BigInt("0x" + hex) : 0n; +} +function numeric(value: number): Uint8Array { + let hex = value.toString(16); + if (hex.length % 2) hex = "0" + hex; + return Buffer.from(hex, "hex"); +} +function peerError(code: number, operation: string): void { + if (code === 0) return; + const compute = operation === "computeSecret"; + const prefix = `DiffieHellman ${operation}() failed: `; + if (code === -1) + throw new Error( + prefix + + (compute + ? "could not validate peer public key" + : "could not validate public key"), + ); + if (code & 1) + throw new RangeError( + prefix + (compute ? "Supplied key is too small" : "key is too small"), + ); + if (code & 2) + throw new RangeError( + prefix + (compute ? "Supplied key is too large" : "key is too large"), + ); + throw new Error( + prefix + (compute ? "invalid peer public key" : "invalid public key"), + ); +} +export class NativeDH { + private pointer: number; + private verifyError: number; + constructor( + prime: number | ArrayBuffer | ArrayBufferView, + generator: number | ArrayBuffer | ArrayBufferView, + group = false, + ) { + const api = library(); + let pointer = 0; + if (group) pointer = api.dh_group(prime); + else if (typeof prime === "number") { + if (typeof generator !== "number") + throw new Error("DiffieHellman init failed: invalid parameters"); + if (!hasActiveRequest()) + throw new Error( + "DiffieHellman key generation requires an active request", + ); + if (prime > 10000) + throw new RangeError( + "DiffieHellman init failed: requested prime size too large", + ); + pointer = api.dh_parameters(prime, generator); + if (!pointer) + throw new Error( + "DiffieHellman init failed: could not generate parameters", + ); + } else { + const p = bytes(prime, "prime"); + if (p.length > 1250) + throw new RangeError("DiffieHellman init failed: key is too large"); + if (!p.length) throw new Error("DiffieHellman init failed: invalid key"); + let g: Uint8Array; + if (typeof generator === "number") { + if (generator < 2) + throw new RangeError( + "DiffieHellman init failed: generator too small", + ); + g = numeric(generator); + } else { + g = bytes(generator, "generator"); + if (g.length > 1250) + throw new RangeError( + "DiffieHellman init failed: generator is too large", + ); + if (!g.length || integer(g) < 2n) + throw new Error("DiffieHellman init failed: invalid generator"); + } + if (!(integer(p) & 1n) || integer(g) >= integer(p)) + throw new Error( + "DiffieHellman init failed: Invalid DH prime generated", + ); + pointer = inputs([p, g], (api, [pp, gg]) => + api.dh_create(pp, p.length, gg, g.length), + ); + if (!pointer) + throw new Error("DiffieHellman init failed: could not set keys"); + } + if (!pointer) + throw new Error("DiffieHellmanGroup init failed: could not set keys"); + try { + if (!api.dh_valid(pointer)) + throw new Error( + "DiffieHellman init failed: Invalid DH prime generated", + ); + // RFC 3526 primes are fixed safe primes. Preserve BoringSSL's generator + // suitability flag (including its p mod 24 rule) without re-testing primality. + const verify = group + ? api.dh_group_check(pointer) + : api.dh_check(pointer); + if (verify < 0) throw new Error("DiffieHellman init failed"); + this.pointer = pointer; + this.verifyError = verify; + released.register(this, pointer, this); + } catch (error) { + api.dh_free(pointer); + throw error; + } + } + + static group(name: unknown): NativeDH { + const groups: Record = { + modp14: 2048, + modp15: 3072, + modp16: 4096, + modp17: 6144, + modp18: 8192, + }; + const normalized = `${name}`.split("\0")[0]!.toLowerCase(); + const bits = Object.hasOwn(groups, normalized) + ? groups[normalized] + : undefined; + if (!bits) + throw new Error( + "Failed to init DiffieHellmanGroup: invalid group. Only groups {modp14, modp15, modp16, modp17, modp18} are supported.", + ); + return new NativeDH(bits, 2, true); + } + getVerifyError() { + return this.verifyError; + } + private get(kind: number): Uint8Array { + const api = library(), + length = api.dh_get(this.pointer, kind, 0); + if (length < 0) + throw new Error( + kind === 2 + ? "No public key" + : kind === 3 + ? "No private key" + : kind === 0 + ? "No DiffieHellman prime" + : "No DiffieHellman generator", + ); + return inputs([length], (api, [out]) => { + if (api.dh_get(this.pointer, kind, out) < 0) + throw new Error("Error while retrieving DiffieHellman key"); + return output(api, out, length); + }); + } + getPrime() { + return this.get(0); + } + getGenerator() { + return this.get(1); + } + getPublicKey() { + return this.get(2); + } + getPrivateKey() { + return this.get(3); + } + generateKeys() { + if (!library().dh_generate(this.pointer)) throw new Error("internal error"); + return this.getPublicKey(); + } + setPrivateKey(value: ArrayBuffer | ArrayBufferView) { + this.set(value, true); + } + setPublicKey(value: ArrayBuffer | ArrayBufferView) { + this.set(value, false); + } + private set(value: ArrayBuffer | ArrayBufferView, privateKey: boolean) { + const data = bytes(value, "key"); + inputs([data], (api, [p]) => { + const code = api.dh_set(this.pointer, +privateKey, p, data.length); + if (!privateKey) peerError(code, "setPublicKey"); + else if (code !== 0) throw new Error("internal error"); + }); + } + computeSecret(value: ArrayBuffer | ArrayBufferView): Uint8Array { + const data = bytes(value, "key"); + if (data.length > 0x7fffffff) + throw new RangeError( + "DiffieHellman computeSecret() failed: key is too large", + ); + if (!data.length) + throw new Error("DiffieHellman computeSecret() failed: invalid key"); + return inputs([data, this.getPrime().length], (api, [p, out]) => { + peerError( + api.dh_peer_check(this.pointer, p, data.length), + "computeSecret", + ); + const length = api.dh_compute(this.pointer, p, data.length, out); + if (length < 0) + throw new Error( + "DiffieHellman computeSecret() failed: error computing shared secret", + ); + return output(api, out, length); + }); + } +} diff --git a/src/compat/crypto/dh.spec.ts b/src/compat/crypto/dh.spec.ts new file mode 100644 index 00000000..5a8ea447 --- /dev/null +++ b/src/compat/crypto/dh.spec.ts @@ -0,0 +1,45 @@ +import { test, expect, expectTypeOf } from "vitest"; +import { runWithRequestScope } from "../../shared/request-scope.js"; +// @ts-expect-error Pinned upstream JavaScript fixture. +import * as fixtures from "./upstream-dh.fixture.mjs"; +for (const [name, fixture] of Object.entries(fixtures)) + test( + "pinned Workers DH: " + name, + () => + runWithRequestScope(() => (fixture as { test: () => unknown }).test()), + 30000, + ); + +import { Buffer } from "node:buffer"; +import { + DiffieHellman, + DiffieHellmanGroup, + createDiffieHellman, + ECDH, +} from "./index.js"; +test("DH requires request scope for numeric generation", () => { + expect(() => createDiffieHellman(64)).toThrow( + new Error("DiffieHellman key generation requires an active request"), + ); +}); +test("DH and ECDH consumer declarations", () => { + const d = DiffieHellman(new Uint8Array([23])); + expectTypeOf(d.getPrime()).toEqualTypeOf(); + expectTypeOf(d.getPrime("hex")).toEqualTypeOf(); + const ec = ECDH("prime256v1"); + expectTypeOf(ec.generateKeys()).toEqualTypeOf(); + expectTypeOf(ec.getPublicKey("hex")).toEqualTypeOf(); + expectTypeOf(ec.getPrivateKey(undefined)).toEqualTypeOf(); + expectTypeOf(d.getPrime(undefined)).toEqualTypeOf(); + expectTypeOf( + ECDH.convertKey(ec.getPublicKey(), "prime256v1"), + ).toEqualTypeOf(); + expectTypeOf( + ECDH.convertKey(ec.getPublicKey(), "prime256v1", undefined, "hex"), + ).toEqualTypeOf(); +}); +function invalidGroup(group: DiffieHellmanGroup) { + // @ts-expect-error Fixed groups do not expose setters. + group.setPrivateKey(new Uint8Array([2])); +} +void invalidGroup; diff --git a/src/compat/crypto/dh.ts b/src/compat/crypto/dh.ts new file mode 100644 index 00000000..b316c70b --- /dev/null +++ b/src/compat/crypto/dh.ts @@ -0,0 +1,227 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +// +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Adapted from workerd@5358e3d337336eaf696a48bc1563d808b10feba5. +import { Buffer } from "node:buffer"; +import { NativeDH } from "./dh-handle.js"; +import { bytes, isAnyBuffer } from "./validation.js"; +import { validateInt32 } from "../net/validators.js"; +import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; +const kHandle = Symbol("kHandle"); +type Input = string | ArrayBuffer | SharedArrayBuffer | ArrayBufferView; +type Encoding = BufferEncoding | "buffer"; +interface SharedDH { + [kHandle]: NativeDH; + readonly verifyError: number; + generateKeys(encoding?: undefined): Buffer; + generateKeys(encoding: "buffer"): Buffer; + generateKeys(encoding: BufferEncoding): string; + computeSecret(key: Input, inputEncoding?: Encoding): Buffer; + computeSecret( + key: Input, + inputEncoding: Encoding | undefined, + outputEncoding: "buffer", + ): Buffer; + computeSecret( + key: Input, + inputEncoding: Encoding | undefined, + outputEncoding: BufferEncoding, + ): string; + getPrime(encoding?: undefined): Buffer; + getPrime(encoding: "buffer"): Buffer; + getPrime(encoding: BufferEncoding): string; + getGenerator(encoding?: undefined): Buffer; + getGenerator(encoding: "buffer"): Buffer; + getGenerator(encoding: BufferEncoding): string; + getPublicKey(encoding?: undefined): Buffer; + getPublicKey(encoding: "buffer"): Buffer; + getPublicKey(encoding: BufferEncoding): string; + getPrivateKey(encoding?: undefined): Buffer; + getPrivateKey(encoding: "buffer"): Buffer; + getPrivateKey(encoding: BufferEncoding): string; +} +export interface DiffieHellman extends SharedDH { + setPrivateKey(key: Input, encoding?: Encoding): this; + setPublicKey(key: Input, encoding?: Encoding): this; +} +export interface DiffieHellmanGroup extends SharedDH {} +type Arguments = [ + sizeOrKey: number | Input, + keyEncoding?: number | Input, + generator?: number | Input, + genEncoding?: Encoding, +]; +function decode( + value: Input, + encoding?: Encoding, +): ArrayBuffer | ArrayBufferView { + if (typeof value === "string") + return Buffer.from(value, encoding === "buffer" ? "utf8" : encoding); + return bytes(value, "key"); +} +function encode(value: Uint8Array, encoding?: Encoding): Buffer | string { + const result = Buffer.from(value); + return encoding && encoding !== "buffer" ? result.toString(encoding) : result; +} +export const DiffieHellman = function ( + this: DiffieHellman, + sizeOrKey: number | Input, + keyEncoding?: number | Input, + generator?: number | Input, + genEncoding?: Encoding, +): DiffieHellman { + if (!(this instanceof DiffieHellman)) + return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); + if ( + typeof sizeOrKey !== "number" && + typeof sizeOrKey !== "string" && + !ArrayBuffer.isView(sizeOrKey) && + !isAnyBuffer(sizeOrKey) + ) + throw new ERR_INVALID_ARG_TYPE( + "sizeOrKey", + ["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + sizeOrKey, + ); + if (typeof sizeOrKey === "number") validateInt32(sizeOrKey, "sizeOrKey"); + if ( + keyEncoding && + keyEncoding !== "buffer" && + !Buffer.isEncoding(keyEncoding as string) + ) { + genEncoding = generator as Encoding; + generator = keyEncoding; + keyEncoding = "utf8"; + } + keyEncoding ??= "utf8"; + genEncoding ??= "utf8"; + if (typeof sizeOrKey !== "number") + sizeOrKey = decode(sizeOrKey, keyEncoding as Encoding); + if (!generator) generator = 2; + else if (typeof generator === "number") validateInt32(generator, "generator"); + else if (typeof generator === "string") + generator = decode(generator, genEncoding); + else if (!ArrayBuffer.isView(generator) && !isAnyBuffer(generator)) + throw new ERR_INVALID_ARG_TYPE( + "generator", + ["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + generator, + ); + this[kHandle] = new NativeDH( + sizeOrKey as number | ArrayBuffer | ArrayBufferView, + generator as number | ArrayBuffer | ArrayBufferView, + ); + Object.defineProperty(DiffieHellman.prototype, "verifyError", { + get(this: DiffieHellman) { + return this[kHandle].getVerifyError(); + }, + enumerable: true, + configurable: true, + }); + return this; +} as unknown as { + new (...args: Arguments): DiffieHellman; + (...args: Arguments): DiffieHellman; + prototype: DiffieHellman; +}; +export const DiffieHellmanGroup = function ( + this: DiffieHellmanGroup, + name: string, +): DiffieHellmanGroup { + if (!(this instanceof DiffieHellmanGroup)) + return new DiffieHellmanGroup(name); + this[kHandle] = NativeDH.group(name); + Object.defineProperty(DiffieHellmanGroup.prototype, "verifyError", { + get(this: DiffieHellmanGroup) { + return this[kHandle].getVerifyError(); + }, + enumerable: true, + configurable: true, + }); + return this; +} as unknown as { + new (name: string): DiffieHellmanGroup; + (name: string): DiffieHellmanGroup; + prototype: DiffieHellmanGroup; +}; +const methods = { + generateKeys(this: SharedDH, encoding?: Encoding) { + return encode(this[kHandle].generateKeys(), encoding); + }, + computeSecret( + this: SharedDH, + key: Input, + inputEncoding?: Encoding, + outputEncoding?: Encoding, + ) { + return encode( + this[kHandle].computeSecret(decode(key, inputEncoding)), + outputEncoding, + ); + }, + getPrime(this: SharedDH, encoding?: Encoding) { + return encode(this[kHandle].getPrime(), encoding); + }, + getGenerator(this: SharedDH, encoding?: Encoding) { + return encode(this[kHandle].getGenerator(), encoding); + }, + getPublicKey(this: SharedDH, encoding?: Encoding) { + return encode(this[kHandle].getPublicKey(), encoding); + }, + getPrivateKey(this: SharedDH, encoding?: Encoding) { + return encode(this[kHandle].getPrivateKey(), encoding); + }, +}; +const setters = { + setPublicKey(this: DiffieHellman, key: Input, encoding?: Encoding) { + this[kHandle].setPublicKey(decode(key, encoding)); + return this; + }, + setPrivateKey(this: DiffieHellman, key: Input, encoding?: Encoding) { + this[kHandle].setPrivateKey(decode(key, encoding)); + return this; + }, +}; +for (const [name, method] of Object.entries({ ...methods, ...setters })) + Object.defineProperty(method, "name", { + value: "dh" + name[0]!.toUpperCase() + name.slice(1), + configurable: true, + }); +Object.assign(DiffieHellman.prototype, methods, setters); +Object.assign(DiffieHellmanGroup.prototype, methods); +export function createDiffieHellman( + sizeOrKey: number | Input, + keyEncoding?: number | Input, + generator?: number | Input, + genEncoding?: Encoding, +): DiffieHellman { + return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); +} +export function createDiffieHellmanGroup(name: string): DiffieHellmanGroup { + return new DiffieHellmanGroup(name); +} +export function getDiffieHellman(name: string): DiffieHellmanGroup { + return createDiffieHellmanGroup(name); +} diff --git a/src/compat/crypto/ecdh.ts b/src/compat/crypto/ecdh.ts index a27f1ff5..b15028ce 100644 --- a/src/compat/crypto/ecdh.ts +++ b/src/compat/crypto/ecdh.ts @@ -7,20 +7,27 @@ import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; import { isArrayBufferView } from "../util/types/index.js"; const cryptoImpl = { ECDHHandle: NativeECDH }; const kHandle = Symbol("kHandle"); +export type ECDHFormat = "compressed" | "uncompressed" | "hybrid"; +type ECDHInput = string | ArrayBufferView; export interface ECDH { - [kHandle]: cryptoImpl.ECDHHandle; + [kHandle]: NativeECDH; computeSecret( - otherPublicKey: string | ArrayBufferView | ArrayBuffer, - inputEncoding?: string, - outputEncoding?: string, - ): Buffer | string; - generateKeys(encoding?: string, format?: string): Buffer | string; - getPrivateKey(encoding?: string): Buffer | string; - getPublicKey(encoding?: string, format?: string): Buffer | string; - setPrivateKey( - key: string | ArrayBufferView | ArrayBuffer, - encoding?: string, - ): void; + key: ECDHInput, + inputEncoding?: BufferEncoding, + outputEncoding?: undefined, + ): Buffer; + computeSecret( + key: ECDHInput, + inputEncoding: BufferEncoding | undefined, + outputEncoding: BufferEncoding, + ): string; + generateKeys(encoding?: undefined, format?: ECDHFormat): Buffer; + generateKeys(encoding: BufferEncoding, format?: ECDHFormat): string; + getPrivateKey(encoding?: undefined): Buffer; + getPrivateKey(encoding: BufferEncoding): string; + getPublicKey(encoding?: undefined, format?: ECDHFormat): Buffer; + getPublicKey(encoding: BufferEncoding, format?: ECDHFormat): string; + setPrivateKey(key: ECDHInput, encoding?: BufferEncoding): void; } export const ECDH = function (this: ECDH, curveName: string) { @@ -32,6 +39,21 @@ export const ECDH = function (this: ECDH, curveName: string) { return this; } as unknown as { new (curveName: string): ECDH; + (curveName: string): ECDH; + convertKey( + key: ECDHInput, + curve: string, + inputEncoding?: BufferEncoding, + outputEncoding?: undefined, + format?: ECDHFormat, + ): Buffer; + convertKey( + key: ECDHInput, + curve: string, + inputEncoding: BufferEncoding | undefined, + outputEncoding: BufferEncoding, + format?: ECDHFormat, + ): string; }; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access diff --git a/src/compat/crypto/index.ts b/src/compat/crypto/index.ts index 7d995842..c682ea31 100644 --- a/src/compat/crypto/index.ts +++ b/src/compat/crypto/index.ts @@ -1,3 +1,5 @@ +import * as dh from "./dh.js"; +export * from "./dh.js"; import { pbkdf2, pbkdf2Sync } from "./pbkdf2.js"; export { pbkdf2, pbkdf2Sync }; import { Hash, Hmac, createHmac, createHash, hash } from "./hash.js"; @@ -52,14 +54,7 @@ export { randomBytes, randomFill, randomFillSync }; export const pseudoRandomBytes = randomBytes; import { scrypt, scryptSync } from "./scrypt.js"; export { hkdf, hkdfSync, randomInt, randomUUID, scrypt, scryptSync }; -export const { - DiffieHellmanGroup, - createDiffieHellmanGroup, - getDiffieHellman, - createDiffieHellman, - DiffieHellman, - constants, -} = original; +export const { constants } = original; import { Cipheriv, Decipheriv, @@ -82,6 +77,7 @@ export default { X509Certificate, Certificate, ...originalSupported, + ...dh, pbkdf2, pbkdf2Sync, Hash, diff --git a/src/compat/crypto/native-cipher.build.json b/src/compat/crypto/native-cipher.build.json index f81a14fc..691f0610 100644 --- a/src/compat/crypto/native-cipher.build.json +++ b/src/compat/crypto/native-cipher.build.json @@ -21,6 +21,6 @@ "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" ], "libraries": ["libdecrepit.a", "libcrypto.a"], - "bindingSha256": "e19810fb59e71f3de0a7b87885779c56dcec1fb5cb5aa841996d9fd9d680f368", - "sha256": "c3e35a8accc7db4c8ba1080ffe774bb3c72f7690910d1e829422371443c50a5d" + "bindingSha256": "930b5fc1bc1844b987e03234c9d8ca560a2da8d6b402707dc171de0bb1c08cc3", + "sha256": "e0d9300f35b387a087fa792d8c04f2f9fef36cbde68d6f9334eccf3db887ff98" } diff --git a/src/compat/crypto/native-cipher.c b/src/compat/crypto/native-cipher.c index 56e2f43c..d5bed397 100644 --- a/src/compat/crypto/native-cipher.c +++ b/src/compat/crypto/native-cipher.c @@ -62,3 +62,36 @@ API int cipher_dh_derive(const unsigned char *prime,int plen,const unsigned char result=DH_compute_key_padded(out,y,dh); done: BN_free(p);BN_free(g);BN_clear_free(x);BN_free(y);DH_free(dh);return result; } + +API DH *dh_create(const unsigned char *prime,int plen,const unsigned char *generator,int glen){ + DH *dh=DH_new();BIGNUM *p=BN_bin2bn(prime,plen,NULL),*g=BN_bin2bn(generator,glen,NULL); + if(!dh || !p || !g || !DH_set0_pqg(dh,p,NULL,g)){DH_free(dh);BN_free(p);BN_free(g);return NULL;}return dh; +} +API DH *dh_parameters(int bits,int generator){ + DH *dh=DH_new();if(!dh || !DH_generate_parameters_ex(dh,bits,generator,NULL)){DH_free(dh);return NULL;}return dh; +} +API DH *dh_group(int bits){ + BIGNUM *p=bits==2048?BN_get_rfc3526_prime_2048(NULL):bits==3072?BN_get_rfc3526_prime_3072(NULL):bits==4096?BN_get_rfc3526_prime_4096(NULL):bits==6144?BN_get_rfc3526_prime_6144(NULL):bits==8192?BN_get_rfc3526_prime_8192(NULL):NULL; + BIGNUM *g=BN_new();DH *dh=DH_new();if(!p || !g || !dh || !BN_set_word(g,2) || !DH_set0_pqg(dh,p,NULL,g)){BN_free(p);BN_free(g);DH_free(dh);return NULL;}return dh; +} +API void dh_free(DH *dh){DH_free(dh);} +API int dh_check(DH *dh){int code=0;return DH_check(dh,&code)?code:-1;} +API int dh_generate(DH *dh){return DH_generate_key(dh);} +API int dh_get(DH *dh,int kind,unsigned char *out){ + const BIGNUM *bn=kind==0?DH_get0_p(dh):kind==1?DH_get0_g(dh):kind==2?DH_get0_pub_key(dh):DH_get0_priv_key(dh); + if(!bn)return -1;int length=BN_num_bytes(bn);if(out && !BN_bn2bin_padded(out,length,bn))return -1;return length; +} +API int dh_set(DH *dh,int private_key,const unsigned char *key,int length){ + BIGNUM *bn=BN_bin2bn(key,length,NULL);if(!bn)return -1; + if(!private_key){int code=0;if(!DH_check_pub_key(dh,bn,&code)){BN_free(bn);return -1;}if(code){BN_free(bn);return code;}} + int ok=DH_set0_key(dh,private_key?NULL:bn,private_key?bn:NULL);if(!ok)BN_clear_free(bn);return ok?0:-1; +} +API int dh_peer_check(DH *dh,const unsigned char *key,int length){ + BIGNUM *bn=BN_bin2bn(key,length,NULL);int code=0;int ok=bn && DH_check_pub_key(dh,bn,&code);BN_free(bn);return ok?code:-1; +} +API int dh_compute(DH *dh,const unsigned char *key,int length,unsigned char *out){ + BIGNUM *bn=BN_bin2bn(key,length,NULL);if(!bn)return -1;int size=DH_compute_key_padded(out,bn,dh);BN_free(bn);return size; +} +API int dh_valid(DH *dh){return BN_is_odd(DH_get0_p(dh)) && BN_ucmp(DH_get0_g(dh),DH_get0_p(dh))<0;} + +API int dh_group_check(DH *dh){return BN_mod_word(DH_get0_p(dh),24)==11?0:DH_NOT_SUITABLE_GENERATOR;} diff --git a/src/compat/crypto/native-cipher.wasm b/src/compat/crypto/native-cipher.wasm index d869b39cd6fae07f731ad255ebb8f77c232e1981..de6c70822586b0b773880ce1a21f2acde43586e4 100755 GIT binary patch delta 85352 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 crypto.createDiffieHellman(13.37), { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: + 'The value of "sizeOrKey" is out of range. ' + + "It must be an integer. Received 13.37", + }); + + assert.throws(() => crypto.createDiffieHellman("abcdef", 13.37), { + code: "ERR_OUT_OF_RANGE", + name: "RangeError", + message: + 'The value of "generator" is out of range. ' + + "It must be an integer. Received 13.37", + }); + // BoringSSL throws when key sizes > 10000 bits are requested, we proactively return a + // RangeError instead which is more descriptive. + assert.throws(() => crypto.createDiffieHellman(10001), { + name: "RangeError", + }); + + for (const bits of [-1, 0, 1]) { + assert.throws(() => crypto.createDiffieHellman(bits), { + name: "Error", + }); + } + + // Through a fluke of history, g=0 defaults to DH_GENERATOR (2). + { + const g = 0; + crypto.createDiffieHellman("abcdef", "hex", g); + } + + for (const g of [-1, 1]) { + const ex = { + name: "RangeError", + }; + assert.throws(() => crypto.createDiffieHellman("abcdef", g), ex); + assert.throws(() => crypto.createDiffieHellman("abcdef", "hex", g), ex); + } + + // Calls with even p, or p values that are interpreted as <= g will be rejected. + crypto.createDiffieHellman("abcdef", "hex", Buffer.from([2])); // OK + for (const bits of ["abcdef", Buffer.from([1]), Buffer.from([4])]) { + assert.throws(() => crypto.createDiffieHellman(bits), { + name: "Error", + }); + } + + for (const g of [Buffer.from([]), Buffer.from([0]), Buffer.from([1])]) { + const ex = { + name: "Error", + }; + assert.throws(() => crypto.createDiffieHellman("abcdef", g), ex); + assert.throws(() => crypto.createDiffieHellman("abcdef", "hex", g), ex); + } + + [[0x1, 0x2], () => {}, /abc/, {}].forEach((input) => { + assert.throws(() => crypto.createDiffieHellman(input), { + code: "ERR_INVALID_ARG_TYPE", + name: "TypeError", + }); + }); + + assert.throws( + function () { + crypto.getDiffieHellman("unknown-group"); + }, + { + name: "Error", + }, + "crypto.getDiffieHellman('unknown-group') " + + "failed to throw the expected error.", + ); + + assert.throws(() => crypto.createDiffieHellman("", true), { + code: "ERR_INVALID_ARG_TYPE", + }); + + [true, Symbol(), {}, () => {}, []].forEach((generator) => + assert.throws(() => crypto.createDiffieHellman("", "base64", generator), { + name: "TypeError", + }), + ); + }, +}; + +/////////////// + +export const dh_verify_error_test = { + test(ctrl, env, ctx) { + // Second OAKLEY group, see + // https://github.com/nodejs/node-v0.x-archive/issues/2338 and + // https://xml2rfc.tools.ietf.org/public/rfc/html/rfc2412.html#anchor49 + const p = + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74" + + "020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437" + + "4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"; + crypto.createDiffieHellman(p, "hex"); + + // Confirm DH_check() results are exposed for optional examination. Use an odd value for prime + // since even values are rejected immediately. + const bad_dh = crypto.createDiffieHellman("03", "hex"); + assert.notStrictEqual(bad_dh.verifyError, 0); + }, +}; + +///////////////////// + +export const dh_constructor_test = { + test(ctrl, env, ctx) { + const DiffieHellmanGroup = crypto.DiffieHellmanGroup; + const dhg = DiffieHellmanGroup("modp14"); + assert.ok( + dhg instanceof DiffieHellmanGroup, + "DiffieHellmanGroup is expected " + + "to return a new instance when " + + "called without `new`", + ); + + const p1 = dhg.getPrime("buffer"); + const DiffieHellman = crypto.DiffieHellman; + const dh = DiffieHellman(p1, "buffer"); + assert.ok( + dh instanceof DiffieHellman, + "DiffieHellman is expected to return a " + + "new instance when called without `new`", + ); + }, +}; +//////////////////// + +// This test will fail if BoringSSL runs in FIPS mode and succeed otherwise; disable it for now. + +/* +function test() { + const odd = Buffer.alloc(39, 'A'); + + const c = crypto.createDiffieHellman(size); + c.setPrivateKey(odd); + c.generateKeys(); +} + +// FIPS requires a length of at least 1024 +if (!common.hasFipsCrypto) { + test(); +} else { + assert.throws(function() { test(); }, /key size too small/); +} +*/ + +////////////////// + +export const dh_group_test = { + test(ctrl, env, ctx) { + assert.throws( + function () { + crypto.getDiffieHellman("modp14").setPrivateKey(""); + }, + new RegExp( + "^TypeError: crypto\\.getDiffieHellman\\(\\.\\.\\.\\)\\." + + "setPrivateKey is not a function$", + ), + "crypto.getDiffieHellman('modp14').setPrivateKey('') " + + "failed to throw the expected error.", + ); + assert.throws( + function () { + crypto.getDiffieHellman("modp14").setPublicKey(""); + }, + new RegExp( + "^TypeError: crypto\\.getDiffieHellman\\(\\.\\.\\.\\)\\." + + "setPublicKey is not a function$", + ), + "crypto.getDiffieHellman('modp14').setPublicKey('') " + + "failed to throw the expected error.", + ); + }, +}; + +//////////////// + +export const dh_exchange_test = { + test(ctrl, env, ctx) { + const alice = crypto.createDiffieHellmanGroup("modp14"); + const bob = crypto.createDiffieHellmanGroup("modp14"); + alice.generateKeys(); + bob.generateKeys(); + const aSecret = alice.computeSecret(bob.getPublicKey()).toString("hex"); + const bSecret = bob.computeSecret(alice.getPublicKey()).toString("hex"); + assert.strictEqual(aSecret, bSecret); + }, +}; + +//////////////// + +// This test verifies padding with leading zeroes for shared +// secrets that are strictly smaller than the modulus (prime). +// See: +// RFC 4346: https://www.ietf.org/rfc/rfc4346.txt +// https://github.com/nodejs/node-v0.x-archive/issues/7906 +// https://github.com/nodejs/node-v0.x-archive/issues/5239 +// +// In FIPS mode OPENSSL_DH_FIPS_MIN_MODULUS_BITS = 1024, meaning we need +// a FIPS-friendly >= 1024 bit prime, we can use MODP 14 from RFC 3526: +// https://www.ietf.org/rfc/rfc3526.txt +// +// We can generate appropriate values with this code: +// +// crypto = require('crypto'); +// +// for (;;) { +// var a = crypto.getDiffieHellman('modp14'), +// var b = crypto.getDiffieHellman('modp14'); +// +// a.generateKeys(); +// b.generateKeys(); +// +// var aSecret = a.computeSecret(b.getPublicKey()).toString('hex'); +// console.log("A public: " + a.getPublicKey().toString('hex')); +// console.log("A private: " + a.getPrivateKey().toString('hex')); +// console.log("B public: " + b.getPublicKey().toString('hex')); +// console.log("B private: " + b.getPrivateKey().toString('hex')); +// console.log("A secret: " + aSecret); +// console.log('-------------------------------------------------'); +// if(aSecret.substring(0,2) === "00") { +// console.log("found short key!"); +// return; +// } +// } + +const apub = + "5484455905d3eff34c70980e871f27f05448e66f5a6efbb97cbcba4e927196c2bd9ea272cded91\ +10a4977afa8d9b16c9139a444ed2d954a794650e5d7cb525204f385e1af81530518563822ecd0f9\ +524a958d02b3c269e79d6d69850f0968ad567a4404fbb0b19efc8bc73e267b6136b88cafb33299f\ +f7c7cace3ffab1a88c2c9ee841f88b4c3679b4efc465f5c93cca11d487be57373e4c5926f634c4e\ +efee6721d01db91cd66321615b2522f96368dbc818875d422140d0edf30bdb97d9721feddcb9ff6\ +453741a4f687ee46fc54bf1198801f1210ac789879a5ee123f79e2d2ce1209df2445d32166bc9e4\ +8f89e944ec9c3b2e16c8066cd8eebd4e33eb941"; +const bpub = + "3fca64510e36bc7da8a3a901c7b74c2eabfa25deaf7cbe1d0c50235866136ad677317279e1fb0\ +06e9c0a07f63e14a3363c8e016fbbde2b2c7e79fed1cc3e08e95f7459f547a8cd0523ee9dc744d\ +e5a956d92b937db4448917e1f6829437f05e408ee7aea70c0362b37370c7c75d14449d8b2d2133\ +04ac972302d349975e2265ca7103cfebd019d9e91234d638611abd049014f7abf706c1c5da6c88\ +788a1fdc6cdf17f5fffaf024ce8711a2ebde0b52e9f1cb56224483826d6e5ac6ecfaae07b75d20\ +6e8ac97f5be1a5b68f20382f2a7dac189cf169325c4cf845b26a0cd616c31fec905c5d9035e5f7\ +8e9880c812374ac0f3ca3d365f06e4be526b5affd4b79"; +const apriv = + "62411e34704637d99c6c958a7db32ac22fcafafbe1c33d2cfdb76e12ded41f38fc16b792b9041\ +2e4c82755a3815ba52f780f0ee296ad46e348fc4d1dcd6b64f4eea1b231b2b7d95c5b1c2e26d34\ +83520558b9860a6eb668f01422a54e6604aa7702b4e67511397ef3ecb912bff1a83899c5a5bfb2\ +0ee29249a91b8a698e62486f7009a0e9eaebda69d77ecfa2ca6ba2db6c8aa81759c8c90c675979\ +08c3b3e6fc60668f7be81cce6784482af228dd7f489005253a165e292802cfd0399924f6c56827\ +7012f68255207722355634290acc7fddeefbba75650a85ece95b6a12de67eac016ba78960108dd\ +5dbadfaa43cc9fed515a1f307b7d90ae0623bc7b8cefb"; +const secret = + "00c37b1e06a436d6717816a40e6d72907a6f255638b93032267dcb9a5f0b4a9aa0236f3dce63b\ +1c418c60978a00acd1617dfeecf1661d8a3fafb4d0d8824386750f4853313400e7e4afd22847e4\ +fa56bc9713872021265111906673b38db83d10cbfa1dea3b6b4c97c8655f4ae82125281af7f234\ +8916a15c6f95649367d169d587697480df4d10b381479e86d5518b520d9d8fb764084eab518224\ +dc8fe984ddaf532fc1531ce43155fa0ab32532bf1ece5356b8a3447b5267798a904f16f3f4e635\ +597adc0179d011132dcffc0bbcb0dd2c8700872f8663ec7ddd897c659cc2efebccc73f38f0ec96\ +8612314311231f905f91c63a1aea52e0b60cead8b57df"; + +export const dh_padding_test = { + test(ctrl, env, ctx) { + /* FIPS-friendly 2048 bit prime */ + const p = crypto.createDiffieHellman( + crypto.getDiffieHellman("modp14").getPrime(), + ); + + p.setPublicKey(apub, "hex"); + p.setPrivateKey(apriv, "hex"); + + assert.strictEqual( + p.computeSecret(bpub, "hex", "hex").toString("hex"), + secret, + ); + }, +}; + +export const dhKeygenTest = { + test() { + // FIPS-mode BoringSSL mandates keys of at least 1024 bits. RFC 8270 recommends that sizes of + // at least 2048 bits should be used, 1024-bit primes are sufficient for these tests though. + const size = 1024; + const dh1 = crypto.createDiffieHellman(size); + const p1 = dh1.getPrime("buffer"); + const dh2 = crypto.createDiffieHellman(p1, "buffer"); + const key1 = dh1.generateKeys(); + const key2 = dh2.generateKeys("hex"); + const secret1 = dh1.computeSecret(key2, "hex", "base64"); + const secret2 = dh2.computeSecret(key1, "latin1", "buffer"); + + // Test Diffie-Hellman with two parties sharing a secret, + // using various encodings as we go along + assert.strictEqual(secret2.toString("base64"), secret1); + + assert.strictEqual(dh1.verifyError, 0); + assert.strictEqual(dh2.verifyError, 0); + + // Create "another dh1" using generated keys from dh1, + // and compute secret again + const dh3 = crypto.createDiffieHellman(p1, "buffer"); + const privkey1 = dh1.getPrivateKey(); + dh3.setPublicKey(key1); + dh3.setPrivateKey(privkey1); + + assert.deepStrictEqual(dh1.getPrime(), dh3.getPrime()); + assert.deepStrictEqual(dh1.getGenerator(), dh3.getGenerator()); + assert.deepStrictEqual(dh1.getPublicKey(), dh3.getPublicKey()); + assert.deepStrictEqual(dh1.getPrivateKey(), dh3.getPrivateKey()); + assert.strictEqual(dh3.verifyError, 0); + + const secret3 = dh3.computeSecret(key2, "hex", "base64"); + + assert.strictEqual(secret1, secret3); + + // computeSecret works without a public key set at all. + const dh4 = crypto.createDiffieHellman(p1, "buffer"); + dh4.setPrivateKey(privkey1); + + assert.deepStrictEqual(dh1.getPrime(), dh4.getPrime()); + assert.deepStrictEqual(dh1.getGenerator(), dh4.getGenerator()); + assert.deepStrictEqual(dh1.getPrivateKey(), dh4.getPrivateKey()); + assert.strictEqual(dh4.verifyError, 0); + + const secret4 = dh4.computeSecret(key2, "hex", "base64"); + + assert.strictEqual(secret1, secret4); + + assert.throws( + () => { + dh3.computeSecret(""); + }, + { name: "Error" }, + ); + }, +}; + +export const ecdh = { + test() { + const curves = crypto.getCurves(); + curves.forEach((i) => { + const alice = crypto.createECDH(i); + const bob = crypto.createECDH(i); + + alice.generateKeys(); + bob.generateKeys(); + + const aliceSecret = alice.computeSecret(bob.getPublicKey(), null, "hex"); + const bobSecret = bob.computeSecret(alice.getPublicKey(), null, "hex"); + + assert.strictEqual(aliceSecret, bobSecret); + }); + }, +}; + +export const ecdhConvertKey = { + test() { + const ecdh = crypto.createECDH("prime256v1"); + ecdh.generateKeys(); + + const compressedKey = ecdh.getPublicKey("hex", "compressed"); + + const uncompressedKey = crypto.ECDH.convertKey( + compressedKey, + "prime256v1", + "hex", + "hex", + "uncompressed", + ); + + // The converted key and the uncompressed public key should be the same + assert.strictEqual(uncompressedKey, ecdh.getPublicKey("hex")); + }, +}; + +export const statelessDh = { + test() { + // DH keygen is currently unsupported by the boringssl+fips + // we use internally. This test works for workerd but fails + // on the internal run. + // const pair1 = crypto.generateKeyPairSync('dh', { + // prime: Buffer.from([31, 0, 0, 0, 0, 1]), + // }); + // const pair2 = crypto.generateKeyPairSync('dh', { + // prime: Buffer.from([31, 0, 0, 0, 0, 1]), + // }); + // const sec1 = crypto.diffieHellman({ + // publicKey: pair1.publicKey, + // privateKey: pair2.privateKey, + // }); + // const sec2 = crypto.diffieHellman({ + // publicKey: pair2.publicKey, + // privateKey: pair1.privateKey, + // }); + // assert.deepStrictEqual(sec1, sec2); + }, +}; + +export const statelessDhX25591 = { + test() { + const pair1 = crypto.generateKeyPairSync("x25519"); + const pair2 = crypto.generateKeyPairSync("x25519"); + const sec1 = crypto.diffieHellman({ + publicKey: pair1.publicKey, + privateKey: pair2.privateKey, + }); + const sec2 = crypto.diffieHellman({ + publicKey: pair2.publicKey, + privateKey: pair1.privateKey, + }); + assert.deepStrictEqual(sec1, sec2); + }, +}; + +export const statelessDhEc = { + test() { + // The Boringssl-based implementation currently does not support + // the mechanisms for stateless dh using named EC curve. + const pair1 = crypto.generateKeyPairSync("ec", { namedCurve: "secp224r1" }); + const pair2 = crypto.generateKeyPairSync("ec", { namedCurve: "secp224r1" }); + assert.throws( + () => { + crypto.diffieHellman({ + publicKey: pair1.publicKey, + privateKey: pair2.privateKey, + }); + }, + { + message: /Failed to derive/, + }, + ); + }, +}; + +export const helloTest = { + test() { + const v2 = crypto.createDiffieHellman("hello"); + assert.throws(() => v2.getPrivateKey(v2, "hello", v2, v2, "hello"), { + message: /No private key/, + }); + }, +}; + +export const DhLargeGeneratorParam = { + async test() { + const prime = Buffer.from([ + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, + 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, + 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, + ]); + + const generator = new Uint8Array(100 * 1024 * 1024); + generator.fill(0x02); + + assert.throws(() => crypto.createDiffieHellman(prime, generator), { + name: "RangeError", + message: /generator is too large/, + }); + }, +}; + +// Test that invalid public keys are rejected by setPublicKey and computeSecret. +export const dhPubKeyValidation = { + test() { + const dh = crypto.createDiffieHellman(1024); + dh.generateKeys(); + const prime = dh.getPrime(); + + // Public key of 0 should be rejected (too small) + const dh2 = crypto.createDiffieHellman(prime); + dh2.generateKeys(); + assert.throws(() => dh2.setPublicKey(Buffer.from([0x00])), { + message: /too small|invalid public key/, + }); + + // Public key of 1 should be rejected (too small) + assert.throws(() => dh2.setPublicKey(Buffer.from([0x01])), { + message: /too small|invalid public key/, + }); + + // Public key equal to the prime should be rejected (too large) + assert.throws(() => dh2.setPublicKey(prime), { + message: /too large|invalid public key/, + }); + + // computeSecret should also reject invalid peer keys proactively + assert.throws(() => dh2.computeSecret(Buffer.from([0x00])), { + message: /too small|invalid peer public key/, + }); + assert.throws(() => dh2.computeSecret(Buffer.from([0x01])), { + message: /too small|invalid peer public key/, + }); + assert.throws(() => dh2.computeSecret(prime), { + message: /too large|invalid peer public key/, + }); + + // Valid exchange should still work + const dh3 = crypto.createDiffieHellman(prime); + dh3.generateKeys(); + const secret1 = dh.computeSecret(dh3.getPublicKey()); + const secret2 = dh3.computeSecret(dh.getPublicKey()); + assert.deepStrictEqual(secret1, secret2); + }, +}; diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index 9e7c0fca..a98c4312 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -79,6 +79,17 @@ generation and shared-secret calculation use the pinned BoringSSL backend, including small primes and rejection of mismatched parameters. Workers' DH key export rejection is preserved. The upstream key-generation and scrypt fixtures run with `npx vitest run src/compat/crypto/kdf-limits.spec.ts`. +The DiffieHellman and DiffieHellmanGroup frontend uses the same BoringSSL backend +for parameter checking, key generation, setters and padded shared secrets. +Fixed groups are modp14 through modp18; their pinned `verifyError` is 8, including +BoringSSL's generator-suitability flag. Numeric parameter generation requires an +active local request, including when crypto is first dynamically imported within +that request. The request marker is initialized at dispatch, without changing +host globals merely by importing the dispatcher. Safe-prime generation is +synchronous and can occupy the WebView for a long time. Workers' per-request +CPU/memory limit-enforcer cancellation is not implemented; the maximum prime +size alone does not provide that protection. The DH/ECDH upstream fixtures and +consumer declaration checks run with `npx vitest run src/compat/crypto/dh.spec.ts`. Remaining key import/export and other crypto error contracts still need work. Cipheriv/Decipheriv use the same pinned BoringSSL revision as Workers. Their frontend preserves Workers' `authLengthTag` option spelling, AEAD's one-update limit, early authentication-tag requirements, tag consumption and buffer copies. diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index 578e6761..710ae5cc 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,146 @@ export const eventCases = { + cryptoDhContracts: function ({ crypto, withRequestScope = (fn) => fn() }) { + return withRequestScope(() => { + const attempt = (fn) => { + try { + return fn(); + } catch (e) { + return { + name: e.name, + code: e.code ?? null, + message: e.message.replace( + /internal error; reference = .*/, + "internal error", + ), + }; + } + }; + const groups = [ + "modp14", + "modp15", + "modp16", + "modp17", + "modp18", + "MODP14", + "modp14\0suffix", + "modp5", + "modp1", + "invalid", + ].map((name) => + attempt(() => { + const d = crypto.getDiffieHellman(name); + return { + length: d.getPrime().length, + generator: d.getGenerator("hex"), + verify: d.verifyError, + setters: [typeof d.setPublicKey, typeof d.setPrivateKey], + }; + }), + ); + const errors = [ + () => crypto.createDiffieHellman(10001), + () => crypto.createDiffieHellman(8, 100000), + () => crypto.createDiffieHellman(8, new Uint8Array([2])), + () => crypto.createDiffieHellman(new Uint8Array(1251)), + () => crypto.createDiffieHellman(new Uint8Array(0)), + () => crypto.createDiffieHellman(new Uint8Array([23]), 1), + () => + crypto.createDiffieHellman(new Uint8Array([23]), new Uint8Array([1])), + () => + crypto.createDiffieHellman( + new Uint8Array([23]), + new Uint8Array(1251), + ), + () => crypto.createDiffieHellman(new Uint8Array([22])), + () => crypto.createDiffieHellman(new Uint8Array([3])).generateKeys(), + ].map((fn) => + attempt(() => { + fn(); + return "accepted"; + }), + ); + const d = crypto.createDiffieHellman(new Uint8Array([23])); + const before = [ + attempt(() => d.getPublicKey()), + attempt(() => d.getPrivateKey()), + ]; + d.setPrivateKey(new Uint8Array([3])); + const pub = d.generateKeys("hex"); + const peer = crypto.createDiffieHellman(new Uint8Array([23])); + peer.setPrivateKey(new Uint8Array([4])); + peer.generateKeys(); + const secret = d.computeSecret(peer.getPublicKey(), undefined, "hex"); + const keyErrors = [ + new Uint8Array(0), + new Uint8Array([0]), + new Uint8Array([1]), + new Uint8Array([23]), + ].map((key) => [ + attempt(() => d.setPublicKey(key)), + attempt(() => d.computeSecret(key)), + ]); + const numeric = crypto.createDiffieHellman(64); + const numericResult = { + length: numeric.getPrime().length, + generator: numeric.getGenerator("hex"), + verify: numeric.verifyError, + }; + const properties = Object.getOwnPropertyNames( + crypto.DiffieHellman.prototype, + ).map((name) => { + const descriptor = Object.getOwnPropertyDescriptor( + crypto.DiffieHellman.prototype, + name, + ); + return [ + name, + descriptor.enumerable, + descriptor.configurable, + descriptor.writable ?? null, + typeof descriptor.value === "function" ? descriptor.value.name : null, + ]; + }); + const x = crypto.generateKeyPairSync("x25519"), + y = crypto.generateKeyPairSync("x25519"); + const xSecret = crypto.diffieHellman({ + publicKey: x.publicKey, + privateKey: y.privateKey, + }); + const ec = crypto.generateKeyPairSync("ec", { namedCurve: "P-224" }); + const ecResult = attempt(() => + crypto.diffieHellman({ + publicKey: ec.publicKey, + privateKey: ec.privateKey, + }), + ); + return { + groups, + errors, + before, + pub, + secret, + verify: d.verifyError, + keyErrors, + numericResult, + properties, + lengths: [ + crypto.DiffieHellman.length, + crypto.createDiffieHellman.length, + ], + x25519: { + length: xSecret.length, + equal: xSecret.equals( + crypto.diffieHellman({ + publicKey: y.publicKey, + privateKey: x.privateKey, + }), + ), + }, + ecResult, + }; + }); + }, + cryptoRandomContracts: async function ({ crypto }) { const attempt = (fn) => { try { diff --git a/src/shared/request-context.spec.ts b/src/shared/request-context.spec.ts new file mode 100644 index 00000000..5d1dd333 --- /dev/null +++ b/src/shared/request-context.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from "vitest"; +test("unused dispatcher import preserves host globals", async () => { + const globals = [ + globalThis.Promise, + globalThis.Promise.prototype.then, + globalThis.MessageChannel, + globalThis.setTimeout, + globalThis.queueMicrotask, + ]; + await import("./request-scope.js"); + expect([ + globalThis.Promise, + globalThis.Promise.prototype.then, + globalThis.MessageChannel, + globalThis.setTimeout, + globalThis.queueMicrotask, + ]).toEqual(globals); +}); +test("first crypto dynamic import retains the active request", async () => { + const { runWithRequestScope } = await import("./request-scope.js"); + const { hasActiveRequest } = await import("./request-context.js"); + expect(hasActiveRequest()).toBe(false); + const result = await runWithRequestScope(async () => { + await Promise.resolve(); + const { createDiffieHellman } = await import("../compat/crypto/dh.js"); + expect(hasActiveRequest()).toBe(true); + return createDiffieHellman(64).getPrime().length; + }); + expect(result).toBe(8); + expect(hasActiveRequest()).toBe(false); +}); +test("request marker does not leak into concurrent outside callbacks", async () => { + const { runWithRequestScope } = await import("./request-scope.js"); + const { hasActiveRequest } = await import("./request-context.js"); + const inside = runWithRequestScope(async () => { + await Promise.resolve(); + return hasActiveRequest(); + }); + const outside = Promise.resolve().then(() => hasActiveRequest()); + expect(await Promise.all([inside, outside])).toEqual([true, false]); +}); diff --git a/src/shared/request-context.ts b/src/shared/request-context.ts new file mode 100644 index 00000000..8a2bd756 --- /dev/null +++ b/src/shared/request-context.ts @@ -0,0 +1,11 @@ +import { AsyncLocalStorage } from "../compat/async-hooks/index.js"; +// Created at dispatch, before optional modules are imported. Merely importing +// the SDK must not install async-context propagation or replace host globals. +let active: AsyncLocalStorage | undefined; +export function hasActiveRequest(): boolean { + return active?.getStore() === true; +} +export function runWithActiveRequest(callback: () => T): T { + active ??= new AsyncLocalStorage(); + return active.run(true, callback); +} diff --git a/src/shared/request-scope.ts b/src/shared/request-scope.ts index d91cc841..b28b91a8 100644 --- a/src/shared/request-scope.ts +++ b/src/shared/request-scope.ts @@ -1,5 +1,6 @@ -// Optional compatibility modules register request-local state here. Keeping -// this leaf dependency-free preserves the raw browser ESM entry point. +// Optional modules register request-local state here. The active request marker +// exists before registration so a first dynamic import sees its current request. +import { runWithActiveRequest } from "./request-context.js"; type Scope = (callback: () => T) => T; const scopes = new Set(); export function registerRequestScope(scope: Scope): void { @@ -11,5 +12,5 @@ export function runWithRequestScope(callback: () => T): T { index === entries.length ? callback() : entries[index](() => run(index + 1)); - return run(0); + return runWithActiveRequest(() => run(0)); } diff --git a/vitest.config.ts b/vitest.config.ts index 30ded4ce..f6d1a54e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -53,6 +53,7 @@ export default defineConfig({ "tests/unit/**/*.test.ts", "src/cloudflare/apis/cloudflare-node/index.spec.ts", "src/shared/globals/index.spec.ts", + "src/shared/request-context.spec.ts", "src/shared/globals/web.spec.ts", "src/browser/abort/installation.spec.ts", "src/compat/buffer/installation.spec.ts", @@ -75,6 +76,7 @@ export default defineConfig({ "src/compat/tls/upgrade.spec.ts", "src/compat/crypto/kdf-limits.spec.ts", "src/compat/crypto/random.spec.ts", + "src/compat/crypto/dh.spec.ts", "src/compat/crypto/cipher.spec.ts", "src/compat/crypto/hash.spec.ts", "src/compat/crypto/hash-types.spec.ts", From 9aab117091f3a806135bea91022029f11ab5da38 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 03:55:56 +0900 Subject: [PATCH 08/30] fix: align signature WebCrypto and custom stream contracts --- .../node-compat/expansion-contracts.probe.mjs | 34 +- .../node-compat/expansion-native.mjs | 4 +- docs/compatibility.md | 32 +- src/compat/crypto/asymmetric.ts | 41 +- src/compat/crypto/constants.ts | 75 +++ src/compat/crypto/digest-stream.spec.ts | 120 +++++ src/compat/crypto/digest-stream.ts | 247 ++++++++++ src/compat/crypto/digests.ts | 36 ++ src/compat/crypto/hash-handle.ts | 37 +- src/compat/crypto/index.ts | 8 +- src/compat/crypto/keys.ts | 11 +- src/compat/crypto/native-crypto.build.json | 4 +- src/compat/crypto/native-crypto.c | 4 +- src/compat/crypto/native-crypto.ts | 5 + src/compat/crypto/native-crypto.wasm | Bin 2295031 -> 2295037 bytes src/compat/crypto/rsa.ts | 41 +- src/compat/crypto/sign-handle.ts | 91 +++- src/compat/crypto/signatures.spec.ts | 35 ++ src/compat/crypto/signatures.ts | 59 ++- src/compat/crypto/validation.ts | 56 ++- src/compat/crypto/webcrypto.ts | 68 +++ src/compat/expansion/README.md | 28 ++ src/compat/expansion/index.spec.mjs | 454 ++++++++++++++++++ src/compat/fs/index.ts | 13 + src/compat/https/index.ts | 10 +- src/compat/stream/destroy.ts | 23 +- src/compat/stream/readable.ts | 12 +- src/compat/tls/jsstream.ts | 26 +- src/compat/tls/upgrade.spec.ts | 47 ++ src/compat/tls/wrap.ts | 6 +- src/compat/zlib/index.ts | 4 +- vitest.config.ts | 2 + 32 files changed, 1491 insertions(+), 142 deletions(-) create mode 100644 src/compat/crypto/constants.ts create mode 100644 src/compat/crypto/digest-stream.spec.ts create mode 100644 src/compat/crypto/digest-stream.ts create mode 100644 src/compat/crypto/digests.ts create mode 100644 src/compat/crypto/signatures.spec.ts create mode 100644 src/compat/crypto/webcrypto.ts diff --git a/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs b/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs index eef7375d..ac15f278 100644 --- a/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs +++ b/demo/tests/experiments/node-compat/expansion-contracts.probe.mjs @@ -9,7 +9,20 @@ import { build } from "esbuild"; import { chromium, webkit } from "@playwright/test"; import { Miniflare, convertV4MiniflareOptions } from "miniflare"; import { workersCapacitorPlugin } from "../../../../dist/tooling/esbuild.js"; -import { eventCases } from "../../../../src/compat/expansion/index.spec.mjs"; +import { eventCases as allEventCases } from "../../../../src/compat/expansion/index.spec.mjs"; + +const requestedCases = process.env.NODE_COMPAT_CASES?.split(","); +if (requestedCases) + for (const name of requestedCases) + assert.ok( + Object.hasOwn(allEventCases, name), + `Unknown compatibility case: ${name}`, + ); +const eventCases = requestedCases + ? Object.fromEntries( + requestedCases.map((name) => [name, allEventCases[name]]), + ) + : allEventCases; const root = fileURLToPath(new URL("../../../..", import.meta.url)); const profile = JSON.parse( @@ -30,7 +43,7 @@ const runtime = new Miniflare( { type: "ESModule", path: "/fixture/main.mjs", - contents: `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; + contents: `import * as tls from 'node:tls'; import * as classicStreams from 'node:stream'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; const cases = {${Object.entries(eventCases) @@ -38,7 +51,7 @@ const cases = {${Object.entries(eventCases) .join(",")}}; export default {async fetch(request) { const name = new URL(request.url).pathname.slice(1); - return Response.json(await cases[name]({process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http})); + return Response.json(await cases[name]({process, tls, classicStreams, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http})); }};`, }, { type: "Text", path: "/fixture/readonly.txt", contents: "abcdef" }, @@ -62,7 +75,12 @@ try { } finally { await runtime.dispose(); } -const output = new URL("../../../artifacts/node-inventory/", import.meta.url); +const output = new URL( + requestedCases + ? "../../../artifacts/node-inventory/targeted/" + : "../../../artifacts/node-inventory/", + import.meta.url, +); await mkdir(output, { recursive: true }); await writeFile( new URL("expansion-contracts-workerd.json", output), @@ -72,13 +90,13 @@ const compiled = await build({ absWorkingDir: root, stdin: { resolveDir: root, - contents: `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; + contents: `import * as tls from 'node:tls'; import * as classicStreams from 'node:stream'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { eventCases } from './src/compat/expansion/index.spec.mjs'; import { runWithRequestScope as withRequestScope } from './dist/shared/request-scope.js'; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); -export const run = name => eventCases[name]({withRequestScope,process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, +export const run = name => eventCases[name]({withRequestScope,process, tls, classicStreams, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, }, bundle: true, platform: "browser", @@ -95,13 +113,13 @@ export const run = name => eventCases[name]({withRequestScope,process, tls, stre const entry = fileURLToPath(new URL("expansion-vite-entry.mjs", output)); await writeFile( entry, - `import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; + `import * as tls from 'node:tls'; import * as classicStreams from 'node:stream'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { eventCases } from ${JSON.stringify(fileURLToPath(new URL("../../../../src/compat/expansion/index.spec.mjs", import.meta.url)))}; import { runWithRequestScope as withRequestScope } from ${JSON.stringify(fileURLToPath(new URL("../../../../dist/shared/request-scope.js", import.meta.url)))}; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.txt':'child'}); -export const run = name => eventCases[name]({withRequestScope,process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, +export const run = name => eventCases[name]({withRequestScope,process, tls, classicStreams, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http});`, ); const viteResult = await viteBuild({ configFile: false, diff --git a/demo/tests/experiments/node-compat/expansion-native.mjs b/demo/tests/experiments/node-compat/expansion-native.mjs index 99d7d369..1d166343 100644 --- a/demo/tests/experiments/node-compat/expansion-native.mjs +++ b/demo/tests/experiments/node-compat/expansion-native.mjs @@ -98,7 +98,7 @@ const compiled = await build({ absWorkingDir: consumer, stdin: { resolveDir: consumer, - contents: `import * as net from 'node:net'; import * as tls from 'node:tls'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; + contents: `import * as net from 'node:net'; import * as tls from 'node:tls'; import * as classicStreams from 'node:stream'; import * as streams from 'node:stream/web'; import * as module from 'node:module'; import * as httpCommon from 'node:_http_common'; import * as httpOutgoing from 'node:_http_outgoing'; import * as httpServer from 'node:_http_server'; import * as http from 'node:http'; import * as fs from 'node:fs'; import * as zlib from 'node:zlib'; import * as sys from 'node:sys'; import * as util from 'node:util'; import * as types from 'node:util/types'; import * as console from 'node:console'; import * as crypto from 'node:crypto'; import { eventCases } from ${JSON.stringify(root + "/src/compat/expansion/index.spec.mjs")}; import { AsyncLocalStorage } from 'node:async_hooks'; import process from 'node:process'; @@ -106,7 +106,7 @@ fs.registerVirtualFiles({'/bundle/readonly.txt':'abcdef','/bundle/nested/child.t import {measureOperation,measurements} from ${JSON.stringify(root + "/demo/tests/experiments/node-compat/native-measurements.mjs")}; export {measurements}; import { runWithRequestScope as withRequestScope } from ${JSON.stringify(consumer + "/node_modules/cf-workers-native/dist/shared/request-scope.js")}; -export const run = name => measureOperation(name,()=>eventCases[name]({withRequestScope,process, tls, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http}),name==='classicCodecs'?()=>zlib.gzipSync('x'.repeat(1024)):undefined); +export const run = name => measureOperation(name,()=>eventCases[name]({withRequestScope,process, tls, classicStreams, streams, sys, util, types, console, crypto, fs, zlib, module, httpCommon, httpOutgoing, httpServer, http}),name==='classicCodecs'?()=>zlib.gzipSync('x'.repeat(1024)):undefined); export {createNetworkRelay, initializeWorkerNetwork} from ${JSON.stringify(consumer + "/node_modules/cf-workers-native/dist/browser/fetch/relay.js")}; export const runTls = async ({port,upgradePort}) => { const attempt = (servername,halfClose=false) => new Promise((resolve,reject)=> { diff --git a/docs/compatibility.md b/docs/compatibility.md index 6251292b..0879e604 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -27,22 +27,22 @@ and [verification scope](native-embedding.md#verification) for host-specific evi Whole-Node compatibility is incomplete. Importing a module successfully does not establish equivalent methods, validation, state, scheduling or engine behavior. -| Area | Behavior and limitations | -| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| path, querystring, string_decoder, buffer, timers | Implement the pinned Workers profile. Workers stubs remain stubs; for example, matchesGlob reproduces the pinned rejection. | -| HTTP and net | In-memory server dispatch; no OS listening server. Outbound TCP/TLS requires the native transport. | -| fs / fs/promises | Read-only embedded files plus request-local in-memory `/tmp`, with writes, descriptors and FileHandle operations. Watch is unsupported, as in Workers. Full filesystem semantics remain under audit. | -| module | Limited builtin registry; createRequire loads VFS JSON only. Executable modules must be bundled; arbitrary dynamic resolution is unsupported. | -| process, console, util / util/types / sys | Incomplete exports or behavior. Engine introspection, call sites and host-object detection are not equivalent to Workers. | -| crypto and zlib | Selected profiles cover random/Hash/HMAC/PBKDF2/HKDF/scrypt, secret/asymmetric keys, signatures, primes and Brotli/Zstd streaming. X509Certificate parsing, fields and checks are implemented with the pinned Workers limits; additional algorithms and full crypto/codec parity remain gaps. | -| TLS and HTTP internals | TLS and HTTP internal imports resolve. Direct Node TLS exchange and hostname rejection are tested on native transports. Existing native sockets upgrade with authenticated TLS before application writes. TLS 1.2/1.3 upgrades are tested on both OSes; custom Duplex wrapping and complete class/state contracts remain gaps. | -| Streams | An owned implementation supplies byte streams and the tested Workers tee cancellation and pipeTo abort behavior on every host. Whole-module acceptance remains open. | -| async_hooks | Build-transformed async functions retain scoped stores. Untransformed native async callbacks reject; arbitrary dynamic code and request-origin isolation are not fully supported. | -| diagnostics_channel | Synchronous channel/store contracts are implemented; async propagation depends on the async-hooks limitations. | -| worker_threads | Workers stubs and local MessagePort behavior, not Node threads. Trusted-event branding, host-object serialization and global identity differ. | -| DNS | DNS-over-HTTPS implementation; synthetic Service Binding/Hyperdrive address overrides are not implemented. | -| events | [WebView events profile](../src/compat/events/README.md): EventEmitter, abort/iteration helpers, transformed AsyncResource propagation and process warning delivery. Native async and V8 inspection remain bounded. | -| Other modules | Implementations and shared contracts exist, but whole-module acceptance remains outstanding. Namespace equality alone is insufficient. | +| Area | Behavior and limitations | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| path, querystring, string_decoder, buffer, timers | Implement the pinned Workers profile. Workers stubs remain stubs; for example, matchesGlob reproduces the pinned rejection. | +| HTTP and net | In-memory server dispatch; no OS listening server. Outbound TCP/TLS requires the native transport. | +| fs / fs/promises | Read-only embedded files plus request-local in-memory `/tmp`, with writes, descriptors and FileHandle operations. Watch is unsupported, as in Workers. Full filesystem semantics remain under audit. | +| module | Limited builtin registry; createRequire loads VFS JSON only. Executable modules must be bundled; arbitrary dynamic resolution is unsupported. | +| process, console, util / util/types / sys | Incomplete exports or behavior. Engine introspection, call sites and host-object detection are not equivalent to Workers. | +| crypto and zlib | Selected profiles cover random/Hash/HMAC/PBKDF2/HKDF/scrypt, secret/asymmetric keys, signatures, primes and Brotli/Zstd streaming. X509Certificate parsing, fields and checks are implemented with the pinned Workers limits; additional algorithms and full crypto/codec parity remain gaps. | +| TLS and HTTP internals | TLS and HTTP internal imports resolve. Direct Node TLS exchange and hostname rejection are tested on native transports. Existing native sockets upgrade with authenticated TLS before application writes. TLS 1.2/1.3 upgrades are tested on both OSes; custom Duplex byte forwarding is implemented without TLS encryption, while complete class/state contracts remain under audit. | +| Streams | An owned implementation supplies byte streams and the tested Workers tee cancellation and pipeTo abort behavior on every host. Whole-module acceptance remains open. | +| async_hooks | Build-transformed async functions retain scoped stores. Untransformed native async callbacks reject; arbitrary dynamic code and request-origin isolation are not fully supported. | +| diagnostics_channel | Synchronous channel/store contracts are implemented; async propagation depends on the async-hooks limitations. | +| worker_threads | Workers stubs and local MessagePort behavior, not Node threads. Trusted-event branding, host-object serialization and global identity differ. | +| DNS | DNS-over-HTTPS implementation; synthetic Service Binding/Hyperdrive address overrides are not implemented. | +| events | [WebView events profile](../src/compat/events/README.md): EventEmitter, abort/iteration helpers, transformed AsyncResource propagation and process warning delivery. Native async and V8 inspection remain bounded. | +| Other modules | Implementations and shared contracts exist, but whole-module acceptance remains outstanding. Namespace equality alone is insufficient. | The exact missing/extra export baseline is maintained in [surface-gaps.json](../demo/tests/experiments/node-compat/surface-gaps.json), against diff --git a/src/compat/crypto/asymmetric.ts b/src/compat/crypto/asymmetric.ts index c1a4116b..ad747d71 100644 --- a/src/compat/crypto/asymmetric.ts +++ b/src/compat/crypto/asymmetric.ts @@ -1,3 +1,4 @@ +import { signatureDigest } from "./digests.js"; import { curveName, generateDh, deriveDh } from "./native-cipher.js"; import { validateExportOptions } from "./key-options.js"; import { @@ -308,15 +309,19 @@ export function sign( const digest = digestName(algorithm), raw = bytes(data, "data"); const execute = () => { - const options = optionsOf(key); + const options = optionsOf(key), + native = handle(operationKey(key, true)); + if (native.type === "dsa") + throw new Error("Signing with DSA keys is not currently supported"); + const normalized = digest === null ? null : signatureDigest(digest); + if ( + native.type === "x25519" || + (native.type === "ed25519" && normalized !== null) || + (native.type !== "ed25519" && normalized === null) + ) + throw new Error("Failed to initialize signing context"); return Buffer.from( - handle(operationKey(key, true)).sign( - digest, - raw, - options.padding ?? 0, - options.saltLength ?? -1, - options.dsaEncoding === "ieee-p1363", - ), + native.sign(normalized, raw, 0, -1, options.dsaEncoding === "ieee-p1363"), ); }; if (callback === undefined) return execute(); @@ -340,13 +345,23 @@ export function verify( raw = bytes(data, "data"), sig = bytes(signature, "signature"); const execute = () => { - const options = optionsOf(key); - return handle(operationKey(key, false)).verify( - digest, + const options = optionsOf(key), + native = handle(operationKey(key, false)); + if (native.type === "dsa") + throw new Error("Verifying with DSA keys is not currently supported"); + const normalized = digest === null ? null : signatureDigest(digest); + if ( + native.type === "x25519" || + (native.type === "ed25519" && normalized !== null) || + (native.type !== "ed25519" && normalized === null) + ) + throw new Error("Failed to initialize verification context"); + return native.verify( + normalized, raw, sig, - options.padding ?? 0, - options.saltLength ?? -1, + 0, + -1, options.dsaEncoding === "ieee-p1363", ); }; diff --git a/src/compat/crypto/constants.ts b/src/compat/crypto/constants.ts new file mode 100644 index 00000000..7f9b4d23 --- /dev/null +++ b/src/compat/crypto/constants.ts @@ -0,0 +1,75 @@ +// Pinned workerd 1.20261012.1 / compatibility date 2026-10-17. +// Descriptor snapshot from the shared Workers acceptance probe. +const values = { + DH_CHECK_P_NOT_SAFE_PRIME: 2, + DH_CHECK_P_NOT_PRIME: 1, + DH_UNABLE_TO_CHECK_GENERATOR: 4, + DH_NOT_SUITABLE_GENERATOR: 8, + RSA_PKCS1_PADDING: 1, + RSA_NO_PADDING: 3, + RSA_PKCS1_OAEP_PADDING: 4, + RSA_X931_PADDING: 5, + RSA_PKCS1_PSS_PADDING: 6, + RSA_PSS_SALTLEN_DIGEST: -1, + RSA_PSS_SALTLEN_MAX_SIGN: -2, + RSA_PSS_SALTLEN_AUTO: -2, + POINT_CONVERSION_COMPRESSED: 2, + POINT_CONVERSION_UNCOMPRESSED: 4, + POINT_CONVERSION_HYBRID: 6, + OPENSSL_VERSION_NUMBER: 0, + SSL_OP_ALL: 2147485776, + SSL_OP_ALLOW_NO_DHE_KEX: 1024, + SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: 262144, + SSL_OP_CIPHER_SERVER_PREFERENCE: 4194304, + SSL_OP_CISCO_ANYCONNECT: 32768, + SSL_OP_COOKIE_EXCHANGE: 8192, + SSL_OP_CRYPTOPRO_TLSEXT_BUG: 2147483648, + SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: 2048, + SSL_OP_LEGACY_SERVER_CONNECT: 4, + SSL_OP_NO_COMPRESSION: 131072, + SSL_OP_NO_ENCRYPT_THEN_MAC: 524288, + SSL_OP_NO_QUERY_MTU: 4096, + SSL_OP_NO_RENEGOTIATION: 1073741824, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: 65536, + SSL_OP_NO_SSLv2: 0, + SSL_OP_NO_SSLv3: 33554432, + SSL_OP_NO_TICKET: 16384, + SSL_OP_NO_TLSv1: 67108864, + SSL_OP_NO_TLSv1_1: 268435456, + SSL_OP_NO_TLSv1_2: 134217728, + SSL_OP_NO_TLSv1_3: 536870912, + SSL_OP_PRIORITIZE_CHACHA: 2097152, + SSL_OP_TLS_ROLLBACK_BUG: 8388608, + ENGINE_METHOD_RSA: 1, + ENGINE_METHOD_DSA: 2, + ENGINE_METHOD_DH: 4, + ENGINE_METHOD_RAND: 8, + ENGINE_METHOD_EC: 2048, + ENGINE_METHOD_CIPHERS: 64, + ENGINE_METHOD_DIGESTS: 128, + ENGINE_METHOD_PKEY_METHS: 512, + ENGINE_METHOD_PKEY_ASN1_METHS: 1024, + ENGINE_METHOD_ALL: 65535, + ENGINE_METHOD_NONE: 0, + defaultCoreCipherList: "", + TLS1_VERSION: 769, + TLS1_1_VERSION: 770, + TLS1_2_VERSION: 771, + TLS1_3_VERSION: 772, + defaultCipherList: "", +} as const; +export const constants = Object.create(null) as Omit< + { + readonly [K in keyof typeof values]: (typeof values)[K] extends number + ? number + : string; + }, + "defaultCipherList" +> & { defaultCipherList: string }; +for (const [name, value] of Object.entries(values)) + Object.defineProperty(constants, name, { + value, + writable: name === "defaultCipherList", + configurable: name === "defaultCipherList", + enumerable: false, + }); diff --git a/src/compat/crypto/digest-stream.spec.ts b/src/compat/crypto/digest-stream.spec.ts new file mode 100644 index 00000000..d975cf66 --- /dev/null +++ b/src/compat/crypto/digest-stream.spec.ts @@ -0,0 +1,120 @@ +import { expect, test } from "vitest"; +import { DigestStream } from "./digest-stream.js"; +import { dispose } from "../../shared/globals/symbols.js"; +const hex = (value: ArrayBuffer) => Buffer.from(value).toString("hex"); +for (const [algorithm, expected] of Object.entries({ + "SHA-256": "15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225", + crc32: "cbf43926", + crc32c: "e3069283", + crc64nvme: "ae8b14860a799888", +})) + test(`DigestStream ${algorithm} split input`, async () => { + const stream = new DigestStream(algorithm); + expect(stream.digest).toBe(stream.digest); + const writer = stream.getWriter(); + await writer.write("1234"); + await writer.write(new TextEncoder().encode("56789")); + await writer.close(); + expect(stream.bytesWritten).toBe(9n); + expect(hex(await stream.digest)).toBe(expected); + }); +test("WTF-8 chunks preserve independent lone surrogates", async () => { + const stream = new DigestStream("SHA-256"); + const writer = stream.getWriter(); + await writer.write("\ud800"); + expect(stream.bytesWritten).toBe(3n); + await writer.write("\udc00"); + await writer.close(); + expect(hex(await stream.digest)).toBe( + "b67d2ab17d9e4a31bfb0c9e3bf5acbbce5f3df109c4afde0c3fef9c16f712b37", + ); +}); +test("well-formed encoding carries a high surrogate until the next string", async () => { + const stream = new DigestStream("SHA-256", { toWellFormed: true }); + const writer = stream.getWriter(); + await writer.write("\ud800"); + expect(stream.bytesWritten).toBe(0n); + await writer.write("\udc00"); + expect(stream.bytesWritten).toBe(4n); + await writer.close(); + const expected = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode("\ud800\udc00"), + ); + expect(hex(await stream.digest)).toBe(hex(expected)); +}); +test("dispose rejects digest while an empty write still succeeds", async () => { + const stream = new DigestStream("SHA-256"); + const writer = stream.getWriter(); + const digest = stream.digest.catch((error) => error); + stream[dispose](); + stream[dispose](); + await writer.write(""); + await expect(writer.write("a")).rejects.toThrow( + "The DigestStream was disposed.", + ); + expect((await digest).message).toBe("The DigestStream was disposed."); + expect(stream.bytesWritten).toBe(0n); +}); +test("abort keeps the exact reason", async () => { + const stream = new DigestStream("SHA-256"); + const reason = { cancel: true }; + const digest = stream.digest.catch((error) => error); + await stream.abort(reason); + expect(await digest).toBe(reason); +}); +test("empty string preserves a pending surrogate and close flushes a lone lead", async () => { + const stream = new DigestStream("SHA-256", { toWellFormed: true }), + writer = stream.getWriter(); + await writer.write("\ud800"); + await writer.write(""); + expect(stream.bytesWritten).toBe(0n); + await writer.close(); + expect(stream.bytesWritten).toBe(3n); + const expected = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode("\ufffd"), + ); + expect(hex(await stream.digest)).toBe(hex(expected)); + stream[dispose](); + writer.releaseLock(); + await stream.abort("ignored"); + expect(hex(await stream.digest)).toBe(hex(expected)); +}); +test("invalid writes error the writer but leave digest pending until disposal", async () => { + const stream = new DigestStream("SHA-256"), + writer = stream.getWriter(); + let settled = false; + void stream.digest.then( + () => { + settled = true; + }, + () => { + settled = true; + }, + ); + await expect(writer.write({} as never)).rejects.toThrow( + "non-ArrayBuffer/ArrayBufferView/string", + ); + await writer.abort("ignored because writer is already errored"); + await Promise.resolve(); + expect(settled).toBe(false); + stream[dispose](); + await Promise.resolve(); + expect(settled).toBe(true); +}); +test("SAB views are bytes while a bare SAB is rejected", async () => { + const buffer = new SharedArrayBuffer(2); + new Uint8Array(buffer).set([1, 2]); + const stream = new DigestStream("crc32"), + writer = stream.getWriter(); + await writer.write(new Uint8Array(buffer, 2, 0)); + await writer.write(new DataView(buffer)); + await writer.close(); + expect(stream.bytesWritten).toBe(2n); + const invalid = new DigestStream("crc32"); + await expect(invalid.getWriter().write(buffer as never)).rejects.toThrow( + "non-ArrayBuffer/ArrayBufferView/string", + ); + invalid[dispose](); +}); diff --git a/src/compat/crypto/digest-stream.ts b/src/compat/crypto/digest-stream.ts new file mode 100644 index 00000000..7aabf947 --- /dev/null +++ b/src/compat/crypto/digest-stream.ts @@ -0,0 +1,247 @@ +// Workers DigestStream contract, pinned to workerd 5358e3d337336eaf696a48bc1563d808b10feba5. +// Stream state and string conversion follow src/workerd/api/crypto/crypto.c++. +import { WritableStream } from "../stream-web/runtime.js"; +import { dispose } from "../../shared/globals/symbols.js"; +import { viewBytes } from "./validation.js"; +import { NativeDigest } from "./native-crypto.js"; + +type Chunk = ArrayBuffer | ArrayBufferView | string; +interface Context { + update(bytes: Uint8Array): void; + final(): Uint8Array; + dispose(): void; +} +const crcTables = new Map(); +class CrcDigest implements Context { + private value: bigint; + private mask: bigint; + private table: bigint[]; + constructor( + private width: number, + polynomial: bigint, + ) { + this.mask = (1n << BigInt(width)) - 1n; + this.value = this.mask; + let table = crcTables.get(polynomial); + if (!table) { + table = Array.from({ length: 256 }, (_, n) => { + let v = BigInt(n); + for (let bit = 0; bit < 8; bit++) + v = (v >> 1n) ^ (v & 1n ? polynomial : 0n); + return v; + }); + crcTables.set(polynomial, table); + } + this.table = table; + } + update(bytes: Uint8Array) { + for (const byte of bytes) + this.value = + (this.value >> 8n) ^ + this.table[Number((this.value ^ BigInt(byte)) & 255n)]!; + } + final() { + let value = this.value ^ this.mask; + const result = new Uint8Array(this.width / 8); + for (let index = result.length - 1; index >= 0; index--) { + result[index] = Number(value & 255n); + value >>= 8n; + } + return result; + } + dispose() { + this.value = 0n; + } +} +function context(name: string): Context { + name = name.split("\0")[0]!.toLowerCase(); + switch (name) { + case "crc32": + return new CrcDigest(32, 0xedb88320n); + case "crc32c": + return new CrcDigest(32, 0x82f63b78n); + case "crc64nvme": + return new CrcDigest(64, 0x9a6c9329ac4bc9b5n); + case "sha-1": + case "sha-256": + case "sha-384": + case "sha-512": + case "md5": + return new NativeDigest(name.replace("-", "")); + default: + throw new DOMException( + "Unrecognized or unimplemented digest algorithm requested.", + "NotSupportedError", + ); + } +} +function isArrayBuffer(value: unknown): value is ArrayBuffer { + try { + Object.getOwnPropertyDescriptor( + ArrayBuffer.prototype, + "byteLength", + )!.get!.call(value); + return true; + } catch { + return false; + } +} +function encodeWtf8(text: string): Uint8Array { + const output: number[] = []; + for (let i = 0; i < text.length; i++) { + let code = text.charCodeAt(i); + if (code >= 0xd800 && code <= 0xdbff) { + const trail = text.charCodeAt(i + 1); + if (trail >= 0xdc00 && trail <= 0xdfff) { + code = 0x10000 + ((code - 0xd800) << 10) + trail - 0xdc00; + i++; + } + } + if (code < 0x80) output.push(code); + else if (code < 0x800) output.push(0xc0 | (code >> 6), 0x80 | (code & 63)); + else if (code < 0x10000) + output.push( + 0xe0 | (code >> 12), + 0x80 | ((code >> 6) & 63), + 0x80 | (code & 63), + ); + else + output.push( + 0xf0 | (code >> 18), + 0x80 | ((code >> 12) & 63), + 0x80 | ((code >> 6) & 63), + 0x80 | (code & 63), + ); + } + return Uint8Array.from(output); +} +interface State { + context: Context; + status: "ready" | "closed" | "errored"; + reason: unknown; + promise: Promise; + resolve(value: ArrayBuffer): void; + reject(reason: unknown): void; + count: bigint; + pending: string; +} +function fail(state: State, reason: unknown) { + if (state.status !== "ready") return; + state.status = "errored"; + state.reason = reason; + state.context.dispose(); + state.reject(reason); +} +export class DigestStream extends WritableStream { + #state: State; + constructor( + algorithm: string | { name: string }, + options?: { toWellFormed?: boolean }, + ) { + if (algorithm == null) + throw new TypeError( + "Cannot initialize N7workerd3api12SubtleCrypto13HashAlgorithmE with required members from an undefined or null value.", + ); + const name = + typeof algorithm === "object" || typeof algorithm === "function" + ? `${algorithm.name}` + : `${algorithm}`; + if ( + options != null && + typeof options !== "object" && + typeof options !== "function" + ) + throw new TypeError( + "Failed to construct 'DigestStream': constructor parameter 2 is not of type 'Options'.", + ); + const wellFormed = !!options?.toWellFormed; + const native = context(name); + let resolve!: State["resolve"], reject!: State["reject"]; + const promise = new Promise((yes, no) => { + resolve = yes; + reject = no; + }); + void promise.catch(() => {}); + const state: State = { + context: native, + status: "ready", + reason: undefined, + promise, + resolve, + reject, + count: 0n, + pending: "", + }; + const write = (bytes: Uint8Array) => { + if (!bytes.length) return; + if (state.status === "errored") throw state.reason; + if (state.status === "closed") return; + native.update(bytes); + state.count += BigInt(bytes.length); + }; + super({ + write(chunk) { + if (typeof chunk === "string") { + if (!wellFormed) return write(encodeWtf8(chunk)); + let text = state.pending + chunk; + state.pending = ""; + const last = text.charCodeAt(text.length - 1); + if (last >= 0xd800 && last <= 0xdbff) { + state.pending = text.slice(-1); + text = text.slice(0, -1); + } + return write(new TextEncoder().encode(text)); + } + if (!isArrayBuffer(chunk) && !ArrayBuffer.isView(chunk)) + throw new TypeError( + "DigestStream is a byte stream but received an object of non-ArrayBuffer/ArrayBufferView/string type on its writable side.", + ); + let bytes: Uint8Array; + try { + bytes = isArrayBuffer(chunk) + ? new Uint8Array(chunk) + : viewBytes(chunk); + } catch { + return; + } // Detached/out-of-bounds sources have zero extent in Workers. + write(bytes); + }, + close() { + if (state.status === "errored") throw state.reason; + if (state.status === "closed") return; + if (state.pending) { + write(new Uint8Array([0xef, 0xbf, 0xbd])); + state.pending = ""; + } + const result = Uint8Array.from(native.final()).buffer; + native.dispose(); + state.status = "closed"; + state.resolve(result); + }, + abort(reason) { + fail(state, reason); + }, + }); + this.#state = state; + } + get digest() { + if (this == null || typeof this !== "object" || !(#state in this)) + throw new TypeError("Illegal invocation"); + return this.#state.promise; + } + get bytesWritten() { + if (this == null || typeof this !== "object" || !(#state in this)) + throw new TypeError("Illegal invocation"); + return this.#state.count; + } + [dispose]() { + if (this == null || typeof this !== "object" || !(#state in this)) + throw new TypeError("Illegal invocation"); + fail(this.#state, new TypeError("The DigestStream was disposed.")); + } +} +Object.defineProperties(DigestStream.prototype, { + digest: { enumerable: true }, + bytesWritten: { enumerable: true }, + [Symbol.toStringTag]: { value: "DigestStream", configurable: true }, +}); diff --git a/src/compat/crypto/digests.ts b/src/compat/crypto/digests.ts new file mode 100644 index 00000000..e352e10b --- /dev/null +++ b/src/compat/crypto/digests.ts @@ -0,0 +1,36 @@ +export const hashNames = [ + "md4", + "md5", + "sha1", + "sha224", + "sha256", + "sha384", + "sha512", + "md5-sha1", + "RSA-MD5", + "RSA-SHA1", + "RSA-SHA224", + "RSA-SHA256", + "RSA-SHA384", + "RSA-SHA512", + "DSA-SHA", + "DSA-SHA1", + "ecdsa-with-SHA1", +]; +export const aliases: Record = Object.assign( + Object.create(null), + Object.fromEntries( + hashNames.map((name) => [ + name.toLowerCase(), + name + .toLowerCase() + .replace(/^rsa-/, "") + .replace(/^(dsa-sha1?|ecdsa-with-sha1)$/, "sha1"), + ]), + ), +); +export function signatureDigest(algorithm: string): string { + const value = aliases[algorithm.split("\0")[0]!.toLowerCase()]; + if (!value) throw new Error(`Unknown digest: ${algorithm}`); + return value; +} diff --git a/src/compat/crypto/hash-handle.ts b/src/compat/crypto/hash-handle.ts index 7190955a..e6031479 100644 --- a/src/compat/crypto/hash-handle.ts +++ b/src/compat/crypto/hash-handle.ts @@ -2,44 +2,15 @@ import { validateCryptoLength } from "./validation.js"; import { secretMaterial, KeyObject } from "./keys.js"; import { Buffer } from "node:buffer"; import { NativeDigest, NativeHmac } from "./native-crypto.js"; -export const hashNames = [ - "md4", - "md5", - "sha1", - "sha224", - "sha256", - "sha384", - "sha512", - "md5-sha1", - "RSA-MD5", - "RSA-SHA1", - "RSA-SHA224", - "RSA-SHA256", - "RSA-SHA384", - "RSA-SHA512", - "DSA-SHA", - "DSA-SHA1", - "ecdsa-with-SHA1", -]; -export const aliases: Record = Object.assign( - Object.create(null), - Object.fromEntries( - hashNames.map((name) => [ - name.toLowerCase(), - name - .toLowerCase() - .replace(/^rsa-/, "") - .replace(/^(dsa-sha1?|ecdsa-with-sha1)$/, "sha1"), - ]), - ), -); +import { aliases } from "./digests.js"; +export { hashNames, aliases } from "./digests.js"; class HashHandle { private hash: NativeDigest; private cached?: Buffer; constructor(algorithm: string | NativeDigest, length?: number) { if (algorithm instanceof NativeDigest) this.hash = algorithm; else { - const normalized = aliases[algorithm.toLowerCase()]; + const normalized = aliases[algorithm.split("\0")[0]!.toLowerCase()]; if (!normalized) throw new Error("Digest method not supported"); this.hash = new NativeDigest(normalized, "Hash"); } @@ -84,7 +55,7 @@ class HmacHandle { ? new Uint8Array(key.buffer, key.byteOffset, key.byteLength) : new Uint8Array(key); validateCryptoLength(data.byteLength, "key is too long"); - const normalized = aliases[algorithm.toLowerCase()]; + const normalized = aliases[algorithm.split("\0")[0]!.toLowerCase()]; if (!normalized) throw new Error("Digest method not supported"); this.hmac = new NativeHmac(normalized, data); } diff --git a/src/compat/crypto/index.ts b/src/compat/crypto/index.ts index c682ea31..ae321bc9 100644 --- a/src/compat/crypto/index.ts +++ b/src/compat/crypto/index.ts @@ -54,7 +54,8 @@ export { randomBytes, randomFill, randomFillSync }; export const pseudoRandomBytes = randomBytes; import { scrypt, scryptSync } from "./scrypt.js"; export { hkdf, hkdfSync, randomInt, randomUUID, scrypt, scryptSync }; -export const { constants } = original; +import { constants } from "./constants.js"; +export { constants }; import { Cipheriv, Decipheriv, @@ -69,8 +70,8 @@ import { privateDecrypt, } from "./rsa.js"; export { publicEncrypt, privateEncrypt, publicDecrypt, privateDecrypt }; -export const webcrypto = globalThis.crypto; -export const subtle = webcrypto.subtle; +import { webcrypto, subtle } from "./webcrypto.js"; +export { webcrypto, subtle }; export const getRandomValues = webcrypto.getRandomValues.bind(webcrypto); export const CryptoKey = globalThis.CryptoKey; export default { @@ -78,6 +79,7 @@ export default { Certificate, ...originalSupported, ...dh, + constants, pbkdf2, pbkdf2Sync, Hash, diff --git a/src/compat/crypto/keys.ts b/src/compat/crypto/keys.ts index a1e27f05..24f85825 100644 --- a/src/compat/crypto/keys.ts +++ b/src/compat/crypto/keys.ts @@ -1,3 +1,4 @@ +import { validateExportOptions } from "./key-options.js"; import { exportJwk } from "./key-formats.js"; // API behavior follows workerd@5358e3d337336eaf696a48bc1563d808b10feba5 // crypto_keys.ts (Apache-2.0 and Node MIT). Host CryptoKey export is an explicit @@ -53,11 +54,13 @@ export abstract class KeyObject { } export(options: any = {}): any { validateObject(options, "options"); - if (options.format === undefined) options.format = "buffer"; - validateString(options.format, "options.format"); - if (options.type !== undefined) - validateString(options.type, "options.type"); const current = state(this); + validateExportOptions( + options, + current.type, + "options", + current.asymmetric?.type, + ); if (current.asymmetric) { if (options.format === "jwk") return exportJwk(current.asymmetric, current.type === "private"); diff --git a/src/compat/crypto/native-crypto.build.json b/src/compat/crypto/native-crypto.build.json index b186dda4..169a2d37 100644 --- a/src/compat/crypto/native-crypto.build.json +++ b/src/compat/crypto/native-crypto.build.json @@ -35,6 +35,6 @@ "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" ], "libraries": ["libssl.a", "libcrypto.a"], - "bindingSha256": "92baac21ba01df72107bcfaa1d2520a672f7677975cef4d3c3b09835a1c11e3e", - "sha256": "a29aa2dc870b72ff2268a0d57958d94460654e19cdbbd54e3c9fe8f92dc4532c" + "bindingSha256": "d79c0264ee2d96b41a255ee119a5ba8e973be6f9f5dc3ea6d404226c840308d5", + "sha256": "f877b01e7fc19c0d9dfc116c9cd579faeef7c65960d9ef5aaafde01c17d5cfda" } diff --git a/src/compat/crypto/native-crypto.c b/src/compat/crypto/native-crypto.c index 2d8bb165..ad8377c0 100644 --- a/src/compat/crypto/native-crypto.c +++ b/src/compat/crypto/native-crypto.c @@ -122,7 +122,7 @@ API int wk_verify(EVP_PKEY *key,const char *digest,const unsigned char *data,siz int length=i2d_ECDSA_SIG(value,&converted);ECDSA_SIG_free(value);if(length<=0){EVP_MD_CTX_free(ctx);return -1;} signature=converted;signature_length=(size_t)length; } - int result=ok?EVP_DigestVerify(ctx,signature,signature_length,data,length):-1;OPENSSL_free(converted);EVP_MD_CTX_free(ctx);return result; + int result=ok?(EVP_DigestVerify(ctx,signature,signature_length,data,length)==1?1:0):-1;OPENSSL_free(converted);EVP_MD_CTX_free(ctx);return result; } API int wk_derive(EVP_PKEY *key,EVP_PKEY *peer) { @@ -193,7 +193,7 @@ API int wk_verify_hash(EVP_PKEY *key,const char *digest,const unsigned char *dat int ok=EVP_PKEY_verify_init(ctx)>0 && EVP_PKEY_CTX_set_signature_md(ctx,EVP_get_digestbyname(digest))>0; if(ok && padding)ok=EVP_PKEY_CTX_set_rsa_padding(ctx,padding)>0; if(ok && padding==RSA_PKCS1_PSS_PADDING)ok=EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx,saltlen)>0; - int result=ok?EVP_PKEY_verify(ctx,signature,signature_length,data,length):-1;EVP_PKEY_CTX_free(ctx);return result; + int result=ok?(EVP_PKEY_verify(ctx,signature,signature_length,data,length)==1?1:0):-1;EVP_PKEY_CTX_free(ctx);return result; } API EC_KEY *wk_ec_new(const char *curve) {return EC_KEY_new_by_curve_name(OBJ_sn2nid(curve));} API void wk_ec_free(EC_KEY *key) {EC_KEY_free(key);} diff --git a/src/compat/crypto/native-crypto.ts b/src/compat/crypto/native-crypto.ts index 0056d3bd..d0c62be2 100644 --- a/src/compat/crypto/native-crypto.ts +++ b/src/compat/crypto/native-crypto.ts @@ -430,6 +430,10 @@ export class NativeDigest { return new NativeDigest("", this.context, pointer); } + assertActive() { + if (!this.pointer) + throw new Error(`${this.context} context has already been finalized`); + } update(data: Uint8Array) { if (!this.pointer) throw new Error(`${this.context} context has already been finalized`); @@ -458,6 +462,7 @@ const ecReleased = new FinalizationRegistry((pointer) => export class NativeECDH { private pointer: number; constructor(curve: string) { + curve = curve.split("\0")[0]!; if (!["secp224r1", "prime256v1", "secp384r1", "secp521r1"].includes(curve)) throw new Error("Invalid curve"); this.pointer = inputs([curve], (api, [p]) => api.wk_ec_new(p)); diff --git a/src/compat/crypto/native-crypto.wasm b/src/compat/crypto/native-crypto.wasm index 4a90a25a027b4f0dd82a62e0c5219b51448418e7..f1415629ad4d248f824773c4a12bdfd1a71780a5 100755 GIT binary patch delta 185 zcmWm1yAHun9KhlHwJufnQq-+H0}o(1yI7ss19Ui>NDLyeh_Q*)DmjDAyI4Gd#3b=E ze8cxS9OaAKRLRrM)Wet5z88r&b5VA^+<0<1zgDMzcSi4x7G&VSMHV^aQGkace3Vc| h1y$5gM*{(xXdy%!9dr?)hdu@vVuTpuyU~;F^#>w)L{b0% delta 177 zcmWN=ISRr+7=TfJ_bl#-8n>ut&?}@DTjv64Q%?{qM6d~I1P@> operate(0, ...args), @@ -57,7 +88,7 @@ function getPaddingAndHash(options: HashOptions): PublicPrivateCipherOptions { } export function privateEncrypt( - privateKey: CreateAsymmetricKeyOptions | KeyObject | CryptoKey | undefined, + privateKey: CreateAsymmetricKeyOptions | KeyData | undefined, buffer: string | ArrayBufferView | ArrayBuffer | undefined, ): Buffer { if (privateKey === undefined) { diff --git a/src/compat/crypto/sign-handle.ts b/src/compat/crypto/sign-handle.ts index 5d1bbfe3..85f26418 100644 --- a/src/compat/crypto/sign-handle.ts +++ b/src/compat/crypto/sign-handle.ts @@ -1,3 +1,5 @@ +import { isAnyBuffer } from "./validation.js"; +import { signatureDigest } from "./digests.js"; import { Buffer } from "node:buffer"; import { NativeDigest } from "./native-crypto.js"; import { keyStates } from "./key-state.js"; @@ -47,10 +49,31 @@ function rawSignature(data: Uint8Array, width: number): Buffer { } return output; } -class SignHandle { +function rsaParameters( + type: string, + padding: number | undefined, + saltLength: number | undefined, + verifying: boolean, +) { + if (type !== "rsa" && type !== "rsa-pss") return; + const mode = padding ?? (type === "rsa-pss" ? 6 : 1); + if ( + ![1, 3, 6].includes(mode) || + (mode === 6 && saltLength !== undefined && saltLength < -2) + ) + throw new Error("Failed to set RSA parameters for signature"); + if (mode === 3) + throw new Error( + verifying + ? "Failed to set digest context for signature verification" + : "Failed to set signature digest", + ); +} +export class SignHandle { private hash: NativeDigest; constructor(private algorithm: string) { - this.hash = new NativeDigest(algorithm); + this.algorithm = signatureDigest(algorithm); + this.hash = new NativeDigest(this.algorithm); } update(data: Uint8Array) { this.hash.update( @@ -58,22 +81,37 @@ class SignHandle { ); } sign(value: any, padding?: number, saltLength?: number, encoding?: number) { + this.hash.assertActive(); const native = key(value), + hash = this.hash.final(); + if (native.type === "dsa") + throw new Error("Signing with DSA keys is not currently supported"); + if (native.type === "ed25519") + throw new Error("Failed to set signature digest"); + if (native.type === "x25519") + throw new Error("Failed to initialize signing context"); + rsaParameters(native.type, padding, saltLength, false); + let signed: Uint8Array; + try { signed = native.signHash( this.algorithm, - this.hash.final(), - padding, - saltLength, + hash, + native.type.startsWith("rsa") ? padding : undefined, + saltLength ?? -1, ); + } catch { + throw new Error("Failed to generate signature"); + } return encoding === 1 && native.type === "ec" ? rawSignature(signed, Math.ceil(native.bits / 8)) : signed; } } -class VerifyHandle { +export class VerifyHandle { private hash: NativeDigest; constructor(private algorithm: string) { - this.hash = new NativeDigest(algorithm, "Verification"); + this.algorithm = signatureDigest(algorithm); + this.hash = new NativeDigest(this.algorithm, "Verification"); } update(data: Uint8Array) { this.hash.update( @@ -82,28 +120,47 @@ class VerifyHandle { } verify( value: any, - signature: Uint8Array, + signature: ArrayBuffer | ArrayBufferView, padding?: number, saltLength?: number, encoding?: number, ) { - const native = key(value), - hash = this.hash.final(); + if (!isAnyBuffer(signature) && !ArrayBuffer.isView(signature)) + throw new TypeError( + "Failed to execute 'verify' on 'VerifyHandle': parameter 2 is not of type 'BufferSource'.", + ); + this.hash.assertActive(); + const native = key(value); + if (native.type === "ed25519" || native.type === "ed448") + throw new Error("Unsupported operation for this key"); + let signatureBytes = isAnyBuffer(signature) + ? Buffer.from(signature) + : Buffer.from( + signature.buffer, + signature.byteOffset, + signature.byteLength, + ); + const hash = this.hash.final(); + if (native.type === "dsa") + throw new Error("Verifying with DSA keys is not currently supported"); + if (native.type === "x25519") + throw new Error("Failed to initialize key for verification"); + rsaParameters(native.type, padding, saltLength, true); if (encoding === 1 && native.type === "ec") { const width = Math.ceil(native.bits / 8); - if (signature.length !== width * 2) return false; - signature = der( + if (signatureBytes.length !== width * 2) return false; + signatureBytes = der( 48, - integer(signature.subarray(0, width)), - integer(signature.subarray(width)), + integer(signatureBytes.subarray(0, width)), + integer(signatureBytes.subarray(width)), ); } return native.verifyHash( this.algorithm, hash, - signature, - padding, - saltLength, + signatureBytes, + native.type.startsWith("rsa") ? padding : undefined, + saltLength ?? -1, ); } } diff --git a/src/compat/crypto/signatures.spec.ts b/src/compat/crypto/signatures.spec.ts new file mode 100644 index 00000000..5f8c13c7 --- /dev/null +++ b/src/compat/crypto/signatures.spec.ts @@ -0,0 +1,35 @@ +import { expect, expectTypeOf, test } from "vitest"; +import { Buffer } from "node:buffer"; +import { + createSign, + createVerify, + sign, + verify, + generateKeyPairSync, +} from "./index.js"; + +test("signature public types preserve binary and encoded results", () => { + const pair = generateKeyPairSync("ec", { namedCurve: "prime256v1" }); + const data = new Uint8Array([1, 2]); + const signature = createSign("sha256") + .update(data) + .sign(pair.privateKey, "buffer"); + expectTypeOf(signature).toEqualTypeOf(); + expectTypeOf( + createSign("sha256").update(data).sign(pair.privateKey, "hex"), + ).toEqualTypeOf(); + expect( + createVerify("sha256").update(data).verify(pair.publicKey, signature), + ).toBe(true); + expectTypeOf(sign("sha256", data, pair.privateKey)).toEqualTypeOf(); + expectTypeOf( + verify("sha256", data, pair.publicKey, signature), + ).toEqualTypeOf(); + const invalid = () => { + // @ts-expect-error Workers requires an ArrayBufferView for one-shot data. + sign("sha256", new ArrayBuffer(1), pair.privateKey); + // @ts-expect-error One-shot signatures cannot be encoded strings. + verify("sha256", data, pair.publicKey, "abc"); + }; + void invalid; +}); diff --git a/src/compat/crypto/signatures.ts b/src/compat/crypto/signatures.ts index 631268b1..10b827fe 100644 --- a/src/compat/crypto/signatures.ts +++ b/src/compat/crypto/signatures.ts @@ -32,7 +32,6 @@ import { default as cryptoImpl, type SignHandle, type VerifyHandle, - type CreateAsymmetricKeyOptions, } from "./sign-handle.js"; import { validateString } from "../net/validators.js"; @@ -49,19 +48,25 @@ import { } from "./sign-errors.js"; import { Writable } from "../stream/writable.js"; +import type { Writable as WritableType, WritableOptions } from "node:stream"; +import type { RsaKeyOptions as CreateAsymmetricKeyOptions } from "./rsa.js"; import { isArrayBufferView } from "../util/types/index.js"; -export interface SignOptions {} - -export interface Sign extends Writable { +export interface SignOptions extends WritableOptions {} +export interface Sign extends WritableType { [kHandle]: SignHandle; - update(data: any): void; - sign(privateKey: any): any; + update(data: string | ArrayBufferView, encoding?: BufferEncoding): this; + sign(privateKey: any, encoding?: undefined | "buffer"): Buffer; + sign(privateKey: any, encoding: BufferEncoding): string; } -export interface Verify extends Writable { +export interface Verify extends WritableType { [kHandle]: VerifyHandle; - update(data: any): void; - verify(publicKey: any, signature: any): any; + update(data: string | ArrayBufferView, encoding?: BufferEncoding): this; + verify( + publicKey: any, + signature: string | ArrayBuffer | SharedArrayBuffer | ArrayBufferView, + encoding?: BufferEncoding, + ): boolean; } const kHandle = Symbol("kHandle"); @@ -75,6 +80,7 @@ export const Sign = function (this: Sign, algorithm: string, options: any) { return this; } as any as { new (algorithm: string, options?: SignOptions): Sign; + (algorithm: string, options?: SignOptions): Sign; }; Object.setPrototypeOf(Sign.prototype, Writable.prototype); Object.setPrototypeOf(Sign, Writable); @@ -191,6 +197,7 @@ export const Verify = function (this: Verify, algorithm: string, options: any) { return this; } as any as { new (algorithm: string, options?: SignOptions): Verify; + (algorithm: string, options?: SignOptions): Verify; }; Object.setPrototypeOf(Verify.prototype, Writable.prototype); Object.setPrototypeOf(Verify, Writable); @@ -271,11 +278,11 @@ Verify.prototype.verify = function ( ); }; -export function createSign(algorithm: string, options: any) { +export function createSign(algorithm: string, options?: SignOptions) { return new Sign(algorithm, options); } -export function createVerify(algorithm: string, options: any) { +export function createVerify(algorithm: string, options?: SignOptions) { return new Verify(algorithm, options); } @@ -284,7 +291,18 @@ type VerifyCallback = (err: any, valid?: boolean) => void; export function sign( algorithm: string | null | undefined, - data: BufferSource, + data: ArrayBufferView, + options: any, +): Buffer; +export function sign( + algorithm: string | null | undefined, + data: ArrayBufferView, + options: any, + callback: SignCallback, +): void; +export function sign( + algorithm: string | null | undefined, + data: ArrayBufferView, options: any, callback?: SignCallback, ): Buffer | void { @@ -346,9 +364,22 @@ export function sign( export function verify( algorithm: string | null | undefined, - data: BufferSource, + data: ArrayBufferView, + options: any, + signature: ArrayBufferView, +): boolean; +export function verify( + algorithm: string | null | undefined, + data: ArrayBufferView, + options: any, + signature: ArrayBufferView, + callback: VerifyCallback, +): void; +export function verify( + algorithm: string | null | undefined, + data: ArrayBufferView, options: any, - signature: BufferSource, + signature: ArrayBufferView, callback?: VerifyCallback, ): boolean | undefined { // Node.js allows the algorithm to be either undefined or null, in which diff --git a/src/compat/crypto/validation.ts b/src/compat/crypto/validation.ts index 829acb73..84213b24 100644 --- a/src/compat/crypto/validation.ts +++ b/src/compat/crypto/validation.ts @@ -34,8 +34,7 @@ export function isAnyBuffer(value: unknown): value is ArrayBuffer { export function bytes(value: unknown, name: string): Buffer { if (typeof value === "string") return Buffer.from(value); if (isAnyBuffer(value)) return Buffer.from(value); - if (ArrayBuffer.isView(value)) - return Buffer.from(value.buffer, value.byteOffset, value.byteLength); + if (ArrayBuffer.isView(value)) return Buffer.from(viewBytes(value)); throw new ERR_INVALID_ARG_TYPE( name, ["string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], @@ -47,3 +46,56 @@ export function bytes(value: unknown, name: string): Buffer { export function validateCryptoLength(length: number, message: string): void { if (length > 0x7fffffff) throw new RangeError(message); } + +const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype); +const typedArrayBuffer = Object.getOwnPropertyDescriptor( + typedArrayPrototype, + "buffer", +)!.get!; +const typedArrayOffset = Object.getOwnPropertyDescriptor( + typedArrayPrototype, + "byteOffset", +)!.get!; +const typedArrayLength = Object.getOwnPropertyDescriptor( + typedArrayPrototype, + "byteLength", +)!.get!; +const dataViewBuffer = Object.getOwnPropertyDescriptor( + DataView.prototype, + "buffer", +)!.get!; +const dataViewOffset = Object.getOwnPropertyDescriptor( + DataView.prototype, + "byteOffset", +)!.get!; +const dataViewLength = Object.getOwnPropertyDescriptor( + DataView.prototype, + "byteLength", +)!.get!; +/** Read the intrinsic view extent, ignoring shadowed user properties. */ +export function viewBytes(value: ArrayBufferView): Uint8Array { + let buffer: ArrayBufferLike; + try { + buffer = typedArrayBuffer.call(value); + } catch { + buffer = dataViewBuffer.call(value); + try { + return new Uint8Array( + buffer, + dataViewOffset.call(value), + dataViewLength.call(value), + ); + } catch { + return new Uint8Array(0); + } + } + try { + return new Uint8Array( + buffer, + typedArrayOffset.call(value), + typedArrayLength.call(value), + ); + } catch { + return new Uint8Array(0); + } +} diff --git a/src/compat/crypto/webcrypto.ts b/src/compat/crypto/webcrypto.ts new file mode 100644 index 00000000..1630413b --- /dev/null +++ b/src/compat/crypto/webcrypto.ts @@ -0,0 +1,68 @@ +import { installResourceSymbols } from "../../shared/globals/symbols.js"; +import { viewBytes } from "./validation.js"; +import { DigestStream } from "./digest-stream.js"; +import { nativeTimingEqual } from "./native-crypto.js"; +import { Buffer } from "node:buffer"; + +type WorkersSubtleCrypto = SubtleCrypto & { + timingSafeEqual( + left: ArrayBuffer | ArrayBufferView, + right: ArrayBuffer | ArrayBufferView, + ): boolean; +}; +type WorkersCrypto = Crypto & { + DigestStream: typeof DigestStream; + subtle: WorkersSubtleCrypto; +}; +installResourceSymbols(globalThis.Symbol); +export const webcrypto = globalThis.crypto as WorkersCrypto; +export const subtle = webcrypto.subtle; +function source(value: ArrayBuffer | ArrayBufferView, parameter: number) { + if (ArrayBuffer.isView(value)) return viewBytes(value); + try { + Object.getOwnPropertyDescriptor( + ArrayBuffer.prototype, + "byteLength", + )!.get!.call(value); + try { + return Buffer.from(value as ArrayBuffer); + } catch { + return new Uint8Array(0); + } + } catch { + throw new TypeError( + `Failed to execute 'timingSafeEqual' on 'SubtleCrypto': parameter ${parameter} is not of type 'BufferSource'.`, + ); + } +} +function timingSafeEqual( + this: SubtleCrypto, + left: ArrayBuffer | ArrayBufferView, + right: ArrayBuffer | ArrayBufferView, +) { + if (this !== subtle) + throw new TypeError( + "Illegal invocation: function called with incorrect `this` reference. See https://developers.cloudflare.com/workers/observability/errors/#illegal-invocation-errors for details.", + ); + const a = source(left, 1), + b = source(right, 2); + if (a.byteLength !== b.byteLength) + throw new TypeError("Input buffers must have the same byte length."); + return nativeTimingEqual(a, b); +} +// Preserve host Crypto/SubtleCrypto identity and native WebCrypto operations. +// These Workers extensions use the owned digest and comparison backend. +if (!("DigestStream" in webcrypto)) + Object.defineProperty(Object.getPrototypeOf(webcrypto), "DigestStream", { + value: DigestStream, + writable: true, + enumerable: true, + configurable: true, + }); +if (!("timingSafeEqual" in subtle)) + Object.defineProperty(Object.getPrototypeOf(subtle), "timingSafeEqual", { + value: timingSafeEqual, + writable: true, + enumerable: true, + configurable: true, + }); diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index a98c4312..fcc9c793 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -18,6 +18,12 @@ Additional profiles cover process resource/permission/report stubs, its pinned wall-clock hrtime behavior, and TLS hostname/ALPN/options contracts. Native runs also test real TLS exchange and hostname mismatch rejection. +Custom Duplex wrapping follows Workers' stream behavior, including byte +forwarding, EOF and error cleanup. This path does not perform a TLS handshake or +encrypt the custom Duplex's bytes. Authenticated TLS/STARTTLS requires the native +socket transport; wrapping an arbitrary stream does not provide that security. +The custom byte-stream adapter uses owned BYOB streams on both WebViews. + The SDK starts a fresh /tmp volume for each local fetch dispatch. Handles opened inside that request retain their volume; embedded bundle assets stay read-only. Standalone imports outside SDK dispatch share a fallback volume. Copying embedded files to /tmp preserves immutable embedded bytes. Permission @@ -57,6 +63,23 @@ Where absent, its input brand is unverified; equivalent half-float bytes are still tested as a byte-array input. This does not certify complete crypto input coverage on that engine. +`crypto.webcrypto.DigestStream` uses the owned incremental digest backend for +SHA-1/256/384/512 and MD5, plus CRC32/CRC32C/CRC64NVME. Its string input follows +Workers' WTF-8 behavior by default; `toWellFormed` carries a trailing high +surrogate across string writes. Digest promises, byte counts, abort and disposal +follow the pinned runtime. `crypto.subtle.timingSafeEqual` uses the Wasm native +comparison primitive and intrinsic BufferSource extents. These extensions retain +the host Crypto/SubtleCrypto objects and their existing WebCrypto operations. +The tests are `src/compat/crypto/digest-stream.spec.ts` and the shared +`cryptoWebExtensions` case. + +Signature digest aliases, constant descriptors, binary signature views and +failure states are compared with Workers. Streaming RSA padding/salt options +apply to the streaming signature API; the pinned one-shot implementation +validates their frontend types but does not apply them. This distinction is +intentional. The strict consumer declarations and parameter matrix cover both +forms; they do not certify every key import/export option. + Random bytes and fills use the pinned Workers frontend and host cryptographic entropy. `pseudoRandomBytes` is the same function as `randomBytes`. Sync fills return the selected subview; callback fills return the original input. Workers' @@ -136,3 +159,8 @@ Workers reports `fips=true` because of its own crypto backend. The WebView's JavaScript implementations do not establish FIPS compliance, so that flag is not copied merely to match the export inventory. `setFips` and `setEngine` retain Workers' unsupported-operation behavior. + +For focused diagnosis, set `NODE_COMPAT_CASES` to a comma-separated list when +running `expansion-contracts.probe.mjs`. Focused output goes to +`demo/artifacts/node-inventory/targeted/` and does not replace the full baseline +used by native acceptance. Omit this variable for the required complete run. diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index 710ae5cc..c572913c 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,458 @@ export const eventCases = { + tlsCustomDuplexStates: async function ({ tls: t, classicStreams: s }) { + const out = {}; + const tick = () => new Promise((r) => setTimeout(r, 15)); + for (const mode of [ + "eof", + "writeError", + "parentObserved", + "directError", + "destroy", + "object", + "encoding", + ]) { + const events = []; + const custom = new s.Duplex({ + allowHalfOpen: true, + objectMode: mode === "object", + read() {}, + write(c, e, cb) { + cb( + ["writeError", "parentObserved"].includes(mode) + ? new Error("write fail") + : undefined, + ); + }, + }); + custom.on("error", (e) => events.push("customError:" + e.message)); + custom.on("close", () => events.push("customClose")); + if (mode === "encoding") custom.setEncoding("utf8"); + let wrapped; + try { + wrapped = new t.TLSSocket(custom); + custom.on("error", (e) => events.push("lateCustomError:" + e.message)); + if (mode === "parentObserved") + wrapped._parentWrap.on("error", (e) => + events.push("parentError:" + e.message), + ); + wrapped.on("error", (e) => events.push("error:" + e.message)); + wrapped.on("close", () => events.push("close")); + wrapped.on("end", () => events.push("end")); + wrapped.on("data", (c) => events.push("data:" + String(c))); + await tick(); + if (mode === "eof") custom.push(null); + if (["writeError", "parentObserved"].includes(mode)) + wrapped.write("x", (e) => events.push("write:" + e?.message)); + if (mode === "directError") custom.emit("error", new Error("manual")); + if (mode === "destroy") wrapped.destroy(); + if (mode === "object") custom.push({ x: 1 }); + if (mode === "encoding") custom.push("abc"); + await tick(); + out[mode] = { + events: [...events], + destroyed: wrapped.destroyed, + customDestroyed: custom.destroyed, + }; + } catch (e) { + out[mode] = { name: e.name, message: e.message, events: [...events] }; + } finally { + wrapped?.destroy(); + custom.destroy(); + await tick(); + } + } + return out; + }, + tlsCustomDuplex: async function ({ tls, classicStreams }) { + const writes = []; + const custom = new classicStreams.Duplex({ + read() {}, + write(chunk, encoding, callback) { + writes.push(Array.from(chunk)); + callback(); + }, + }); + const wrapped = new tls.TLSSocket(custom); + wrapped.on("error", () => {}); + const read = new Promise((resolve) => + wrapped.once("data", (chunk) => resolve(Array.from(chunk))), + ); + await new Promise((resolve) => setTimeout(resolve, 10)); + await new Promise((resolve, reject) => + wrapped.write(new Uint8Array([1, 2]), (error) => + error ? reject(error) : resolve(), + ), + ); + custom.push(new Uint8Array([3, 4])); + const data = await read; + const result = { + writes, + data, + encrypted: wrapped.encrypted, + authorized: wrapped.authorized, + secureConnecting: wrapped.secureConnecting, + }; + wrapped.destroy(); + custom.destroy(); + return result; + }, + cryptoWebExtensions: async function ({ crypto }) { + const attempt = async (fn) => { + try { + return await fn(); + } catch (e) { + return { name: e.name, message: e.message, code: e.code ?? null }; + } + }; + const result = {}; + for (const algorithm of [ + "SHA-1", + "SHA-256", + "SHA-384", + "SHA-512", + "MD5", + "crc32", + "crc32c", + "crc64nvme", + "sha-256", + "SHA-256\0suffix", + "sha256", + "MD4", + null, + {}, + 42, + ]) + result[String(algorithm)] = await attempt(async () => { + const d = new crypto.webcrypto.DigestStream(algorithm), + w = d.getWriter(); + await w.write("1234"); + await w.write(new Uint8Array([53, 54, 55, 56, 57])); + await w.close(); + return { + digest: Array.from(new Uint8Array(await d.digest)), + count: String(d.bytesWritten), + }; + }); + for (const well of [false, true]) + for (const chunks of [ + ["\ud800"], + ["\ud800", "\udc00"], + ["\ud800", new Uint8Array([65]), "\udc00"], + ]) + result[well + ":" + JSON.stringify(chunks)] = await attempt( + async () => { + const d = new crypto.webcrypto.DigestStream("SHA-256", { + toWellFormed: well, + }), + w = d.getWriter(), + counts = []; + for (const chunk of chunks) { + await w.write(chunk); + counts.push(String(d.bytesWritten)); + } + await w.close(); + return { + counts, + final: String(d.bytesWritten), + digest: Array.from(new Uint8Array(await d.digest)), + }; + }, + ); + const d = new crypto.webcrypto.DigestStream("SHA-256"); + const reason = { abort: true }, + rejected = d.digest.catch((e) => e); + await d.abort(reason); + result.abort = (await rejected) === reason; + const disposed = new crypto.webcrypto.DigestStream("SHA-256"), + writer = disposed.getWriter(); + const disposal = disposed.digest.catch((e) => ({ + name: e.name, + message: e.message, + })); + disposed[Symbol.dispose](); + disposed[Symbol.dispose](); + await writer.write(""); + result.disposed = await disposal; + result.afterDispose = await attempt(() => writer.write("a")); + for (const [i, value] of [ + null, + {}, + "a", + new ArrayBuffer(1), + new Uint8Array(1), + new DataView(new ArrayBuffer(1)), + ].entries()) + result["timing" + i] = await attempt(() => + crypto.subtle.timingSafeEqual(value, new Uint8Array(1)), + ); + result.timingLength = await attempt(() => + crypto.subtle.timingSafeEqual(new Uint8Array(1), new Uint8Array(2)), + ); + result.timingDifferent = crypto.subtle.timingSafeEqual( + new Uint8Array([1]), + new Uint8Array([2]), + ); + const named = function () {}; + Object.defineProperty(named, "name", { value: "SHA-256" }); + result.functionAlgorithm = await attempt(async () => { + const d = new crypto.webcrypto.DigestStream(named); + await d.getWriter().close(); + return Array.from(new Uint8Array(await d.digest)); + }); + for (const view of [ + new Uint8Array([1]), + new DataView(new Uint8Array([1]).buffer), + ]) { + Object.defineProperties(view, { + buffer: { value: new Uint8Array([9]).buffer }, + byteOffset: { value: 0 }, + byteLength: { value: 1 }, + }); + result["shadowCompare:" + view.constructor.name] = + crypto.subtle.timingSafeEqual(view, new Uint8Array([1])); + const d = new crypto.webcrypto.DigestStream("crc32"), + w = d.getWriter(); + await w.write(view); + await w.close(); + result["shadowDigest:" + view.constructor.name] = Array.from( + new Uint8Array(await d.digest), + ); + } + const detached = new ArrayBuffer(1), + detachedView = new DataView(detached); + structuredClone(detached, { transfer: [detached] }); + result.detached = [ + crypto.subtle.timingSafeEqual(detached, new ArrayBuffer(0)), + crypto.subtle.timingSafeEqual(detachedView, new ArrayBuffer(0)), + ]; + return result; + }, + cryptoSignatureParameters: function ({ crypto }) { + const attempt = (fn) => { + try { + return fn(); + } catch (e) { + return { name: e.name, message: e.message, code: e.code ?? null }; + } + }; + const data = new Uint8Array([1, 2]); + const rsa = crypto.generateKeyPairSync("rsa", { modulusLength: 1024 }); + const privatePem = rsa.privateKey.export({ format: "pem", type: "pkcs8" }); + const publicPem = rsa.publicKey.export({ format: "pem", type: "spki" }); + const signature = crypto.sign("sha256", data, rsa.privateKey); + const results = []; + for (const padding of [-1, 0, 1, 2, 3, 4, 5, 6, 99]) + for (const saltLength of [undefined, -3, -2, -1, 0, 32, 95]) { + const options = { padding, saltLength }; + results.push([ + attempt( + () => + crypto + .createSign("sha256") + .update(data) + .sign({ key: privatePem, ...options }).length, + ), + attempt(() => + crypto + .createVerify("sha256") + .update(data) + .verify({ key: publicPem, ...options }, signature), + ), + attempt( + () => + crypto.sign("sha256", data, { key: privatePem, ...options }) + .length, + ), + attempt(() => + crypto.verify( + "sha256", + data, + { key: publicPem, ...options }, + signature, + ), + ), + ]); + } + const ec = crypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" }); + const ecPrivate = ec.privateKey.export({ format: "pem", type: "pkcs8" }), + ecPublic = ec.publicKey.export({ format: "pem", type: "spki" }); + for (const padding of [undefined, -1, 3, 99]) + results.push([ + attempt( + () => + crypto + .createSign("sha256") + .update(data) + .sign({ key: ecPrivate, padding }).length > 0, + ), + attempt(() => + crypto + .createVerify("sha256") + .update(data) + .verify({ key: ecPublic, padding }, new Uint8Array([1])), + ), + ]); + for (const signature of [ + null, + {}, + [], + new ArrayBuffer(1), + new DataView(new ArrayBuffer(1)), + ]) + results.push( + attempt(() => + crypto + .createVerify("sha256") + .update(data) + .verify(ec.publicKey, signature), + ), + ); + for (const type of ["ed25519", "x25519"]) { + const pair = crypto.generateKeyPairSync(type); + const signer = crypto.createSign("sha256").update(data); + results.push( + attempt(() => signer.sign(pair.privateKey)), + attempt(() => { + signer.update(data); + return true; + }), + ); + results.push( + attempt(() => crypto.sign("sha256", data, pair.privateKey)), + attempt(() => + crypto.verify("sha256", data, pair.publicKey, new Uint8Array(64)), + ), + ); + } + return results; + }, + cryptoSignatureInventory: function ({ crypto }) { + const attempt = (fn) => { + try { + return fn(); + } catch (e) { + return { name: e.name, message: e.message, code: e.code ?? null }; + } + }; + const result = {}; + result.constants = { + nullPrototype: Object.getPrototypeOf(crypto.constants) === null, + descriptors: Object.getOwnPropertyDescriptors(crypto.constants), + }; + for (const algorithm of [ + "sha3-256", + "ripemd160", + "blake2b512", + "DSA-SHA", + "sha256", + "SHA256", + "sha256\0suffix", + ]) { + for (const operation of ["createSign", "createVerify"]) + result[operation + ":" + algorithm] = attempt(() => { + crypto[operation](algorithm); + return true; + }); + } + const pair = crypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" }); + const data = new Uint8Array([1, 2, 3]); + const signature = crypto.sign("sha256", data, { + key: pair.privateKey.export({ format: "pem", type: "pkcs8" }), + dsaEncoding: "ieee-p1363", + }); + for (const view of [ + signature, + new DataView( + signature.buffer, + signature.byteOffset, + signature.byteLength, + ), + new Uint16Array( + signature.buffer, + signature.byteOffset, + signature.byteLength / 2, + ), + ]) { + result[ + "verify:" + (view === signature ? "Buffer" : view.constructor.name) + ] = attempt(() => + crypto + .createVerify("sha256") + .update(data) + .verify( + { + key: pair.publicKey.export({ format: "pem", type: "spki" }), + dsaEncoding: "ieee-p1363", + }, + view, + ), + ); + } + for (const algorithm of ["sha3-256", "DSA-SHA", "sha256\0suffix"]) + result["oneShot:" + algorithm] = attempt( + () => crypto.sign(algorithm, data, pair.privateKey).length > 0, + ); + const ed = crypto.generateKeyPairSync("ed25519"); + const verifier = crypto.createVerify("sha256").update(data); + result.ed = attempt(() => + verifier.verify(ed.publicKey, new Uint8Array(64)), + ); + result.afterEd = attempt(() => { + verifier.update(data); + return true; + }); + for (const type of ["pkcs1", "sec1"]) + result["export:" + type] = attempt( + () => pair.privateKey.export({ format: "der", type }).length, + ); + result.nulCurve = attempt(() => { + crypto.createECDH("prime256v1\0suffix"); + return true; + }); + return result; + }, + fsReadLinesInventory: async function ({ fs }) { + const path = "/tmp/read-lines-inventory"; + const attempt = (fn) => { + try { + fn(); + return "accepted"; + } catch (e) { + return { name: e.name, message: e.message, code: e.code ?? null }; + } + }; + await fs.promises.writeFile(path, "a\nb\n"); + const handle = await fs.promises.open(path, "r"); + try { + const open = attempt(() => handle.readLines()); + await handle.close(); + return { open, closed: attempt(() => handle.readLines()) }; + } finally { + await handle.close(); + await fs.promises.unlink(path); + } + }, + zlibCrcSeedInventory: function ({ zlib }) { + return [ + -1, + 4294967296, + 1.5, + NaN, + Infinity, + "1", + null, + undefined, + 0, + 4294967295, + ].map((value) => { + try { + return zlib.crc32(new Uint8Array([1, 2]), value); + } catch (e) { + return { name: e.name, message: e.message, code: e.code ?? null }; + } + }); + }, + cryptoDhContracts: function ({ crypto, withRequestScope = (fn) => fn() }) { return withRequestScope(() => { const attempt = (fn) => { diff --git a/src/compat/fs/index.ts b/src/compat/fs/index.ts index 3cb5df60..85ca18c1 100644 --- a/src/compat/fs/index.ts +++ b/src/compat/fs/index.ts @@ -361,6 +361,19 @@ promises.open = async (...args: any[]) => { fileMode(args[2], 0o666); args[2] = 0o666; const handle = await openPromise(...args); + const prototype = Object.getPrototypeOf(handle); + if (!Object.hasOwn(prototype, "readLines")) + Object.defineProperty(prototype, "readLines", { + value: function readLines(this: any) { + if (this.fd === -1 || this.fd === undefined) + throw Object.assign(new Error("Bad file descriptor"), { + code: "EBADF", + }); + throw new Error("not implemented"); + }, + writable: true, + configurable: true, + }); const closeHandle = handle.close.bind(handle); handle.close = () => { if (handle.fd === -1) { diff --git a/src/compat/https/index.ts b/src/compat/https/index.ts index 52328f08..b635a623 100644 --- a/src/compat/https/index.ts +++ b/src/compat/https/index.ts @@ -13,7 +13,8 @@ type ClientRequest = InstanceType; import { Agent, globalAgent } from "./agent.js"; import { ERR_METHOD_NOT_IMPLEMENTED } from "./errors.js"; import { Server } from "./server.js"; -import type { IncomingMessageCallback } from "node-internal:internal_http_util"; +import type { IncomingMessage } from "node:http"; +type IncomingMessageCallback = (message: IncomingMessage) => void; import type { RequestOptions, RequestListener } from "node:http"; import type { ServerOptions } from "node:https"; @@ -55,9 +56,14 @@ export function get( return req; } +export function createServer(handler?: RequestListener): Server; export function createServer( options: ServerOptions, - handler: RequestListener, + handler?: RequestListener, +): Server; +export function createServer( + options?: ServerOptions | RequestListener, + handler?: RequestListener, ): Server { if (!enableNodejsHttpServerModules) { throw new ERR_METHOD_NOT_IMPLEMENTED("createServer"); diff --git a/src/compat/stream/destroy.ts b/src/compat/stream/destroy.ts index 93e3f7f4..aa681b7b 100644 --- a/src/compat/stream/destroy.ts +++ b/src/compat/stream/destroy.ts @@ -158,8 +158,29 @@ function _destroy( } } +// Workers consumes an unobserved internal TLS parent error at its async +// stream boundary. Direct EventEmitter.emit still throws to its caller. +const internalErrorInterruptions = new WeakMap(); +export function markInternalErrorInterruption(stream: object, error: unknown) { + internalErrorInterruptions.set(stream, error); + queueMicrotask(() => { + if (internalErrorInterruptions.get(stream) === error) + internalErrorInterruptions.delete(stream); + }); +} function emitErrorCloseNT(self: Readable | Writable, err: Error): void { - emitErrorNT(self, err); + try { + emitErrorNT(self, err); + } catch (error) { + if ( + internalErrorInterruptions.has(self) && + internalErrorInterruptions.get(self) === error + ) { + internalErrorInterruptions.delete(self); + return; + } + throw error; + } emitCloseNT(self); } diff --git a/src/compat/stream/readable.ts b/src/compat/stream/readable.ts index 0863074f..21511a41 100644 --- a/src/compat/stream/readable.ts +++ b/src/compat/stream/readable.ts @@ -1,4 +1,5 @@ // @ts-nocheck +import { ReadableStream as WorkersReadableStream } from "../stream-web/runtime.js"; import { asyncDispose } from "../../shared/globals/symbols.js"; // Copyright (c) 2017-2022 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: @@ -2371,7 +2372,9 @@ export function newReadableStreamFromStreamReadable( return new globalThis.ByteLengthQueuingStrategy({ highWaterMark }); }; - const strategy = evaluateStrategyOrFallback(options?.strategy); + const strategy = createTypeBytes + ? { highWaterMark } + : evaluateStrategyOrFallback(options?.strategy); let controller; let wasCanceled = false; @@ -2399,7 +2402,10 @@ export function newReadableStreamFromStreamReadable( // constructor rejects an invalid high-water mark, and a throw there must // leave the source as it was, not paused with this adapter's listeners // on it. The first pull() comes no earlier than a later microtask. - const stream = new globalThis.ReadableStream( + const StreamConstructor = createTypeBytes + ? WorkersReadableStream + : globalThis.ReadableStream; + const stream = new StreamConstructor( { start(c) { controller = c; @@ -2436,6 +2442,8 @@ export function newReadableStreamFromStreamReadable( return; } controller.close(); + if (createTypeBytes) + queueMicrotask(() => controller.byobRequest?.respond(0)); }); streamReadable.on("data", onData); diff --git a/src/compat/tls/jsstream.ts b/src/compat/tls/jsstream.ts index 752401b9..2a70fc28 100644 --- a/src/compat/tls/jsstream.ts +++ b/src/compat/tls/jsstream.ts @@ -27,15 +27,16 @@ import { Buffer } from "node:buffer"; /* eslint-disable @typescript-eslint/no-redundant-type-constituents */ +import { markInternalErrorInterruption } from "../stream/destroy.js"; import { notStrictEqual } from "../assert/index.js"; import { Socket } from "../net/internal-net.js"; import { ERR_STREAM_WRAP } from "./errors.js"; import { Duplex, toBYOBWeb } from "../stream/duplex.js"; import type { SocketInfo, - Writer, - Socket as CloudflareSocket, -} from "node-internal:sockets"; + PlatformSocket as CloudflareSocket, +} from "../net/sockets.js"; +type Writer = WritableStream; const kCurrentWriteRequest = Symbol("kCurrentWriteRequest"); const kCurrentShutdownRequest = Symbol("kCurrentShutdownRequest"); @@ -99,8 +100,8 @@ export class JSStreamSocket extends Socket { writable: webStream.writable as unknown as Writer, }, // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - reader: new ReadableStreamBYOBReader(webStream.readable), - writer: new WritableStreamDefaultWriter(webStream.writable), + reader: webStream.readable.getReader({ mode: "byob" }), + writer: webStream.writable.getWriter(), options: { host: "0.0.0.0", port: 0, @@ -109,7 +110,15 @@ export class JSStreamSocket extends Socket { }; stream.pause(); - stream.on("error", (err) => this.emit("error", err)); + stream.on("error", (err) => { + const unobserved = this.listenerCount("error") === 0; + try { + this.emit("error", err); + } catch (error) { + if (unobserved) markInternalErrorInterruption(stream, error); + throw error; + } + }); const ondata = (chunk: string | Buffer): void => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (typeof chunk === "string" || stream.readableObjectMode === true) { @@ -117,14 +126,13 @@ export class JSStreamSocket extends Socket { stream.pause(); stream.removeListener("data", ondata); - this.emit("error", new ERR_STREAM_WRAP()); - return; + throw new ERR_STREAM_WRAP(); } // TODO(soon): We need to trigger read() result for _handle.reader.read(buf) call // in node:net. }; - stream.on("data", ondata); + stream.prependListener("data", ondata); stream.once("end", () => { closePromise.resolve(); }); diff --git a/src/compat/tls/upgrade.spec.ts b/src/compat/tls/upgrade.spec.ts index 8e61798c..22273032 100644 --- a/src/compat/tls/upgrade.spec.ts +++ b/src/compat/tls/upgrade.spec.ts @@ -108,3 +108,50 @@ it("closes the connection when destroyed during authentication", async () => { expect(call.mock.calls.some(([, m]) => m === "socketWrite")).toBe(false); plain.destroy(); }); + +import { Duplex } from "../stream/duplex.js"; +it("constructs the Workers custom Duplex wrapper and forwards bytes", async () => { + const writes: string[] = []; + const custom = Duplex({ + read() {}, + write(chunk: Uint8Array, _encoding: string, callback: () => void) { + writes.push(Buffer.from(chunk).toString()); + callback(); + }, + }); + const wrapped = new TLSSocket(custom); + wrapped.on("error", () => {}); + const data = new Promise((resolve) => + wrapped.once("data", (chunk: Uint8Array) => + resolve(Buffer.from(chunk).toString()), + ), + ); + await tick(); + wrapped.write("outbound"); + custom.push(Buffer.from("inbound")); + expect(await data).toBe("inbound"); + await tick(); + expect(writes).toEqual(["outbound"]); + wrapped.destroy(); + custom.destroy(); +}); +it("custom Duplex EOF emits end before close", async () => { + const custom = Duplex({ + read() {}, + write(_chunk: unknown, _encoding: unknown, callback: () => void) { + callback(); + }, + }); + const wrapped = new TLSSocket(custom); + const events: string[] = []; + wrapped.on("error", () => {}); + wrapped.on("data", () => {}); + wrapped.on("end", () => events.push("end")); + wrapped.on("close", () => events.push("close")); + await tick(); + custom.push(null); + await tick(); + expect(events).toEqual(["end", "close"]); + wrapped.destroy(); + custom.destroy(); +}); diff --git a/src/compat/tls/wrap.ts b/src/compat/tls/wrap.ts index 55c29924..71513b65 100644 --- a/src/compat/tls/wrap.ts +++ b/src/compat/tls/wrap.ts @@ -109,7 +109,7 @@ export declare class TLSSocket extends Socket { constructor( socket: Socket | Duplex | undefined, - opts: TLSSocket["_tlsOptions"], + opts?: TLSSocket["_tlsOptions"], ); prototype: TLSSocket; @@ -247,10 +247,6 @@ export function TLSSocket( } } - if (socket != null && !(socket instanceof Socket)) { - throw new ERR_METHOD_NOT_IMPLEMENTED("TLS upgrade of a custom Duplex"); - } - if ( socket instanceof Socket && (socket.destroyed || diff --git a/src/compat/zlib/index.ts b/src/compat/zlib/index.ts index e6112e98..69711759 100644 --- a/src/compat/zlib/index.ts +++ b/src/compat/zlib/index.ts @@ -1,5 +1,5 @@ import { Buffer } from "node:buffer"; -import { validateInteger } from "../crypto/validation.js"; +import { validateUint32 } from "../net/validators.js"; import { ERR_INVALID_ARG_TYPE } from "../net/errors.js"; import * as compression from "./compression.js"; export * from "./compression.js"; @@ -369,7 +369,7 @@ function data(input: unknown): Uint8Array { ); } export function crc32(input: unknown, value = 0): number { - validateInteger(value, "value", 0, 0xffffffff); + validateUint32(value, "value"); let crc = value ^ -1; for (const byte of data(input)) { crc ^= byte; diff --git a/vitest.config.ts b/vitest.config.ts index f6d1a54e..734acb9f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -80,6 +80,8 @@ export default defineConfig({ "src/compat/crypto/cipher.spec.ts", "src/compat/crypto/hash.spec.ts", "src/compat/crypto/hash-types.spec.ts", + "src/compat/crypto/signatures.spec.ts", + "src/compat/crypto/digest-stream.spec.ts", "src/compat/fs/writable.spec.ts", "src/compat/fs/glob.spec.ts", "src/compat/fs/directory.spec.ts", From c3bb1cabec8afd6bd86bebd93dfdfacbbb3d673a Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 04:17:47 +0900 Subject: [PATCH 09/30] fix: align FileHandle identity lifecycle and descriptor ownership --- src/compat/expansion/README.md | 12 +- src/compat/expansion/index.spec.mjs | 170 +++++++++++++++++++ src/compat/fs/file-handle.spec.ts | 83 +++++++++ src/compat/fs/file-handle.ts | 254 ++++++++++++++++++++++++++++ src/compat/fs/index.ts | 111 +----------- src/compat/fs/writable.ts | 15 +- vitest.config.ts | 1 + 7 files changed, 540 insertions(+), 106 deletions(-) create mode 100644 src/compat/fs/file-handle.spec.ts create mode 100644 src/compat/fs/file-handle.ts diff --git a/src/compat/expansion/README.md b/src/compat/expansion/README.md index fcc9c793..246f19a2 100644 --- a/src/compat/expansion/README.md +++ b/src/compat/expansion/README.md @@ -29,7 +29,7 @@ inside that request retain their volume; embedded bundle assets stay read-only. Standalone imports outside SDK dispatch share a fallback volume. Copying embedded files to /tmp preserves immutable embedded bytes. Permission changes are validated no-ops and statfs returns the pinned Workers zero values. Embedded files support read-only descriptors, FileHandles, streams and links; copies into /tmp are writable and independent. Numeric file timestamps use milliseconds; embedded timestamps remain unchanged. Watching files is unsupported in Workers too. Directory iteration, recursive listings, glob patterns, device files, symlink copying, -FD reuse, byte streams and stream fd/custom-fs options follow the pinned Workers +Monotonically allocated descriptors, byte streams and stream fd/custom-fs options follow the pinned Workers profile. File sizes are capped at 128 MiB and paths at 4096 bytes/48 segments. `process.cwd()` and `process.chdir()` use the request-local virtual directory; streams retain the creating request's filesystem when external callbacks deliver @@ -40,6 +40,16 @@ retain their license headers and run with `npx vitest run src/compat/fs/director src/compat/fs/glob.spec.ts src/compat/fs/writable.spec.ts`; the zlib integration fixture also exercises a host stream crossing into the request-local filesystem. +FileHandles expose owned prototype methods and EventEmitter behavior. Their +backend and originating volume are private; the `fd` getter cannot be assigned, +and borrowed methods use their actual receiver. Closed handles reject operations +with Workers' EBADF contract. Descriptor numbers are never reused within a +volume, so a stale handle cannot access a later file. Closing a handle drops its +retained volume reference; finalization also releases an abandoned descriptor. +`readableWebStream()` captures `read` when created. The upstream and additional +receiver/lifetime tests run with `npx vitest run src/compat/fs/file-handle.spec.ts +src/compat/fs/directory.spec.ts`. + zlib 1.3.1, Brotli 1.1.0 and Zstd 1.5.7 use bundled, synchronously initialized WebAssembly. The profile covers parameters, synchronous/callback operations, incremental streams, flush/reset, byte accounting and output limits for synchronous diff --git a/src/compat/expansion/index.spec.mjs b/src/compat/expansion/index.spec.mjs index c572913c..83642d81 100644 --- a/src/compat/expansion/index.spec.mjs +++ b/src/compat/expansion/index.spec.mjs @@ -1,4 +1,174 @@ export const eventCases = { + fsFileHandleInvalidReceivers: async function ({ fs }) { + const h = await fs.promises.open("/tmp/fh-receivers", "w+"); + const methods = { + appendFile: ["x"], + chmod: [0], + chown: [0, 0], + datasync: [], + sync: [], + read: [new Uint8Array(1)], + readv: [[]], + readFile: [], + readLines: [], + stat: [], + truncate: [], + utimes: [0, 0], + write: [new Uint8Array(1)], + writev: [[]], + writeFile: ["x"], + close: [], + readableWebStream: [], + createReadStream: [], + createWriteStream: [], + }; + const attempt = async (fn) => { + try { + const value = await fn(); + if (value?.destroy) { + value.on("error", () => {}); + value.destroy(); + } + return "accepted"; + } catch (e) { + return { + name: e.name, + code: e.code ?? null, + ...(e.name === "TypeError" ? {} : { message: e.message }), + }; + } + }; + const result = { fake: {}, closed: {} }; + for (const [name, args] of Object.entries(methods)) + result.fake[name] = await attempt(() => h[name].call({}, ...args)); + await h.close(); + for (const [name, args] of Object.entries(methods)) + result.closed[name] = await attempt(() => h[name](...args)); + await fs.promises.unlink("/tmp/fh-receivers"); + return result; + }, + fsFileHandleLifetime: async function ({ fs }) { + const path = "/tmp/fh-lifetime"; + await fs.promises.writeFile(path, "original"); + const old = await fs.promises.open(path, "r+"), + fd = old.fd; + fs.closeSync(fd); + const current = await fs.promises.open(path, "r+"); + const error = async (fn) => { + try { + return await fn(); + } catch (e) { + return { name: e.name, code: e.code ?? null }; + } + }; + const result = { + reused: fd === current.fd, + oldStat: await error(() => old.stat()), + oldRead: await error(() => old.read(new Uint8Array(1))), + }; + await old.close(); + result.current = (await current.stat()).size; + await current.close(); + await fs.promises.writeFile(path, ""); + const empty = await fs.promises.open(path, "r"), + stream = empty.readableWebStream(); + empty.read = () => Promise.reject(new Error("replacement")); + const read = await stream.getReader().read(); + result.capturedRead = read.done; + await empty.close(); + await fs.promises.unlink(path); + return result; + }, + fsFileHandleSurface: async function ({ fs }) { + const error = async (fn) => { + try { + const value = await fn(); + return { accepted: true, value }; + } catch (e) { + return e instanceof TypeError + ? { name: e.name } + : { + name: e.name, + message: e.message, + code: e.code ?? null, + syscall: e.syscall ?? null, + }; + } + }; + await fs.promises.writeFile("/tmp/handle-surface-a", "one"); + await fs.promises.writeFile("/tmp/handle-surface-b", "second"); + const a = await fs.promises.open("/tmp/handle-surface-a", "r+"), + b = await fs.promises.open("/tmp/handle-surface-b", "r"); + const result = {}; + try { + const prototype = Object.getPrototypeOf(a); + result.own = Reflect.ownKeys(a).map(String); + result.prototype = Reflect.ownKeys(prototype).map(String); + result.descriptor = { + own: Object.hasOwn(a, "fd"), + get: typeof Object.getOwnPropertyDescriptor(prototype, "fd")?.get, + set: typeof Object.getOwnPropertyDescriptor(prototype, "fd")?.set, + }; + result.publicConstructor = + a.constructor === fs.promises.FileHandle && + a instanceof fs.promises.FileHandle; + result.constructor = { + name: a.constructor.name, + length: a.constructor.length, + }; + result.new = await error(() => new a.constructor()); + result.identity = a.read === a.read && a.read === prototype.read; + let receiver = false; + result.chain = + a.on("test", function () { + receiver = this === a; + }) === a; + a.emit("test"); + result.receiver = receiver; + result.borrowedStat = (await a.stat.call(b)).size; + const detached = a.stat; + result.detached = await error(() => detached()); + result.fake = await error(() => a.stat.call({})); + result.fakeReadLines = await error(() => a.readLines.call({ fd: a.fd })); + result.assign = await error(() => { + a.fd = 12345; + }); + const written = await a.writeFile("xy"); + result.writeFile = { + bytesWritten: written.bytesWritten, + buffer: Array.from(written.buffer), + plain: Object.getPrototypeOf(written) === Object.prototype, + }; + const read = await a.read(new Uint8Array(2), 0, 2, 0); + result.read = { + bytesRead: read.bytesRead, + buffer: Array.from(read.buffer), + plain: Object.getPrototypeOf(read) === Object.prototype, + }; + result.noBackend = [ + "fs", + "refs", + "closePromise", + "ref", + "unref", + "getAsyncId", + ].map((name) => name in a); + await a.close(); + result.closedFd = typeof a.fd; + result.closedStat = await error(() => a.stat()); + result.closedReadLines = await error(() => a.readLines()); + let closed = 0; + a.on("close", () => closed++); + await a.close(); + result.repeatClose = closed; + return result; + } finally { + await a.close(); + await b.close(); + await fs.promises.unlink("/tmp/handle-surface-a"); + await fs.promises.unlink("/tmp/handle-surface-b"); + } + }, tlsCustomDuplexStates: async function ({ tls: t, classicStreams: s }) { const out = {}; const tick = () => new Promise((r) => setTimeout(r, 15)); diff --git a/src/compat/fs/file-handle.spec.ts b/src/compat/fs/file-handle.spec.ts new file mode 100644 index 00000000..915adc40 --- /dev/null +++ b/src/compat/fs/file-handle.spec.ts @@ -0,0 +1,83 @@ +import { expect, test } from "vitest"; +import * as fs from "./index.js"; +import { runWithTemporaryFiles } from "./writable.js"; + +test("FileHandle owns its events, methods and readonly descriptor", async () => { + await runWithTemporaryFiles(async () => { + fs.writeFileSync("/tmp/handle-one", "one"); + fs.writeFileSync("/tmp/handle-two", "second"); + const a = await fs.promises.open("/tmp/handle-one", "r"), + b = await fs.promises.open("/tmp/handle-two", "r"); + try { + expect(a.read).toBe(a.read); + expect(a.read).toBe(Object.getPrototypeOf(a).read); + expect( + a.on("sample", function (this: unknown) { + expect(this).toBe(a); + }), + ).toBe(a); + a.emit("sample"); + expect((await a.stat.call(b)).size).toBe(6); + await expect(a.stat.call({})).rejects.toBeInstanceOf(TypeError); + const detached = a.stat; + await expect(detached()).rejects.toBeInstanceOf(TypeError); + expect(() => { + a.fd = 12345; + }).toThrow(TypeError); + expect(() => new a.constructor()).toThrow("Illegal constructor"); + expect(() => a.readLines.call({ fd: a.fd })).toThrow(TypeError); + for (const key of ["fd", "fs", "refs", "closePromise", "read", "stat"]) + expect(Object.hasOwn(a, key)).toBe(false); + expect(a.ref).toBeUndefined(); + expect(a.unref).toBeUndefined(); + await a.close(); + expect(a.fd).toBeUndefined(); + await expect(a.stat()).rejects.toMatchObject({ + code: "EBADF", + message: "Bad file descriptor", + }); + let count = 0; + a.on("close", () => count++); + await a.close(); + expect(count).toBe(1); + } finally { + await a.close(); + await b.close(); + } + }); +}); +test("FileHandle operations retain the originating volume and result object contract", async () => { + const handle = await runWithTemporaryFiles(async () => { + fs.writeFileSync("/tmp/owned", "abc"); + return fs.promises.open("/tmp/owned", "r+"); + }); + try { + const bytes = new Uint8Array(3), + read = await handle.read(bytes, 0, 3, 0); + expect(Object.getPrototypeOf(read)).toBe(Object.prototype); + expect(read.buffer).toBe(bytes); + expect(Array.from(bytes)).toEqual([97, 98, 99]); + const written = await handle.writeFile("xy"); + expect(written.bytesWritten).toBe(2); + expect(Buffer.from(written.buffer).toString()).toBe("xy"); + expect(await handle.readFile("utf8")).toBe("xy"); + } finally { + await handle.close(); + } +}); + +test("external close cannot let an old handle close a new descriptor", async () => { + await runWithTemporaryFiles(async () => { + fs.writeFileSync("/tmp/lease", "abc"); + const old = await fs.promises.open("/tmp/lease", "r+"), + fd = old.fd; + fs.closeSync(fd); + const current = await fs.promises.open("/tmp/lease", "r+"); + expect(current.fd).not.toBe(fd); + expect(current.constructor).toBe(fs.promises.FileHandle); + await expect(old.stat()).rejects.toMatchObject({ code: "EBADF" }); + await old.close(); + expect((await current.stat()).size).toBe(3); + await current.close(); + }); +}); diff --git a/src/compat/fs/file-handle.ts b/src/compat/fs/file-handle.ts new file mode 100644 index 00000000..5ea6fbcf --- /dev/null +++ b/src/compat/fs/file-handle.ts @@ -0,0 +1,254 @@ +// FileHandle's public contract follows workerd 5358e3d337336eaf696a48bc1563d808b10feba5. +// The filesystem backend and its request scope remain private. +import { Buffer } from "node:buffer"; +import { EventEmitter } from "node:events"; +import { ReadableStream } from "../stream-web/runtime.js"; +import { + asyncDispose, + installResourceSymbols, +} from "../../shared/globals/symbols.js"; +import { + validateObject, + validateBoolean, + validateUint32, +} from "../net/validators.js"; +import { validatePosition } from "./io-validation.js"; +import { createReadStream, createWriteStream } from "./streams.js"; +import { registerFileHandle } from "./stream-utils.js"; +installResourceSymbols(globalThis.Symbol); +const badge = Symbol("FileHandle constructor"); +const kFileHandle = Symbol("kFileHandle"); +type Scope = (operation: () => T) => T; +type Backend = { + fd: number; + run: Scope; + api: Record any>; +}; +const handleScopes = new WeakMap(); +const released = new FinalizationRegistry<() => void>((close) => close()); +function badDescriptor() { + return Object.assign(new Error("Bad file descriptor"), { + code: "EBADF", + syscall: "stat", + }); +} +export class FileHandle extends EventEmitter { + #fd: number | undefined; + #backend: Backend | undefined; + [kFileHandle] = true; + constructor(token: symbol, backend: Backend) { + if (token !== badge) throw new TypeError("Illegal constructor"); + super(); + this.#fd = backend.fd; + this.#backend = backend; + released.register( + this, + () => backend.run(() => backend.api.closeSync(backend.fd)), + this, + ); + registerFileHandle(this); + handleScopes.set(this, backend.run); + } + get fd() { + return this.#fd; + } + #invoke(name: string, ...args: any[]) { + if (this.#fd === undefined) throw badDescriptor(); + const fd = this.#fd; + const backend = this.#backend!; + return backend.run(() => backend.api[name](fd, ...args)); + } + async appendFile(data: string | ArrayBufferView, options: any = {}) { + await this.#invoke("appendFileSync", data, options); + } + async chmod(mode: string | number) { + await this.#invoke("fchmodSync", mode); + } + async chown(uid: number, gid: number) { + await this.#invoke("fchownSync", uid, gid); + } + async datasync() { + await this.#invoke("fdatasyncSync"); + } + async sync() { + await this.#invoke("fsyncSync"); + } + read( + bufferOrOptions: any = {}, + offsetOrOptions: any = {}, + length?: number, + position: number | bigint | null = null, + ) { + try { + if (this.#fd === undefined) throw badDescriptor(); + let options: any; + if (ArrayBuffer.isView(bufferOrOptions)) + options = + typeof offsetOrOptions === "number" + ? { + buffer: bufferOrOptions, + offset: offsetOrOptions, + length: length ?? bufferOrOptions.byteLength - offsetOrOptions, + position: position ?? null, + } + : { buffer: bufferOrOptions, ...offsetOrOptions }; + else options = bufferOrOptions; + const { + buffer = Buffer.alloc(16384), + offset = 0, + length: count = buffer.byteLength - offset, + position: at = null, + } = options; + validateUint32(offset, "offset"); + validateUint32(count, "length"); + validatePosition(at, "position"); + const bytesRead = this.#invoke("readSync", buffer, offset, count, at); + return Promise.resolve({ bytesRead, buffer }); + } catch (error) { + return Promise.reject(error); + } + } + readv(buffers: ArrayBufferView[], position: number | bigint | null = null) { + try { + return Promise.resolve({ + bytesRead: this.#invoke("readvSync", buffers, position), + buffers, + }); + } catch (error) { + return Promise.reject(error); + } + } + async readFile(options: any = {}) { + return await this.#invoke("readFileSync", options); + } + readLines(_options: any = {}): never { + if (this.#fd === undefined) throw badDescriptor(); + throw new Error("not implemented"); + } + async stat(options: any = {}) { + return await this.#invoke("fstatSync", options); + } + async truncate(length = 0) { + await this.#invoke("ftruncateSync", length); + } + async utimes(atime: any, mtime: any) { + await this.#invoke("futimesSync", atime, mtime); + } + write( + buffer: string | ArrayBufferView, + offsetPositionOrOptions: any = null, + lengthOrEncoding?: any, + position: number | bigint | null = null, + ) { + try { + if (this.#fd === undefined) throw badDescriptor(); + if (typeof buffer === "string") + buffer = Buffer.from(buffer, lengthOrEncoding); + const bytesWritten = this.#invoke( + "writeSync", + buffer, + offsetPositionOrOptions, + lengthOrEncoding, + position, + ); + return Promise.resolve({ bytesWritten, buffer }); + } catch (error) { + return Promise.reject(error); + } + } + writev(buffers: ArrayBufferView[], position: number | bigint | null = null) { + try { + return Promise.resolve({ + bytesWritten: this.#invoke("writevSync", buffers, position), + buffers, + }); + } catch (error) { + return Promise.reject(error); + } + } + writeFile(data: string | ArrayBufferView, options: any = {}) { + try { + const bytesWritten = this.#invoke("writeFileSync", data, options); + return Promise.resolve({ + bytesWritten, + buffer: ArrayBuffer.isView(data) ? data : Buffer.from(data, options), + }); + } catch (error) { + return Promise.reject(error); + } + } + close(): Promise { + try { + if (this.#fd !== undefined) this.#invoke("closeSync"); + this.#fd = undefined; + this.#backend = undefined; + handleScopes.delete(this); + released.unregister(this); + this.emit("close"); + return Promise.resolve(); + } catch (error) { + return Promise.reject(error); + } + } + async [asyncDispose]() { + await this.close(); + } + readableWebStream(options: any = {}): ReadableStream { + if (this.#fd === undefined) throw badDescriptor(); + validateObject(options, "options"); + const { autoClose = true } = options; + validateBoolean(autoClose, "options.autoClose"); + const handle = this; + const read = this.read.bind(this); + let controller: ReadableByteStreamController, + ended = false; + const finish = () => { + if (!ended) { + ended = true; + controller.close(); + } + }; + const stream = new ReadableStream({ + type: "bytes", + autoAllocateChunkSize: 16384, + start(c) { + controller = c; + }, + async pull(c) { + const request = c.byobRequest!, + view = request.view!; + const { bytesRead } = await read( + view, + view.byteOffset, + view.byteLength, + ); + if (bytesRead === 0) { + finish(); + if (autoClose) await handle.close(); + } + request.respond(bytesRead); + }, + async cancel() { + ended = true; + if (autoClose) await handle.close(); + }, + }); + this.once("close", finish); + return stream; + } + createReadStream(options: any = {}): any { + const run = handleScopes.get(this) ?? ((fn: () => any) => fn()); + return run(() => createReadStream("", { ...options, fd: this })); + } + createWriteStream(options: any = {}): any { + const run = handleScopes.get(this) ?? ((fn: () => any) => fn()); + return run(() => createWriteStream("", { ...options, fd: this })); + } +} +Object.defineProperty(FileHandle, "name", { + value: "FileHandle", + configurable: true, +}); +export function createFileHandle(backend: Backend) { + return new FileHandle(badge, backend); +} diff --git a/src/compat/fs/index.ts b/src/compat/fs/index.ts index 85ca18c1..0bd3b9af 100644 --- a/src/compat/fs/index.ts +++ b/src/compat/fs/index.ts @@ -1,7 +1,6 @@ -import { ReadableStream as WorkersReadableStream } from "../stream-web/runtime.js"; +import { createFileHandle, FileHandle } from "./file-handle.js"; import { installIo } from "./io.js"; import * as fileStreams from "./streams.js"; -import { registerFileHandle } from "./stream-utils.js"; import { Dir as WorkersDir, Dirent as WorkersDirent } from "./directory.js"; import { readDirectory } from "./directory-backend.js"; import { globSync as workersGlob } from "./glob.js"; @@ -10,6 +9,7 @@ import { validateInteger } from "../crypto/validation.js"; import * as embedded from "./embedded.js"; import { temporary, + retainTemporaryFiles, temporaryPath, clearTemporaryFiles, writablePath, @@ -356,108 +356,11 @@ for (const name of ["utimes", "lutimes", "futimes"]) { name !== "futimes", ); } -const openPromise = promises.open; -promises.open = async (...args: any[]) => { - fileMode(args[2], 0o666); - args[2] = 0o666; - const handle = await openPromise(...args); - const prototype = Object.getPrototypeOf(handle); - if (!Object.hasOwn(prototype, "readLines")) - Object.defineProperty(prototype, "readLines", { - value: function readLines(this: any) { - if (this.fd === -1 || this.fd === undefined) - throw Object.assign(new Error("Bad file descriptor"), { - code: "EBADF", - }); - throw new Error("not implemented"); - }, - writable: true, - configurable: true, - }); - const closeHandle = handle.close.bind(handle); - handle.close = () => { - if (handle.fd === -1) { - handle.emit("close"); - return Promise.resolve(); - } - return closeHandle(); - }; - const publicHandle = new Proxy(handle, { - get(target, key) { - if (key === "fd") return target.fd === -1 ? undefined : target.fd; - const value = Reflect.get(target, key, target); - return typeof value === "function" && - key !== "constructor" && - !Object.hasOwn(target, key) - ? value.bind(target) - : value; - }, - }); - registerFileHandle(handle); - registerFileHandle(publicHandle); - handle.readableWebStream = (options: any = {}) => { - if (handle.fd === -1) - throw Object.assign(new Error("bad file descriptor"), { code: "EBADF" }); - validateObject(options, "options"); - const { autoClose = true } = options; - validateBoolean(autoClose, "options.autoClose"); - let controller: ReadableByteStreamController, - ended = false; - const finish = () => { - if (!ended) { - ended = true; - controller.close(); - } - }; - const stream = new WorkersReadableStream({ - type: "bytes", - autoAllocateChunkSize: 16384, - start(c) { - controller = c; - }, - async pull(c) { - const req = c.byobRequest!; - const view = req.view!; - const { bytesRead } = await handle.read(view, 0, view.byteLength, null); - if (bytesRead === 0) { - finish(); - if (autoClose) await handle.close(); - } - req.respond(bytesRead); - }, - async cancel() { - ended = true; - if (autoClose) await handle.close(); - }, - }); - handle.once("close", finish); - return stream; - }; - handle.createReadStream = (options: any = {}) => - fileStreams.createReadStream("", { ...options, fd: handle }); - handle.createWriteStream = (options: any = {}) => - fileStreams.createWriteStream("", { ...options, fd: handle }); - const stat = handle.stat.bind(handle); - const isReadonly = readonlyFile(handle.fd); - const utimes = handle.utimes.bind(handle); - handle.utimes = async (atime: any, mtime: any) => { - const access = fileTime(atime), - modified = fileTime(mtime); - await stat(); - if (!isReadonly) await utimes(access, modified); - }; - handle.stat = async (...options: any[]) => - temporaryStats(await stat(...options), isReadonly); - handle.chmod = async (mode: unknown) => { - fileMode(mode); - await stat(); - }; - handle.chown = async (uid: number, gid: number) => { - validateInteger(uid, "uid", -1, 0xffffffff); - validateInteger(gid, "gid", -1, 0xffffffff); - await stat(); - }; - return publicHandle; +promises.FileHandle = FileHandle; +promises.open = async (path: any, flags: any, mode: any) => { + const run = retainTemporaryFiles(); + const fd = api.openSync(path, flags, mode); + return createFileHandle({ fd, run, api }); }; // Copy an embedded bundle file into request-local /tmp without granting any // write access to its source volume. diff --git a/src/compat/fs/writable.ts b/src/compat/fs/writable.ts index 5a1bbfc9..087d36cb 100644 --- a/src/compat/fs/writable.ts +++ b/src/compat/fs/writable.ts @@ -96,7 +96,14 @@ function createTemporary() { // descriptors, FileHandles and streams cannot bypass these rules. const core = (volume as any)._core; let nextFd = 3; - core.newFdNumber = () => core.releasedFds.pop() ?? nextFd++; + // Workers allocates monotonically within a volume. Reusing a closed number + // would let a stale FileHandle access or close an unrelated descriptor. + core.releasedFds.push = () => 0; + core.newFdNumber = () => { + if (nextFd === 2147483647) + throw Object.assign(new Error("Too many open files"), { code: "EMFILE" }); + return nextFd++; + }; const createNode = core.createNode.bind(core); core.createNode = (...args: any[]) => { const node = createNode(...args); @@ -569,6 +576,12 @@ export function runWithTemporaryFiles(callback: () => T): T { registerRequestScope(runWithTemporaryFiles); +/** Retain the originating virtual volume for descriptor operations. */ +export function retainTemporaryFiles() { + const volume = active(); + return (operation: () => T): T => contexts.run(volume, operation); +} + export function readonlyFile(value: any, follow = true): boolean { return active().readonly(value, follow); } diff --git a/vitest.config.ts b/vitest.config.ts index 734acb9f..6058fa5e 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -83,6 +83,7 @@ export default defineConfig({ "src/compat/crypto/signatures.spec.ts", "src/compat/crypto/digest-stream.spec.ts", "src/compat/fs/writable.spec.ts", + "src/compat/fs/file-handle.spec.ts", "src/compat/fs/glob.spec.ts", "src/compat/fs/directory.spec.ts", "src/shared/http-wire/index.spec.ts", From 7b923317fc8670344bf3cca553575746337eebb6 Mon Sep 17 00:00:00 2001 From: rdlabo Date: Sat, 24 Oct 2026 04:39:26 +0900 Subject: [PATCH 10/30] fix: align Workers key parsing and metadata contracts --- src/compat/crypto/LICENSE-3RD-PARTY.md | 5 + src/compat/crypto/LICENSE.ncrypto | 21 + src/compat/crypto/asymmetric.ts | 62 +-- src/compat/crypto/key-formats.ts | 226 +++++++++-- src/compat/crypto/key-state.ts | 1 + src/compat/crypto/keys.spec.ts | 121 ++++++ src/compat/crypto/keys.ts | 95 ++++- src/compat/crypto/native-cipher.build.json | 4 +- src/compat/crypto/native-cipher.c | 71 ++++ src/compat/crypto/native-cipher.ts | 42 ++ src/compat/crypto/native-cipher.wasm | Bin 312272 -> 662343 bytes src/compat/expansion/README.md | 22 +- src/compat/expansion/index.spec.mjs | 449 +++++++++++++++++++++ src/compat/tls/README.md | 4 +- vitest.config.ts | 1 + 15 files changed, 1038 insertions(+), 86 deletions(-) create mode 100644 src/compat/crypto/LICENSE.ncrypto create mode 100644 src/compat/crypto/keys.spec.ts diff --git a/src/compat/crypto/LICENSE-3RD-PARTY.md b/src/compat/crypto/LICENSE-3RD-PARTY.md index 46e266ea..a2380591 100644 --- a/src/compat/crypto/LICENSE-3RD-PARTY.md +++ b/src/compat/crypto/LICENSE-3RD-PARTY.md @@ -32,3 +32,8 @@ and lifecycle behavior from `src/workerd/api/node/crypto.c++` at that same worke revision. The cipher fixture retains the upstream `crypto_cipher-test.js` notices. BoringSSL commit `427ec40cc8edc545253289232e885708c409e5ea` supplies the bundled cipher primitives; its license and bundled third-party notices are in `LICENSE.boringssl`. + +PEM/DER key parsing is adapted from `src/ncrypto.cpp` in Node.js ncrypto +`v1.1.4`, the version pinned by workerd. Copyright (c) 2025 Node.js; the MIT +license is retained in `LICENSE.ncrypto`. Parsing uses the bundled BoringSSL +backend before transferring owned key material to the OpenSSL operation backend. diff --git a/src/compat/crypto/LICENSE.ncrypto b/src/compat/crypto/LICENSE.ncrypto new file mode 100644 index 00000000..6da37b65 --- /dev/null +++ b/src/compat/crypto/LICENSE.ncrypto @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Node.js + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/compat/crypto/asymmetric.ts b/src/compat/crypto/asymmetric.ts index ad747d71..f74b3280 100644 --- a/src/compat/crypto/asymmetric.ts +++ b/src/compat/crypto/asymmetric.ts @@ -1,5 +1,5 @@ import { signatureDigest } from "./digests.js"; -import { curveName, generateDh, deriveDh } from "./native-cipher.js"; +import { curveName, generateDh, deriveDh, decodeKey } from "./native-cipher.js"; import { validateExportOptions } from "./key-options.js"; import { ERR_INCOMPATIBLE_OPTION_PAIR, @@ -44,12 +44,16 @@ function input(value: any, privateKey: boolean): NativeKey { value, ); const options = - typeof value === "string" || - value instanceof ArrayBuffer || - ArrayBuffer.isView(value) + typeof value === "string" || isAnyBuffer(value) || ArrayBuffer.isView(value) ? { key: value } : value; - const data = options.key; + const { + key: data, + encoding = "utf8", + format = "pem", + type, + passphrase, + } = options; if ( data == null || data instanceof KeyObject || @@ -62,44 +66,40 @@ function input(value: any, privateKey: boolean): NativeKey { ); if ( typeof data === "object" && - !(data instanceof ArrayBuffer) && + !isAnyBuffer(data) && !ArrayBuffer.isView(data) ) return importJwk(data, privateKey); - const format = options.format ?? "pem"; + if ( + typeof data !== "string" && + !isAnyBuffer(data) && + !ArrayBuffer.isView(data) + ) + throw new ERR_INVALID_ARG_TYPE( + "key", + ["ArrayBuffer", "Buffer", "TypedArray", "DataView", "string", "object"], + value, + ); validateOneOf(format, "format", ["pem", "der"]); - if (options.type !== undefined) + if (type !== undefined) validateOneOf( - options.type, + type, "type", privateKey ? ["pkcs1", "pkcs8", "sec1"] : ["pkcs1", "spki"], ); const raw = - typeof data === "string" - ? Buffer.from(data, options.encoding ?? "utf8") - : bytes(data, "key"); + typeof data === "string" ? Buffer.from(data, encoding) : bytes(data, "key"); const password = - options.passphrase === undefined + passphrase == null ? undefined - : typeof options.passphrase === "string" - ? Buffer.from(options.passphrase, options.encoding ?? "utf8") - : bytes(options.passphrase, "passphrase"); - if (privateKey) return NativeKey.parse(raw, format === "pem", true, password); + : typeof passphrase === "string" + ? Buffer.from(passphrase, encoding) + : bytes(passphrase, "passphrase"); + const decoded = decodeKey(raw, format === "pem", privateKey, type, password); try { - return NativeKey.parse(raw, format === "pem", false, password); - } catch (error) { - // createPublicKey also accepts a serialized private key, deriving public material. - let key: NativeKey; - try { - key = NativeKey.parse(raw, format === "pem", true, password); - } catch { - throw error; - } - try { - return NativeKey.parse(key.export(false, false, "spki"), false, false); - } finally { - key.dispose(); - } + return NativeKey.parse(decoded.data, false, decoded.privateKey); + } finally { + decoded.data.fill(0); } } export function createPrivateKey(value: unknown): PrivateKeyObject { diff --git a/src/compat/crypto/key-formats.ts b/src/compat/crypto/key-formats.ts index 1c524092..a5f20742 100644 --- a/src/compat/crypto/key-formats.ts +++ b/src/compat/crypto/key-formats.ts @@ -1,11 +1,12 @@ import { Buffer } from "node:buffer"; import { NativeKey } from "./native-crypto.js"; +import { curveName, validateEcPrivate } from "./native-cipher.js"; // DER encodings follow RFC 5208/5915/8410; JWK members follow RFC 7518/8037. const curves: Record = { "P-256": { oid: "2a8648ce3d030107", size: 32, native: "prime256v1" }, "P-384": { oid: "2b81040022", size: 48, native: "secp384r1" }, "P-521": { oid: "2b81040023", size: 66, native: "secp521r1" }, - secp256k1: { oid: "2b8104000a", size: 32, native: "secp256k1" }, + "": { oid: "2b81040021", size: 28, native: "secp224r1" }, }; export function der(tag: number, ...parts: Uint8Array[]): Buffer { const data = Buffer.concat(parts); @@ -24,74 +25,211 @@ export function der(tag: number, ...parts: Uint8Array[]): Buffer { export function integer(bytes: Uint8Array): Buffer { let offset = 0; while (offset < bytes.length - 1 && bytes[offset] === 0) offset++; - const data = bytes.subarray(offset); + const data = bytes.length ? bytes.subarray(offset) : Buffer.from([0]); return der(2, ...(data[0] & 128 ? [Buffer.from([0]), data] : [data])); } -function field(jwk: any, name: string, size?: number): Buffer { - if (typeof jwk[name] !== "string") - throw new TypeError(`Missing JWK member: ${name}`); - const bytes = Buffer.from(jwk[name], "base64url"); - if (size !== undefined && bytes.length !== size) - throw new TypeError(`Invalid JWK member length: ${name}`); - if (!bytes.length) throw new TypeError(`Empty JWK member: ${name}`); - return bytes; +function required(jwk: any, name: string): string { + if (jwk[name] === undefined) + throw new Error(`${jwk.kty} JWK missing ${name} parameter`); + return `${jwk[name]}`; } -export function importJwk(jwk: any, privateKey: boolean): NativeKey { +function field(jwk: any, name: string): Buffer { + const value = required(jwk, name).replace(/[\t\n\f\r ]/g, ""); + if ( + !/^[A-Za-z0-9_-]*={0,2}$/.test(value) || + value.replace(/=+$/, "").length % 4 === 1 || + (value.includes("=") && value.length % 4 !== 0) + ) + throw new Error(`${jwk.kty} JWK invalid ${name} parameter`); + return Buffer.from(value, "base64url"); +} +function fixed(bytes: Buffer, length: number): Buffer { + let start = 0; + while (start < bytes.length && bytes[start] === 0) start++; + bytes = bytes.subarray(start); + if (bytes.length > length) throw new Error("EC JWK invalid public key"); + return Buffer.concat([Buffer.alloc(length - bytes.length), bytes]); +} +function dictionaryString(value: any): string { + const object = (item: any) => + item !== null && (typeof item === "object" || typeof item === "function"); + if (object(value)) { + const primitive = value[Symbol.toPrimitive]; + if (primitive != null) { + value = Reflect.apply(primitive, value, ["string"]); + if (object(value)) + throw new TypeError("Cannot convert object to primitive value"); + } else { + const original = value; + for (const name of ["toString", "valueOf"]) { + const method = original[name]; + if (typeof method !== "function") continue; + value = Reflect.apply(method, original, []); + if (!object(value)) break; + } + if (object(value)) + throw new TypeError("Cannot convert object to primitive value"); + } + } + if (typeof value === "symbol") + throw new TypeError("Cannot convert a Symbol value to a string"); + return String(value).replace( + /[\ud800-\udbff](?![\udc00-\udfff])|(? { + const result: Record = {}; + for (const name of [ + "kty", + "use", + "key_ops", + "alg", + "ext", + "crv", + "x", + "y", + "d", + "n", + "e", + "p", + "q", + "dp", + "dq", + "qi", + "oth", + "k", + "pub", + "priv", + ]) { + const field = value[name]; + if (field === undefined && name !== "kty") continue; + if (name === "key_ops" || name === "oth") { + if (!Array.isArray(field)) + throw new TypeError( + `Incorrect type for the '${name}' field on 'JsonWebKey': the provided value is not of type 'Array'.`, + ); + result[name] = []; + const length = field.length; + for (let i = 0; i < length; i++) { + const item = field[i]; + if (name === "key_ops") result[name].push(dictionaryString(item)); + else { + if ( + item != null && + typeof item !== "object" && + typeof item !== "function" + ) + throw new TypeError( + `Incorrect type for array element ${i}: the provided value is not of type 'RsaOtherPrimesInfo'.`, + ); + const prime: Record = {}; + for (const member of ["r", "d", "t"]) { + const part = item?.[member]; + if (part !== undefined) prime[member] = dictionaryString(part); + } + result[name].push(prime); + } + } + } else + result[name] = name === "ext" ? Boolean(field) : dictionaryString(field); + } + return result; +} +export function importJwk(value: any, privateKey: boolean): NativeKey { + const jwk = dictionary(value); + const unsupported = () => + new Error( + `JWK ${privateKey ? "private" : "public"} key import is not implemented for this key type`, + ); if (jwk?.kty === "RSA") { - const names = privateKey - ? ["n", "e", "d", "p", "q", "dp", "dq", "qi"] - : ["n", "e"]; + required(jwk, "n"); + required(jwk, "e"); + const values = [field(jwk, "n"), field(jwk, "e")]; + if (privateKey) { + const names = ["d", "p", "q", "dp", "dq", "qi"]; + for (const name of names) required(jwk, name); + values.push(...names.map((name) => field(jwk, name))); + } return NativeKey.parse( der( 48, ...(privateKey ? [integer(Buffer.from([0]))] : []), - ...names.map((name) => integer(field(jwk, name))), + ...values.map(integer), ), false, privateKey, ); } if (jwk?.kty === "EC") { - const curve = curves[jwk.crv]; - if (!curve) throw new TypeError("Unsupported JWK curve"); + const crv = required(jwk, "crv"); + let native: string; + try { + native = curveName(crv); + } catch (error) { + throw new Error( + (error as Error).message === "Failed to initialize key" + ? "EC JWK unsupported curve" + : "EC JWK unsupported crv parameter", + ); + } + const curve = Object.values(curves).find( + (value) => value.native === native, + )!; + required(jwk, "x"); + required(jwk, "y"); + const x = field(jwk, "x"), + y = field(jwk, "y"); const oid = der(6, Buffer.from(curve.oid, "hex")); const point = Buffer.concat([ Buffer.from([4]), - field(jwk, "x", curve.size), - field(jwk, "y", curve.size), + fixed(x, curve.size), + fixed(y, curve.size), ]); const bits = der(3, Buffer.from([0]), point); - const data = privateKey - ? der( - 48, - integer(Buffer.from([1])), - der(4, field(jwk, "d", curve.size)), - der(160, oid), - der(161, bits), - ) - : der( - 48, - der(48, der(6, Buffer.from("2a8648ce3d0201", "hex")), oid), - bits, - ); - return NativeKey.parse(data, false, privateKey); + const publicData = der( + 48, + der(48, der(6, Buffer.from("2a8648ce3d0201", "hex")), oid), + bits, + ); + let publicKey: NativeKey; + try { + publicKey = NativeKey.parse(publicData, false, false); + } catch { + throw new Error("EC JWK invalid public key"); + } + if (!privateKey) return publicKey; + publicKey.dispose(); + const d = field(jwk, "d"); + validateEcPrivate(native, d); + return NativeKey.parse( + der( + 48, + integer(Buffer.from([1])), + der(4, d), + der(160, oid), + der(161, bits), + ), + false, + true, + ); } - if (jwk?.kty === "OKP" && ["Ed25519", "X25519"].includes(jwk.crv)) { + if (jwk?.kty === "OKP") { + const crv = required(jwk, "crv"); + if (!["Ed25519", "X25519"].includes(crv)) + throw new Error("OKP JWK unsupported crv parameter"); + const value = field(jwk, privateKey ? "d" : "x"); + if (value.length !== 32) throw unsupported(); const algorithm = der( 48, - der(6, Buffer.from([43, 101, jwk.crv === "Ed25519" ? 112 : 110])), + der(6, Buffer.from([43, 101, crv === "Ed25519" ? 112 : 110])), ); const data = privateKey - ? der( - 48, - integer(Buffer.from([0])), - algorithm, - der(4, der(4, field(jwk, "d", 32))), - ) - : der(48, algorithm, der(3, Buffer.from([0]), field(jwk, "x", 32))); + ? der(48, integer(Buffer.from([0])), algorithm, der(4, der(4, value))) + : der(48, algorithm, der(3, Buffer.from([0]), value)); return NativeKey.parse(data, false, privateKey); } - throw new TypeError("Unsupported JWK key type"); + throw unsupported(); } function encode(bytes: Uint8Array, size?: number): string { if (size !== undefined && bytes.length < size) @@ -139,5 +277,5 @@ export function exportJwk(key: NativeKey, privateKey: boolean): any { x: encode(key.raw(false)), ...(privateKey ? { d: encode(key.raw(true)) } : {}), }; - throw new TypeError("Unsupported JWK key type"); + throw new Error("Key type is invalid for JWK export"); } diff --git a/src/compat/crypto/key-state.ts b/src/compat/crypto/key-state.ts index 3e771d07..daba033a 100644 --- a/src/compat/crypto/key-state.ts +++ b/src/compat/crypto/key-state.ts @@ -5,6 +5,7 @@ export interface KeyState { bytes?: Uint8Array; asymmetric?: NativeKey; native?: CryptoKey; + nativeAlgorithm?: KeyAlgorithm; } export const keyStates = new WeakMap(); export function isKeyObject(value: unknown): boolean { diff --git a/src/compat/crypto/keys.spec.ts b/src/compat/crypto/keys.spec.ts new file mode 100644 index 00000000..7c69cc66 --- /dev/null +++ b/src/compat/crypto/keys.spec.ts @@ -0,0 +1,121 @@ +import { expect, test } from "vitest"; +import { + createPrivateKey, + createPublicKey, + generateKeyPairSync, + KeyObject, +} from "./index.js"; + +test("JWK conversion reads unused dictionary members once before parsing", () => { + let reads = 0; + const key = createPublicKey({ + key: { + kty: new String("RSA"), + get n() { + return ++reads === 1 ? "AQ" : "@@"; + }, + e: "AQAB", + }, + }); + expect(reads).toBe(1); + expect(key.export({ format: "jwk" })).toEqual({ + kty: "RSA", + n: "AQ", + e: "AQAB", + }); + const failure = new Error("unused d"); + expect(() => + createPublicKey({ + key: { + kty: "RSA", + n: "AQ", + e: "AQAB", + get d() { + throw failure; + }, + }, + }), + ).toThrow(failure); + for (const name of ["key_ops", "oth"]) { + const array = [null]; + Object.defineProperty(array, 0, { + get() { + Object.defineProperty(array, 1, { + get() { + throw new Error("added item"); + }, + }); + return name === "key_ops" ? "sign" : {}; + }, + }); + expect( + createPublicKey({ + key: { kty: "RSA", n: "AQ", e: "AQAB", [name]: array }, + }).type, + ).toBe("public"); + } +}); + +test("EC private scalars use the Workers range without requiring point consistency", () => { + const { privateKey } = generateKeyPairSync("ec", { + namedCurve: "prime256v1", + }); + const jwk = privateKey.export({ format: "jwk" }); + for (const d of ["", "AA", "_".repeat(43)]) + expect(() => createPrivateKey({ key: { ...jwk, d } })).toThrow( + "EW JWK invalid private key", + ); + expect( + createPrivateKey({ key: { ...jwk, d: "AQ" } }).export({ format: "jwk" }).d, + ).toBe("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE"); +}); + +test("DER decoding respects the selected encoding", () => { + const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 512 }); + const key = privateKey.export({ format: "der", type: "pkcs8" }); + expect( + createPrivateKey({ key, format: "der", type: "pkcs8" }).equals(privateKey), + ).toBe(true); + expect( + createPrivateKey({ key, format: "der", type: "pkcs1" }).equals(privateKey), + ).toBe(true); + const pkcs1 = privateKey.export({ format: "der", type: "pkcs1" }); + expect(() => + createPrivateKey({ key: pkcs1, format: "der", type: "pkcs8" }), + ).toThrow("Failed to parse private key"); +}); + +test("host key metadata is copied from intrinsic getters", async () => { + const pair = await crypto.subtle.generateKey( + { name: "ECDSA", namedCurve: "P-256" }, + true, + ["sign", "verify"], + ); + Object.defineProperty(pair.publicKey, "algorithm", { + value: { name: "fake" }, + configurable: true, + }); + const key = KeyObject.from(pair.publicKey) as ReturnType< + typeof createPublicKey + >; + const get = Object.getOwnPropertyDescriptor( + CryptoKey.prototype, + "algorithm", + )!.get!; + get.call(pair.publicKey).namedCurve = "fake"; + expect(key.asymmetricKeyType).toBe("ec"); + expect(key.asymmetricKeyDetails).toEqual({ namedCurve: "prime256v1" }); +}); + +test("shared key buffers are decoded when the host provides shared memory", () => { + const { privateKey } = generateKeyPairSync("ec", { + namedCurve: "prime256v1", + }); + const data = new TextEncoder().encode( + privateKey.export({ format: "pem", type: "pkcs8" }), + ); + const shared = new SharedArrayBuffer(data.byteLength); + new Uint8Array(shared).set(data); + expect(createPrivateKey(shared).equals(privateKey)).toBe(true); + expect(createPrivateKey({ key: shared }).equals(privateKey)).toBe(true); +}); diff --git a/src/compat/crypto/keys.ts b/src/compat/crypto/keys.ts index 24f85825..64a9c625 100644 --- a/src/compat/crypto/keys.ts +++ b/src/compat/crypto/keys.ts @@ -25,6 +25,30 @@ function boundary(): never { { code: "ERR_METHOD_NOT_IMPLEMENTED" }, ); } +const nativeAlgorithm = Object.getOwnPropertyDescriptor( + globalThis.CryptoKey.prototype, + "algorithm", +)!.get!; +const nativeType = Object.getOwnPropertyDescriptor( + globalThis.CryptoKey.prototype, + "type", +)!.get!; +function cloneAlgorithm(value: any): any { + if (ArrayBuffer.isView(value)) + return new Uint8Array( + value.buffer, + value.byteOffset, + value.byteLength, + ).slice(); + if (value && typeof value === "object") + return Object.fromEntries( + Object.entries(value).map(([name, entry]) => [ + name, + cloneAlgorithm(entry), + ]), + ); + return value; +} export function create( type: KeyState["type"], value: Omit, @@ -46,7 +70,10 @@ export abstract class KeyObject { static from(key: CryptoKey): KeyObject { if (!(key instanceof globalThis.CryptoKey)) throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key); - return create(key.type, { native: key }); + return create(nativeType.call(key), { + native: key, + nativeAlgorithm: cloneAlgorithm(nativeAlgorithm.call(key)), + }); } abstract get type(): KeyState["type"]; get [Symbol.toStringTag]() { @@ -74,6 +101,12 @@ export abstract class KeyObject { ); if (options.cipher !== undefined) validateString(options.cipher, "options.cipher"); + if ( + current.type === "public" && + options.type === "pkcs1" && + current.asymmetric.type !== "rsa" + ) + throw new Error("PKCS1 can only be used for RSA keys"); const passphrase = options.passphrase === undefined ? undefined @@ -145,13 +178,56 @@ export class SecretKeyObject extends KeyObject { } abstract class AsymmetricKeyObject extends KeyObject { get asymmetricKeyType() { - const key = state(this).asymmetric; - if (!key) return boundary(); + const current = state(this), + key = current.asymmetric; + if (!key) { + const name = current.nativeAlgorithm?.name; + if (name === undefined) return boundary(); + return ( + ( + { + "RSASSA-PKCS1-v1_5": "rsa", + "RSA-PSS": "rsa", + "RSA-OAEP": "rsa", + ECDSA: "ec", + ECDH: "ecdh", + Ed25519: "ed25519", + "NODE-ED25519": "ed25519", + X25519: "x25519", + } as Record + )[name] ?? name + ); + } return key.type; } get asymmetricKeyDetails() { - const key = state(this).asymmetric; - if (!key) return boundary(); + const current = state(this), + key = current.asymmetric; + if (!key) { + const algorithm = current.nativeAlgorithm as + | RsaHashedKeyAlgorithm + | EcKeyAlgorithm + | undefined; + if (!algorithm) return boundary(); + if ("modulusLength" in algorithm) { + let publicExponent = 0n; + for (const value of algorithm.publicExponent) + publicExponent = (publicExponent << 8n) | BigInt(value); + return { modulusLength: algorithm.modulusLength, publicExponent }; + } + if ("namedCurve" in algorithm) + return { + namedCurve: + ( + { + "P-256": "prime256v1", + "P-384": "secp384r1", + "P-521": "secp521r1", + } as Record + )[algorithm.namedCurve] ?? algorithm.namedCurve, + }; + return {}; + } if (key.type === "rsa") return { modulusLength: key.bits, @@ -160,6 +236,15 @@ abstract class AsymmetricKeyObject extends KeyObject { ), }; if (key.type === "ec") return { namedCurve: key.group }; + if (key.type === "dsa") { + const q = key.parameter("q"); + return { + modulusLength: key.bits, + divisorLength: q.length + ? (q.length - 1) * 8 + 32 - Math.clz32(q[0]) + : 0, + }; + } return {}; } toCryptoKey() { diff --git a/src/compat/crypto/native-cipher.build.json b/src/compat/crypto/native-cipher.build.json index 691f0610..e3fe7ddf 100644 --- a/src/compat/crypto/native-cipher.build.json +++ b/src/compat/crypto/native-cipher.build.json @@ -21,6 +21,6 @@ "-sEXPORTED_FUNCTIONS=[\"_malloc\",\"_free\"]" ], "libraries": ["libdecrepit.a", "libcrypto.a"], - "bindingSha256": "930b5fc1bc1844b987e03234c9d8ca560a2da8d6b402707dc171de0bb1c08cc3", - "sha256": "e0d9300f35b387a087fa792d8c04f2f9fef36cbde68d6f9334eccf3db887ff98" + "bindingSha256": "efa506a0bdd98bb852ed8915d2f741ccf527ba8b7d49df564f30920ce163e42e", + "sha256": "e68aa7213767bf14c391ce0e407e4272e5036e3d5778613c34c7ca44ebbda5b1" } diff --git a/src/compat/crypto/native-cipher.c b/src/compat/crypto/native-cipher.c index d5bed397..195eebb8 100644 --- a/src/compat/crypto/native-cipher.c +++ b/src/compat/crypto/native-cipher.c @@ -7,8 +7,73 @@ #include #include #include +#include +#include #include #define API EMSCRIPTEN_KEEPALIVE +// Key decoding follows ncrypto v1.1.4 using the same BoringSSL parser as Workers. +static unsigned char *key_der; +static int key_der_size; +API void cipher_key_clear(void) { + if(key_der){if(key_der_size>0)OPENSSL_cleanse(key_der,key_der_size);OPENSSL_free(key_der);} + key_der=NULL;key_der_size=0; +} +API const unsigned char *cipher_key_data(void){return key_der;} +API int cipher_key_der_size(void){return key_der_size;} +struct key_password {const unsigned char *data;int size;}; +static int key_password_cb(char *out,int capacity,int writing,void *opaque){ + (void)writing;struct key_password *pass=opaque; + if(!pass || pass->size>capacity)return -1; + if(pass->size)memcpy(out,pass->data,pass->size);return pass->size; +} +static int key_encrypted(const unsigned char *data,int size){ + if(size<2 || data[0]!=0x30)return 0; + size_t offset=2,length=data[1]; + if(length&0x80){size_t count=length&0x7f;length=0; + if(count>sizeof(size_t) || count+2>(size_t)size)return 0; + for(size_t i=0;i { + if (!api.cipher_ec_private_valid(curve, data, value.byteLength)) + throw new Error("EW JWK invalid private key"); + }); +} + +export function decodeKey( + data: Uint8Array, + pem: boolean, + privateKey: boolean, + type: string | undefined, + password?: Uint8Array, +): { data: Uint8Array; privateKey: boolean } { + return inputs([data, password ?? 0], (api, [p, pass]) => { + try { + const result = api.cipher_key_parse( + p, + data.byteLength, + +pem, + +privateKey, + type === undefined + ? -1 + : ["pkcs8", "pkcs1", "sec1", "spki"].indexOf(type), + pass, + password?.byteLength ?? 0, + +(password !== undefined), + ); + if (!result) + throw new Error( + `Failed to parse ${privateKey ? "private" : "public"} key`, + ); + return { + data: output(api, api.cipher_key_data(), api.cipher_key_der_size()), + privateKey: result === 2, + }; + } finally { + api.cipher_key_clear(); + } + }); +} + // Match Workers' curve lookup before handing generation to the owned key backend. export function curveName(name: string): string { return inputs([name], (api, [p]) => { diff --git a/src/compat/crypto/native-cipher.wasm b/src/compat/crypto/native-cipher.wasm index de6c70822586b0b773880ce1a21f2acde43586e4..a52792b19396a3e64caae93e9e813600bf252786 100755 GIT binary patch literal 662343 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

tgu+ubPax<1NUF=sILt&aI*u^ag&Qyen zN*5#N&k%+ycM=pim}-)c-<=0cShho_^4bsF(C=)AF4Iz*D34!p`HnO8%g9$A<$rHR zJ5yVhrY^0k%Kp_N5OuD4DVjpLCeZ+}31covd5OV7> z*($SKQ``mHpc4qrR1gUyQ7k;1B8N9DBs(^dOigrNKq`~Z%!`|GOjxAuM5;XW#4j@M zZw_5j8-pWRUlW|-dDdvbh5#d223O>KoX;vX!7ZV1y-_I~9SS1o2j^lHs(^U?pVCxP z6yoCC?fm8~q573!PqSSY>E67!B~*=920_iBrjbsuf%sV~0;>~f^sv$j8~&cxdvyDi z7*WHcfCVw%XGOk)qb1mch;}rwRxv6V7aIl#hdHa1rOViHsnWx^GzMctQ>9Mf0pOT{ zrBgd0RO^0oCxo+seFul?VfEu*I1?i)8T?Qv57I~m#?8DL3I)uZt)Y|#E+xP1AqBQk zUO%wkLFz*LWD%QfTSFPNO>ta)_0}((i%e~ZVz8AklT-@MU1a`%;B_!%THp}Iz=#GP zGdV}uGFg}NHL?f^TgDR)XOKP}mo0-Aa}vQpgssBchu};II5F9pli|W->T7RcKJYug zMR{!7u*-zVWLfp^RxYF$FO{3KKZI&_wqg}7p-0YQ@W37Z0*S_B6CgsPIoS7ttC`jr zf8niV$B?Ou0*G)qZ2J8es?!3>2HP8tC0&Sh$M#}OajPU3u-!(y-U$+H8uNb)6)^uP z1{r?Xc^Dz`NROhKr5D!~U`n`Wocl4=bSBxN1okv&Xl=dv9E!E7~v*| z>0lc>bAc~dP@seheUXiREi6@H;|LS#?!%;6^&rZ_N%D;~d(bQGC6r>H=&Kh9?9kE<0&dUdn6v9aXgR+;oW2R6`7 zRE-Y}&_#)+1`^*@Cg z_{NO>In))4Tk`o?lc{9X^e*Ui26*nRW zZbbr;eWH+IkI?GaV1mCL0}eBa9nc?sMg-R>Aj zeo;*MXFUc_@=F7AM*6UGVrP*nU#-;lI^LWzAMOd|-Y)ql;KIVn#5nUr6LX({D}L!i zjF#I%$bepdVz`w@PxZ1T`E@oIe*4=?^j0TjyEzet=h-)Z7VK_AGeu8kqPq@;} zJoXEyVvCvbODLluOSzvCf+!`a%MtC5D;nMKLL#y3V%smF+KIxI%pH$Zu4cjqLbZ`Z zMPg?W4P`H7yZ!(snYp|ro_p2-Nz?WXUfyN-SppaZ2eL2~Yl)dC#xW+B!jKPLv3+lj9)OSl*nz`DEy7o-Ju%h@<%X)}c^?c7RN%38lRE;d z`D9WLLw6c$+8z$oE?ApHxl5EiOdyrL;CLtn*qgjr`S)Q7|3zOJqJHSGbTjKnsHfM> z`;LTaRN8`}WPnG!Id=qyfmR#~RcVA4`Z4nHUC{oa@X(XH+eH%@2JRIy4CV)50sgWh zq0Y!qMn32q+8-z54|awSLuTZnmg9|@FiHD?3=!k{Gl&v@NES0JO|UtZNw$-}12?*G zGLcTf!ormugG~<25ByDFu1m*2T0G@}WJHgA&NT55zzE}IB8xUl7RBy&EKJNWrX7z5o0UEvMfv&RrFv(~(MEY#>iW0iK%v5G$~1?wLVJ>iYj4{umnt;IP22XM#B@#D#B z9A>!|tw=H7InAGMwnYmmNO2Tn#~Eb`z@DUx`;BE@n{LbGBheJZIUZ$Bkb$pIHZSHq z%RLJo&%vt^Q_PNGbZSv$4vV?(S!~)6TMSuDBDL8hZ$;(GWwM<8AmaY!*KIjwX7pOg zOm^f+@u&wtlpR4hbOuUlJDN%*wH*$?AVL)UEkne2 zgOFs69Sp_P2)yRszlEyf2Aa#$D-&u5uD9SrdJGgQjY5G+{m#sh7K({Ayw;;d0#$^1 zdD}d|mcL@b;1-I2vAv{S5#+u3=dzaR_dCxIw`FAB*ry8s%LtzM$^z2-C}KK}l#!~F zT%O(M1Q_rZ^AvtOvYvB7_}!`#q2`F7cKIFl0C%CMpOnw_Pa;xbt$FxlsCO0_B19a} zyS*KR(Oz7nW6kE1@WqTZr6<9$wwPL{JmwJReCJLr^m~uk!DcLTz|6`$mF^;-%z4@c zN2)^J_+yDY2A)~6y!V8kh%5GFhWMTwg@jG`BJLrm0Ln$=0}HG|jw}|y%=tXhl>#n2 z7)<0n;rG5^;ThNJk!&nHrZ)1P@ZZALL;Q^~`aYpquSISJm`d-xC_Lj{MFI0dS?GF9 z45a??W=~ltKl{xrP`$HfSQu*-awR!Fu*(fC1*bz@dLk&B5xr5bND;uGvSRWn+8lfz zK8`qN#3xp)E{Jzx@{^Dy`@4YOi?z|bc{-GrHdRpOU0kuo zC-UjV@CG6iAf&6!XGABdc~+bmH=LDYFcZ&8*vy`@p}K83SR#kpdq1GI;2;Kv%DX9q z;tbil)#MRFur$SKiGYCa=R$R^lv5Vof|H_4+;AA%TqzKo#F_)raZe?( zNV^La;WQ*V=3$>wp&40H+y|`-*E#uvGt6k8s)a*13w^3?=GRc_nBk1~6^6N6(@@wN zY~hqoA!(R0)%>bfU7Io=BWx{^gz4yvvJp2i#A)vEt8gcZ>yVIX6A*D*@<_wR6~{Hk zqN_w~M(_dtmK5j^-}yH#c^_gv_N&Z>kg92+k+%l~8jrT{LHj75eq{k)yvn$FMpia^ z$*h46CNGHRzzaSf4~hhbCOhYNOa_0JB@rrkDhbwXK5uK{#NQ92FKd5QE&;c}-|}KK zLs0%2uWA&HS2R)kT>bOd1`I?{-hoR8C&)))m`ZN~Mlo=&&juh9@!_Ha9_K^B9K= z9kO^6l+guT;s>CvS=AZ9KIXuI_MIl$H(3metgBGunPQjV%&-L2vBzmS5ly8Om7PgK zj$w{iINr+_CJGi#A$aKaRzQc4ENOwMoT%zHo{&g$GL$WpDA$Ukkz$3sea9BLGf`cN zqY+aQRn_Y-IuIi`I!MXD(TQ_w#E8h~aB$p$j1(G{j49?18w=&p(mamoBfSKAJs8Fu zQz=O`WL`Jn8Cze4kHt7is&$L#D@iuap+jnSS~kgaQ6b2SVKh>1%OaM0JxSd{69jQj z^EGgjcdJ z8BV4JmDC_S6$J35Z>nlt3EkjD#aO+srK-X9({(X#p9-iy;_c>uy7{UZ&@LcZ*yZ!Q z+!@GYvaaD0Erp4671oZ}`M~sqP&|0KQ=5U>_QnBIl%{f$5H97%QHsfF>XK~4U|KUi z3X);?`tzS|Ibi&U(o}tVo=Q!2;DA$vFS zu3>YeN(-Z6dS%to_krnIS@C$x^Oa#S!rQr(A%1*d)>c+^a-r5k0U7|Is|tiEkhT@5?Gaam^?!-@@>Z*30jg^I&&H-!5ku)giVv1cR@0(Yv zOLc3iN2+6@lnT|I0iyq57;7h@d%<_ipEJ?>&1Og@IQBI2R;GFkH<;JVQYU>MmgHuu zPy9GowylP0fghc#x+(Z*!INPA6M|e!hfn`pGzn?ebJf#Dh&A#O!HmYWtfzg>&Yh$K z*wlGe(FxG7@7mO@q#=$uI$E&$;Q)zap6k=1ny{Ov>*Yd z#c=<^_*z(sbUB!rc`AIx@DC+=*E&UcWYE3<2PshE( z8O~0i3!X4l@>PrQY{~0|e2mCgKQQckxjYNCJA>Ea)^3vBY;#AxYMvHJP8tZ#G86Mv zR<&u_2s-q;jz_r8!IzOD&otW=s%CcPt1Be%Hx3*C+eS4+9%P$s`-4>j{&-TfHC6Nt zs~j&?ICcVp@jl6HGbyZc^QYLrOLzPBL7XWdI&zAc0Z`rMu&UuZZ5qC#3QSgks@0Mg zd62s|A{r zM>r+k*|XFQq^T&&pe2Ep<-tEhqW5nk$Tp?{#1DC#iUqiCnaM0vZG4B!HHBbzhsI$r!C55Umv@8qeXaa&u;~zL}juxs~YH&Ijdt8AJgX$gas$Wwz$+bpU@&sZy<&# zQAmScL2MSte*>Ifm|AsIH^}km#!B;G9n}sI)5CVb9+?BEuV`hq+|=v}ARO~X!ByFToj$Htk$m`OC4Bl2 z(%cR>BjTO2jcwz8;au2QMY%V?vtzRp$QQT61&gH_*&)g9@t=92o=SS)VCBL%TvJ#% z!S9Ppu+Bu5pAaqYrOd)KAPCEA$K)u$(P%qdcO)@Pivqv{C=DH5U-jsXodq5}sHy>+ z{sW1zhm0dYm_j>=FGh0xB*Bq%2iI^zjfP(3F1w|r*MmfOw~& zdK*!fw>MHX!Q!83r0QqG9AGJvyE613`2P?ySPkQ6&NL0=nC*>J^{U&&p%{lX=L01J z8Mw|ClinB%X}f9ASUpe_g~0M~z|skU=*jMK3vQq?%HndJFx#;-E0E?r2KQ%``?e<2 zeZ;jGv7o#qBLZr_ML;tE8t;cQCbNm^rv~A0N`#P|)#*s3Hmr%NnSnJ9c97zHf*xQh zAfRKfTiQg`={Gy*KF`qvxr&}(NV_=PND`ZJ3Yi9+C7UfTz#s_$VWE*Hjg^461dJD- zQ{3OHf^#*73MIh+z_`0NRlS1CMXe&+!?I042BR5G!RyM*uT51~{}5%a`6ZZr2}hoo z5}Y;Ejvd$mfT&CV;9EGG8m%y!^_BDXI3DcwT7Ur0}BR$qj7fX9&Zg< zab?Mr)~bacaYo;^QLS#U!yi)tJ>6^shaXyAa-a>gJck z^T)QT0GZT_+NvxJ*x0tv4NA?bwkogH4vDH1HI?!YcK|EiLWjPJ5)CqAVO_An$fnER zf;cm!t;#WN+NtzFDKe_ja>2;H-tAOfWQqp}n)QX;H|8Rt<7&SjUba`-sT*6`Dj{o0 zUEFH0WkzZPU2q`)Cr^~RHJcjkRVy^PPkXQmB;;tX>Nk>0VG<;tA%@(&`_rO9#w^DY zFDm|u;o_gRR|B%Krd@YL=JFpW8})=%AqTA1nE5C)Ljf9BVFBSvT##dG0Xc6@C3BEhKuvB{Jf5 zX3amg+dVSmb9hIc%LtN#M`18baQ(WzK&tSzI0Tzxe6f0q5J$e(T-sUjp#JNfRb9;I zTKrIw-Dq=Nz`PAf)+Bc!<(qC@)V-7q5!%e0{SH=JIKdPwg`D_Z7p2_*@n5^ByWBr- za#S<-&*6^hia`0V9Ec-(%{fOkZ+alh&7zNc*{}xh9T3Eu9NS3H31*$NjbHwW6OXrd zRSgo3SHnsi(G~QK1+=uQRjqb+RrRh#es`N((#lF3m!wc#8*U`)UttXM;CkbVW}F|# zkdQc?5P^(HRp$1~R8AK7GtPCuo{!bLGY%>vic>`V&Af7%YDtwD2+;lul;3`t3O74h z4Vp}%)G|=)Do&hiB{6S0qSK%Y>E|rd{12*X<|%}#;2GDz^J;d?*86KyoE)&IKm9=! z{E12nnggNF3CfpN;3pEz0OFbj5lc)5w&ie1VB5jUCWa35^Lp1W@Xbsy`jAY$&*iEc4QgXAR~-<7zy5NV0LGiD-4qWXV5GQueQ5C} z3VV=5zBhfkLF+3uk9C7yd&o@hrfQ~Occ+F>YQE~Ga;Y94?56Tt9>-fAYY>0Ay~BYs z#Hu?v^Jhe;Jhri2x~nD%B#WtmsE1Z3*~=M=yQ`{b-stY?rhF_wq6FnS`w~gIMYw;C z^9bUi#-7$gHA+X47hgD4$PP4ic@NbbZT?3ORWAjU;=_y|H?w-ET=3OpJ=7%#9yr}Y z<)DVlD^!OE*mKyqc&Ah)d*FxGyRYSNj32FX*DHF0nTYKPD95f>%5=rd@GZS}?64)IvdHKs`ONOIW~MC!Pz8ph}DGL6rgs} zq3O{R0zFBKZwQr@A9^2XeOeB9lY+F7fweJ=b7@%FL}-!U|DpKDrWO#+N4fDr~1^ru|jw z-Y``q`b0tZ2>L=WLJCuDz?aWSL9TtX^eWW}4DsAm*lvcHW>>2x?g4qQL96^u$#uCYiLpXnuC{HYff%8DKq!=R;~R5v(4n*>c(RTt^Yrh1A_|EKn?q^RhQO) z6e1yZPYftXjJG2dJn2gK=JM;*^`MB?u2a`xbsfA;Rl_|gUi%m3cwLR@rd;6Q`!8rR zjXpLxQUX<~aU9}z!ULSd$0!J-F>Ov-a3#*J%3rR!LB5by!_z(D7!Z1u%q$X8NrBU5 z()E}hV?MZEjpW|$|8LrX3jG+2M{dBD^=!5obAzfGJSwXX`9#R*A>o)WZcyzrff1KD zV97!B;6H8Bu;-tK z%4DH^j&d}=z*0IS+dO!)s@_9TLcN!c#Ml!^f17JW=P!C&Hi zzPedGfKGM11*YL&%%`^~ClwY20s1P=^tlyMV*Oi10>0u_^*2DyzZGVh)#lKx>J#5A z^Pk&PP437Ozl0M*J7q(~%#`Fra&;fRP1Qyil*Q;H!d%m_FP3Fl$shZwU;VJKUVXcI z*SB|cKXn!@IM)v|z1LK`Lp6#NLrTG0Rk)=89r%P6Jbfpa(LS^8E?B?5y;Ei4)qy)z zTkA+&Z5rN%N!wxmbQgAxGV|nJpmYJ|HIK=6tF{>fvqdYk0g4>;AXvq9cdO=VEf1X8 zwL&`R3*I^UZrDIyF>CKuJeHVnkDA|QayIxRBq&@5&>x2r@Bp2K!ONg~T+T}GjklCO zOLC6jnlQ23j3^FUre{vyqcU@+W*1gSU*@FSJV)^`{xtuy%1q^EVuPa1b$?d3V94J7 zGn4?|dh^x2Dl3Fj60lst#DKQI9ub^uPTs2;;cK10$Y?+aUvKXIi@FJ0=AysAs#a!p z`~~g{a0b}1-TOUF_I;{r9iU1V)eD>=30ZfsRMUCx0%ylW-GP7;2)=KgzE53^V~1<+ zQ)Sq>xNegr8^c+;MmFEC@^aT_7N9XUCzH4jaNdy;aysx$DXG?9CHQbV2{U!g^D&0r zkIWK&2yrRP*c$6oe!wT1NS~v5)2*J1M;K=7&nozB#V5CBnwjsKmf&ElY!o#0RMy zEla@&z~mptU}?u!n2IHdF~SAow8)?vZ%H-J4^X$Gsrv`O_j1NO_z*_Iv&+2&LSZ^P z+GYeE`)XOqn-8gEUuBpKc|#MVjbvWOvgBb^1Ia7#4p+2P!H?YL#7~SSfJ_T8UR{My zO9)?qFhNk7`O!j;V46>x`yNpbgJErbM5s0ND0ai+rq!eB>grI(7-kmFk_b9t!$BWq zX3V3YsN?3tM^)ombhkpQvMhqk0@gKVJ`i2bj>&!#cuakZu5Nn_q%g!Z`3HoyL#E$9 zR0e(?{f8>Zvb!MEpx4(}c?I+k2PDhP+<&NvzFnr*%jI_V+qDApqU|O&USYJMa3RU+y3A$l>vy!fRTxZDB~zO^ta4&br!zw#PQXD8Bn0K zdG4PoKsH78I41_B7tCtr{!?A!8#4M|st`9+IdL2y`K9KwVrP}*TvP|_S zR9=KHbtYe`VfOqBtL`0B^$8V@d|G0bkkj4vgi@{CdMEjvBWJDcC~Aeb7u|x_4EK@o z1kZ?VD0tRPc|v7U<68Ix7VZwS{Rvg?@@bR+toV+66zAH+u3>q*rxnc5D@nc^o^PygU-^W~F@iFlKrQhnJ6@P|03lBx2P z$~J?af^Q2T4jw{KYQrF_A=D*b~+=?;o$SNI*6B7XHfQmbaH(n>8J&F9qSMHLv zf=kTbi&XY2FMghC*8E$g0>`F*t6nslC%|mpxJWg~{JoM3>wb*XH2(VWDFw-NsU`;( zk=J5C{Pxr@(pI3KxN@~mF-W@9JX|b`U{7)lst%7OXtyus8RsG2t0ka#)O4u1fL>9~Kym>GlXvI2c*-9e@Y65EfO zcBjDuRY4B!o4|Cv3OvA1O&)fCK|m#^*YL(@RV90kAVn? zB+jHh3m$dc-2AKuG||DITuJK0{O+*xGR+(ZS3@WM2pl5cOEZb$*{ZL_H;j*`cb& z%`h@V4?o0EApG55gs5vn#{GCTe;Q~3pTT{fSDQKDh6=z)1c$;B`F`@{?C_WCem zftzM9lnH~84g*R77%6zAXB$BYq!xhrDgH&(`v$ZWLN^S+5Z1tkXfP{G0hMCRzlDpT z8m}RQJBLIJF(Jvt0vQc}c%)^6oSOA?N1%B+X@0+%^`g29l0#smy4JPd-8oWqt&a`y z2pBrBqY;_18qjd-z{sTcf&q%?>ThQCNY#N7a1yfjRelLJ;!;!ZCDo|OQ4|M{L_4ym zf?Na*sX)*?{gUdAZGF{Cs%{n9s-@qyqEH81<0vRdf4TC{&}& znz5==>L?s###L~%BUOD(Eu-ik?+l#`=Z)F*n(Ed7&wl$1e#^!^5N4b!OyG##Cm6Oj z7ykNjs!445;p5Z|$W}ZyGd_s)elWgUlg%eb|e^9nrH9_@20XVX_3PYF6{Ki)V=-FwlG-%b)e;IYZ7O&AEckR0I@_zUY0sKE4~WME+z znzKy<)y=NS@XC~$^eM2HmYFN2fO?Rrbcz~MwOn~Q>XQI!YdBTtYrs_10Py3dswa?G zBXb&DqZ>>|{B#|WQJ4W>Gjs(-kLvI3b$m)tdZ%8C(jXsYsQ>I~2ZZHd{ zscTy<%PN4G!#(dBMvf#dCGS~!k`@jQC1rMtcX&E<=U0uI0n6@3rppYB zd8v7427C#nX44FHWk1`)ewSlvVa4m~{(6xUOe{bJ4JPQJB2Dt)=_K!=p1+@za<6~` zEKu09&SD+oG5WoP#wSkfa z!679Nyr~xWDv!@bAOUq2%If1y%eUe4JY`0{jhXz#yz{ntFasfPqLL#Fm_?5=pwzUU zBSzOh&rvt@0;A-I^ceOn+<8(qS@PA6mZ zJP7!+Okf_C&=}Kn9z4!N%q{cOr5)in*kWB>Zk~5a+zi@s-!f!MR825mXWQ7YljN*G zfO?H&d(Ifer7z4Kp zhkPqr0Xjx{#OzB(3UlaPjB%OC`;Ynq18)vPE=Q_{_dgRAFy=lb^Y8zt@GUOHKzNNj zh%J!f&~3yT70H}phROTr4NT&;l*HsolDmllE%776W2E%wZfWz|e^fWd^Ali11OWTD zlkP--`o5=Xd#!}(62UUKIBXkXCcLNe{wx`xptBf8!dQzp(c;!#w}eHzqFl?lNkIvP zoCc-sQ(QuZ8#lccBS(Csu86JD6uhr;iX;J*EB><*WW4{KN@%Aj@L`A`oTb=H7^&FV zgiP4yyb2NfWU)X*Bq+6rbZ3gs`E3KGB%AJm171Y)R)Q=Y%mV)drv%ZYM{#}T;bPUm zGT0j|EE^Gz_yfX8f~*Ja@et?;&K9DfInkA~B`_y!Faa@u!KFOJo6hv4aLOhsc0Q(OD$5|e| z-<3uqA~F;3t@Ao=J6dO%XV?exDFATWcnP$pvF42uRX;c*I}rF=97*0e^L`*W#q2Cm z{W4}|2a@8PkrTp!0NE`5fPi$*aGaDTg=1-Fd=_zO6=2*_UXg|aIP(;#)qm=b9=fY)goj3fao`L zQ$3p@^U3LlLIwq!*-o+B{qsXuxVD(dA4+u}f2gj+Q|dxUe_PBA3!%$wF#{K>zc$>* zG$i2Dan2{a_r=X=z#(&ri&KK{Bx2Crv=}C95v&>;O@~Dgwl|vF7C}zmVx}z;oCobs zyN~2)&_|HtwwQN5Qf+egCEAf1Dl(!QMhQMhKC;0ieynEJqi6{uhO=dvoPNgNec?vs zyMFHP-OfG=rr&%F8{-tya50=lGfmNAjL>%_(-uSAqqvFr0Q)^+b}Yf}_MPdx6ju1} z%tQ8P#!_`F@95$YG8{@o?P5`eBUpc;UV|O9Fu{2SAp-PXZ#D}*QMH;v$-(}8H_ftG zh-fn7Lm*uHNL?T(Nbs1E5tO#2P~}fmjY^Z?6h>$n0?J!{Dh92;eQE<1Uj7uVDmA}; zs-9^3-*y;^1nqLpsA(zA2wJ$Oeuk+-bjW9r9uR{68HVGGah4&#;GOf{pVLdDUT z*~?Tb`oJO3xUwoF7%L~r*;K5gO!N0iD$7(|uJS3>O1Z>zpI=O7nM_H8{hJ#r<6W;` zuIf;BJs+QWak=_?{@5hTRoK)q@y@QAc3XPI+XHexS2b_NlEUJdJjGo+_ycMKUtzzp zRAN&p@fExo_La@a5}4qlQ7gk4h<$HMoEQ7xg*}+pK38=vkJu^@G`Bmh07q(y!|st3 z6a$g3uwk_6plL8d@LEfUFgyDMu^h@tTmeDuC?f4RbT5c|9)G3@0L-V*<}EOSzlNCG zR;XLKZN0HF#lcMN`bcD>*v)m_3Pd^Y!JVqIQc?5#X<^j;8`xh$pb!n=eM1;!+#Z4f z$AqUKh=;#k<@htUa=$c$mDAV;Dg-sgGi9znxF{Ps7HB(xh7-}TL>5l0Xa2rY1t5|< zu~PMHhgnR*8o=IzHNfM0)}w%EQ_9D=%0$QV>Ij8b@_x<}wu>D$YczyuY)`|UM$b9!X2vRTz9DAyDnwO4@B2d4 zVjwuntnfP%m$(9ERH%W!D4yr_IZm+4pq(5j7*J`7z?oAJH{woh{8j+o0}@Kk1>Vg zQT5Qsei{4{tn>Yno4-_Je4#aoqRk67;DaH>w?@?pyQ8tY0+Psxn9J9I6_l9)YvA`d zYzD8vu@6Y>>(;3DFhDSJM9y`jfH0grAUH;T9X7STQdidj_epeQo?u;p`-UeR0?D93 zjGIJ^3DFz#6(V>Jm_=WyOPRfcqKZ4-$#e!W-GUwOWR#;nj}Y9{Y3lpUG+L`_dvp34 z?veUQg0j-1VGD9VpQ$oI5?m6}Lhd)DBQ)k%vP~w>iFh#v0oJr7BJAu~84}YglvoWy zlg;BKD6kC7+O?`$_%QP864C-;tzgZ7eN*VC5pJ zGxTc&`7J`aPpo8!f__NvNV>fEYn4~DPi`7d2z~&r$WbB==%{G0XEo=WUn5v;-M_k+ z#cO}`|^j!?fxD*wVuU7?JaHLWxO-@vw+=-Vk7>oN|^%f3N8r=JDFJo z@UJ7_@S1xaL=AzF+s3pLTZI(pz@Oxxn;&N<3Lrw;(K3&%hkdEkOj!@b z@Ra##z3P~82-A=6SQC;SL`K!*Z%_rPiy%fjgBE6Cf_iRHUG(g95gxmgjt*hYhbe9;*ra*$wmmFLrkxYDzhKlTJTUH>y!%y+2Mb= z9+gLIC*Zxp317IR(xcI%X8A@)a_fw5ld97Vjfyd^(-H?Jl8O`nIDn213E>p9!b67) zzw4l503AcNVp0Be69n@;=J`!Hq5Hq#9Nv~MmT!&;O#g6Mcoj~rP+^2*gxPYL7UCZ= zC$xxpV=(RUDL4R7|9e&E3fPd7WFC-wIs<1F6N-ZSDhoMz_}_#B=x1T%fmmI$NGw~R zi8%2y?KWe+S3R28HIKJm5i$djy0Gg?6JQm)pSPo9wXZUjH^Y%X&UD$VnpASt8od3- zW>u$XP8zmljB_&X-uMy$dPtytfAD!k4*F>O(r019I|K$lhki0zH^$ThU*|!6YqMJ7ZmKr^LFSDg)GbBuQZhTO1BQ$aAB%(Y;s4KO zWl6WCUF;L=P@f@Pzz%Mm<9-ws=<^?C*nj#_wMAn4{B7#GM&Kq;zA)8dg-OJ!_%)l> z4^TZ=V4`pbXWXG=#=V(|+tedPo06~u+&79-zHN3m@Ne$&K4%5O5m)g14kz7tYfyaf z2SrJZtO}nGABd0n{PHuY3AtGQzrtOP?!{)x5I{hHsFq$KxOwH+!U9#YBROJPohD@H6h0V8i^#myroI*Y8z5i(=o% z6fc=waN#N5{eLl#<)#=TnQyx9Q=MX_Z}k5KVZ}^e$-#Z{G45XCFz z#X~AH6MUJf1a~TMb>Y+4>aY(iJ_P60H>|S2oI0esXCsRtd9lwaElUM|k1_t4+YZC< zF72w_bzoT>*Xj9flQyw65PwKNOp8eA1}XvC7mH5XH_C*L{9gNwhKYOn%=lxfPK$xK+e%`tI938{Z2^F5&vnY_1|kg6w&^L(AtbId z$BwCoI$&2`HZ#R(>a0Q-2O?QSy z1gugU^V7~#0APionOg+-q`=ehlK6xZQd%{ljI-$5K4;joA_}*2B3BR`ELCADJ_IO< z0lWnvN#bQwzW^}nh5t&mw0EEd;3g8Sf5F3NvK8Nh4kvKV9$DayR_ zo2nHYS&fX&mRW}~X7_KhC529?&Y2jF^_6@nZqQRD=+;79rRFauREx{qmLByvC%vvj zZ*^b@1Tiz>!E46J7|2Mce>|bGTe_$Q`Y(bi8LN<69=1*NZJ3`OZ*)>M$&2>!)tItV zJwx5&CsivB4z|)DHck2*h+rHwLa$Xgp&4m^ba1wvRN*!+i|-754vP`rD=|B!1Y8|5 zH)$^FH)z>xb4oS327MZ|2{#KTI3K(w31Jd=aj^x4a!*3yF)jyZH-b~(#&JCvEVC%4 zOr%LA|2d_0@;IG2t;TgeU6peYl!Z<|0v9~2pwhkNqVUGa-z)fcV3MEq#zyky8$V;{ z2Aok}K|poRs*Jk81>PaJ@GH3R8-ojWEapjg@+@}i6~>%JUXU-$#$4m`+T2BDUt8t;8)(K(h8qyML+##jg(@zsE-Kixt|t~+GIO>#uZ{&K8=%+?u$e6Vk)kGu`lrT zrAc@&J$?EbKenFnK3#z6`Pipx)RlIDar&G?v*G!Vb|L8Y04@Qm7cB{n$}~5sDd-s;y3Re2?G}9t8XiLmk$h{VRxP3aFTJC~-`fCFqmSZAQs@R-_3IHRphpwni3$$Kk?-U^XsEEre8R|Ipq|RV;)H10#h-7KdGy zLv~X&crHnz>$qMC3WgK`eI^F<0kGQOpk(|rIJtm-<0sSpWZ9o=`6-O87tZGkApi}A&#`F3pH~XcAv$?ybhBL|bQn99(zK|iH z_`8S?(0IVC(G6cLxg4p0HDUlJRx~><913T+_f_Bk8LzI0X%p^}D%YlH-%*;~NA0l2 z7r}luvCxeb=wU?>4*l6k@z`NUQB(`=6?bJ6^cf@*^A@gmz`=H$#^+QV@40Xl&#_!k zct=bf=8IIF-wV4ntZkVzgy1)Wzm;S`yeeCFW%+xQ{2h|NN6X(;f*arp2|mk(`Al+H z1>Z{z>)?B)Z9q2yQ@kgjn|FlG)H52hqpOjO9EV|HwQKEosnD#mLYNl1N8{x3UUr@;b69I{c<02pp z-$rSpfDRn6fH--zU0wylRpn`iJn1lqGCquiA@O0&f6{{`=8AOP#P_LrJYDBzW3@v? zz)}{%4yS`S7z2|Esd#QW*v6}7WxCG6&+c?28+pAXrLw-lcPlhSFTVk@G9z;gXyYaP z!uS?1YXa#Ga65g9&_4f$4KDI`ddz#qgKt3-{b3D;R5duwyjexp=iwX-GUjgdU^~&! z??LEj)PX9x7VjDfN++kv;VmtHPm{kxoV!#`Wh$q#iqH@YWXYW3YF2?r2?X*mH&h?S zrDBe-EuMyP+Tq4DP;7k9`=DU>yTFsoch8ZV;*c3O!EjuCf1DW^)V=zg$)<$jBJl$Q z%0ik$3br_IoW4Lu$c9{Rvio|1T0|gn{R%OHnrRx+4k%$jNay#I3K6LRal$F4cniau z^UI1t*w82Cg?-fY@n zZ5EALtaP@nP^JUx`UM}Zhzvpoo5ymtdBw<#SiYW4G4)7GD%8_k+j?S0rZek#QtPZ9 zk$R*%38)8+O+kk$R6W?EC?l%egCH=4lE>iF!N=|}X7+EOaqu3AQ6nn|zM2OEYZnOGvyjFFx`J>Oup&|C#e4=&Tc7H>YWRgboBC+l zhGeEa`SO%2bbEW2dEqa*TCM*YUyuI@UmDn7z+ID6)VK`nV!2OPY8V0n+iZgO>85?C z=8DM0=CyUs4Rm!}uXUmluPG1F$ou}APJf-LYjwcDMjPsK z2CnJ`)am02uylDTW{Q(nZjOd7Ekw%2=2=itz-*hv@c2l9CNQJjm8BaL{XR0#gdp?S zxh%%yuJLv;ZWD(2du_o<3%2FF;zFb>UNtY!Rp`cve}tO^u1xX;N1B3cT^-y1f#-DI zRfq(b5Yt0IbHc`aRv3KK!y6=nh3rDWj@)3{m+Q0pr5XBs(=c7rEYH@VTIhH^vU>Mp zWCV#H-4^_&EL&&Y`FoJ)NV%5C03~67B0bE58Iudcc5rB-bTGude65cuIu@nC4V-qD z&N46N=%#uTL?A!CGmvm-!u1EgGwX76b^Sd)V2g_Ru+fy|=&aPp>I|AgVcpF1ssSGM z92~47K0rGSC31Ks$2;GhgsOq))PXg0KGfc@g5k$0~Jt@Ou5#U-SI|3W> z^`$K%rjrCZfMGg`j=--ME7(1(>nie5bdB+`urZ0Pq&TeW@s41&+F!0xOuDUeSy&f{ zoja_r%)vl*C*8aBP#EPLVbV{BO2rf&xHiqp1^UjWu;U}h2+D#u&>`C$OJkS`YxqhjsS*Y(qJlBvy7;DPRyM_8n{G2V+9UGU5@(JL^afqz^**Bm)eh~tF z6bVGEO_bFHkHg9qJZkQ(sk`GK{M?$lcD2JfVwZw+38%Rnk;T+bGyCujM$up`-OP`B zuBjG5RXo&fT&5~OBuZS0HQX3S1-1MpqwsPG3xwh_6^*Aoyz}ZG-2B5tT z*?NP>lJvI*dY741M{6{CVI6&wudGDZ)#Gv2tYlt2%rbCos;~32SUTQ$Z~%OUAVxuh zp}^Td6Kdchw{Hvi6GdJIy!SWMe<;K#+VrT9#^AbO#|Nc@A~^(^tRV44qH*%Y+ZZD8r?(MorL-uhfConP%N zd@GoYfFdCsLIf!1ZkzeI6_n4ZCeT`E-Q?B--^tRsl5oN zd#W8ehIvCs3hp#x@g4)Q>k@r=>}N)v@pFeZx)qX%KiEdM@a;C!+JN|WnJ?Su+I5R- zfW)JGlTCj%!TG+dBHzqxi~4^wx3txb+V9}*K^q6=4izK@cWPLAX-Q&n;}vH}iTmzl z_nleXR%dnKX%4x88ssi>6#NP4IIU)fD**gf7YZ8g z6W~oSYM5T_bQT4WkqJ0mPqr?M{iNm367ee@#4f_cy>`uvclJNSu3GzPgr7&@JC=h- zs84zLa$7{W9v0llq;e(RsaWDm#5%4pmD}rTyvB`vMPf-8DTb8ZNthI{)5xDd*afZ$ zd$@(bA4h=QO=@%mAC0{PSr!4Y_{f!*Hq1pT47fbfRgn@dQrQpf_8=<1!)s%`L0jiR zgBf^R5m&W|@1XO&MT{eHwo?GYWv}=T#*9uKbb}CQ9|h`BX%2RMYy^7ydIcU9d(DQ?iS37R`KG#o!V^I0yj*2FA4n z2$hr96k<{)8I-Se85YjX*vRd2L=FTjNV^ko5KxS1Bt}Be#7jJ%@Z*k+;7cK7+bTe- z!3A)=g89p(x-R#FmoC+}R^{q#$A)Z-bEZRvf$IN^8r@klI;>V_U7vKq_ut1kFP|s+ z1s&|LFPovAbz6u+OFQevceVChIbNwQrcY_cH9@&sHuOdYOLGUu`Jd5#8yqN^^qU1{5I7 zIUVsDCcB%?t#njop$Fu)F5Ps0x+LH3rmORJc{hDUh%y}=tOx~R8XLrZguClTNt1aP zb+fs)yZ$Ext1r9j`Wh}$n5Ao3lO*^aLPktV58W}{N{@o&*~+F*58dDoLo3UHCT5Vq z`>;rk3?2zbE8}B5kb0zIo@8SM^oc>_s$8ii}zasB|WugAUq3dn&)*Q?N z0;MYg{P=Q;QwB%pqzXEoxIf5J;*me>)oXP9ayp6RUSDm#x<=RIb;dw~*obLmt|FA| zw8O{9H!89i$|OFmiG%&&0180o&c9A)B7lW@b~O-}^EB8y>`;DZ3GAF= zRT*iv@5#t8_g$y6%Z;$*kY?0%R_g;|Xnm0phOX+3u=yTEb0a&+5?D2e+pUUYC6v&z z3D*A=2MxIx$#qSg; zmVTXAq(K|^1FdB}f3KD9kUR~xAY*d)2HmRZ#S4`i$Jjy*P0@|IlI5o5N7IbE5$>r; z2ACV=xYNL9RUCD5_C{}g*2@EGOAg@+g5G{pIZ$`tE(p3L3Ytc&cEF(CSGieNZ5y#3 z6~msuj{}7iH~uJ+hG$?q&lvQ%>3g$o-fUu?7y)S81+(cG_Pc`Dmcdvs2xdG_O!(+# zT`PZD&fmfX=PEAV4fLaL(M>6w*;q#~kXYf2WrpK0nP(*S#7lGgEfBFjuVJ=7 zuQSZ7TXaT)RW%UJ;rzrL`n!CAf_MtUD2{z{^-OV#w&PwPOk?*gx;MEBv9CgsV~_2? z9%TV~->NUGe*j7x#eFwogxZuWU#kWUO_bs<@%;C~TXkn1V>^ATZi;=h(QUdujXu`1 zR$j;{-=PZDqM^3|4>n)n>BJ-IQJ&YqQTNXN#mS6#?A#YMU5{O13O_fYywLQI zIx8!JLflj?a5H{}>y2KZ?eM_mw;+Fk@XBh6f5 z-B6A=xnh74KOvBz3oCh0?0clIusF5(+wD5Z9l^xq^7@6_BV(Gl!b9MVav~1@xJ8r4 z$Dqu|^?ny@j|b%RgVg3V#(T+mOI#}$NA!!<`n9})OUbJ>A9ry_uwy!epNOx@Z9@7T zI=fyh#Qak+-T`aHk9S5)PCpE zv)cfcMa#;S74O0YEAf{HJl{VsC&oX328(~-s^)WO^7FbCADkP4_~6F1Ja;77$#N!F zH1|Dsm%jAEZu;TlYI3!CtArdSvglV}Rp-SndcLUdE_!4lQl`V*GUwrYFrb%^(6GR@ zDZX21w9lXMR+ic%c=bWFe336=g`F!56vi3<<8m#w4Y5F6V%~&a2nZEci@IlB-X|{X zF)?ToWzm`b_edjIySu_B#=Nm>YhCnBzAP)Zb~I6lmrHeeEb7Qg)5$UKWyy#md0s5A zEH*RY&(U^32J{wJIY_&>1jC{j4EeIeF3_e}phQ`9mVK0a4_6K<8-H)KdSng2o3Em8 z@{zqNcE}?uc7@xD8h_DQ|Gn!q;>z7xO&_F~}#YUfwq8uWQrYb9fUCP4hg3KMsPwC#yWzAEp?KJalP>2zu{_NlMB>dF=yZ(kO!?maRv97m4q=PYS z&*YjS@Mk=z3opeWh}%UjafuGXeB?m;HpM{<290Co)jIbZ;#};=n28VSHyskrnMyl^pp7M^pL&* zKd(HbJCPhv3FO-MiroRhZEKhjE4m(d$RmeHwa&x3A;n!b?H#|f_dM6ZsfTrrnfkEq z^gls0qW%83AH;>ZjhR_%7j~>Mi!;Wm_QyG^<%j_0S%BSHc7bJXH@07sF=KdS$zOs+U~D7e`2W050V4@vnV zg#4#z!5t;9J*Kz%B9Er%ab3f|GsArRn9ef`AJ?g^1rjF=gF7=QD=^m@{|5WOABfXU z41N)!<)!$;>JAPo_IIibfUD`$MY3lwHVfMRCuYc;!a9h3bPkpzoDml2rocgE! z13)_e3+~*V=J|gi!g8ls|1W({dbFlwpz8jFegI$IdqOv^vNMA%KtcsX+z0XP&=a8f z-6rKp-5dCCeiFQIni>A2epj1ZwpMzLwU1Mk!LcQMo=8P188zq(y@pGKP9UE%0;;DFHQIniPkKhX zYcw{lz$_Y~b45cWt@;3mgmkN2Qf1?D0Q!g17m6oH8>+J@l2HO{z39!0NOi@2rxYX_ zRps0lo+_7Zh7Hw~iRU%otDwobu%gMW9(tbs<9?=IXU<*pv+Os`pA%W+%I7Mm0Yl%Y z84VH=HQ*4Os;(L^f`vp4C@?FY)9IPJnK8R947A=rV+y9ZVtt(kF%o$(!g$`SSusoq zDKQ}Sx5T#AX_#P!G~cX>Ahi)Wz~x5d%pImXfMWL#)6F?HdC%*d%t?7z{pUx=tT%n0 z*L9m-%pmgO5S~7K{XBb!_{cZsijN%kO>BE!*Ke>fH)6M#2jUUC1yzUJ`bX~TD_)71DQcM7uSyh4uUB>LhCAsn zfu|5V>rT)(S0_C!&d80GumTyfz^5h4@MI>U9Je4g(eJ!(MLw}(V45r!-1(}mk8w;N zrEB4*#VFmcXlJh6poUXAOT-|B`|olCnKW#`b|`O_#B~ii(?6^k1gJy?L_ZL=F?|+8 z6PzQ4LB*K$WHd_*>y`(A--&{gEOE#ZaqL1j6IEuk&aW?)Tlejy+~Uu(bN!qf_`>J& zo3Cc*YUb~wbsmi6FO1e*Iq0kzkrkg_Xi~GxB}rMj%L{qDtrNlYDHT!21%Vpwn0a$+ z2F9QUFf{lRCggG48RP{Ks2-foHyPcyNR9~72@#wd#E0M&5C|nCTply+BTl={#bFy8 zO39|-B;noAGmdYF0JV^fi4iLKpGsCUfvLorb^V zeQ2CEzR%2oiGfCD{WyJx|FfWZDK#z2bW6|5HxG{2bsKz!`{#UtOE^Yw02L?5MnFfd zKRAyQpF*cpvHyNtrm=Lqu9I8grO<|e#1kZ8g8qm91GkdV6R>}OU^Y+C{c#Gu`$XNG z6ujV7oT&9Vdp)+{MZB0-ChE(4pP26^>W)ZIm@x^5F-py4lk}#{Qh2K%IwS}2N1P3w zf{PV#U#%uXiTMO+YjG(%GG{@SO+kp(lIbP zfu<7rjzY_7%h^UE=R(3X-zw`CrHrxg+P*yg(M14n52%{Kri-%OHN! zaTa8xSIv#HbQ5oWkp*w%EcZUkL`;*HVa!aMrCWJGvnv4ogjJ30WSJ?Qr5jx7HuHmv z0Ev?s2c7(GY!Lgr@FK9L|Lc0aSL>G-1G;^-&S~q``t?PCycBSKssHl^16KPT1J&N0 ztzYylFkRo!ZR#v+f-H;S)PVI26J%!uAKcJhHz&OT^=7L1<_+D!Yg}mr4mpD{ZV_(V z_)Qx)Ok}4j07Veq|E6{r21y`9CzYE44^x_?$`2>^XZk{%@suF4E2Hb=`HPe@{pm}Z9Jr<2mo0PZpT~%BZ zi@cZrdRy1?UVh`feCKT)_FiuEUT%9^*JQ5J7iOk_r8&QT=(Zqo5Y*()(M?#45U@H3 zgy}m+H*Da(JmeJv(?rCEB(D~YuAQ^Xo5FYI=&D}bJG{Er&(Tdgx=ml|zD#5OO)_kW z8?=n}{=^crvL+fN+coBAysYGbNyMC-n8X8hyXb?LC5gkEj znq7V|OYeQ$j_z@X(=h-ZI&)~Q4T~U#feBFzk=d;~PtWu)yqn;RuQ;t{&pe%XncMTv z5}d`J(WuWxz#BD~=SVWA>q#M*eF~HK<8z~`?nNp%ifGFU^mn-K!Nx4Ija$xi1 z^3euAVB2y$0#VM?MT|B|)j357Y}*y>2t}YK@F4!v&l(bn6U%MhDdk& z8*PL4b*pBq>_Q`hfWUI4yo$^V@9PQ8Q3_%r=DOm54)tB6Sl_PHQ#fOBSr0A~_SBx;&S5wc$%sg0T@(jph>f(XceixdH2<&mDZ zETa}{HUbK7d3Z4?7sal0zpp&hz2mzpU0d&=wQ0Cm=n%DfUkqR|n#gBA1Coj0Br2B_@sUK=& zrH}MOm(4`P5NC_jN{uCE!(anH^*eLDjm6tAT+RU5nKDgqruq0Iz00@NO#2vCrLAW5 z$9j<7+9W;OX@nT{NiTC7hK#+{+`U-;8{c;>*3aPQ-X(fiJAh?@Tmgp-1aL`!Ym%Oc zyeW7!as+bkyRWJ{jq#>*CfL0Lg?p=MuvA}`A;o7Bb`%0?+%E%{!g{{d%vq{y6Cl$$ z&RF>a2M8eT_<1Qr!L26e6TJpM`##Zq2$$`=Nkp@Ed6k;ppXy$<@uddrCiwDszKftq zdXCc^FBYD_rsY7l-)cVjRJXB}t;GF@&cf#+tx5k(zfc4uInD+m8H0R-Vk(jvXYtj0 zL!+;FcmnS~P7|=ugq`vK3IkwwlXV>eeK%45uZ)POs!F2+k2J^_A`M zK|9lkVDe%%&61RIrt{T!d1VJEW=L`5|wwl|%Kv>yU^ZXb3Bz~U#QjcTT6m~g)4?0bvO_UJ|LCL@? zOJsayy06yLn*ccEd`H|vzh#>(aM1aN4gBI;4h<={NqQBhW0ipU=qKIU^jf2<__mh( zX^no^i76YJhGHo=ZWb$Czl>a}yxR#kH*5NgZW=&P#E zCPF;{bT((4%{0HPlM$`)wZ4Kw8HA_-fY=sRLH2XJiq>t0f2|+k^oMYy z0AGqXaNO*Y2|C;O>fO)SDEq34vz@OtMqZir-{`xqmhwCKmwCu3i0agp@g!uT*+DPt77hWC7?}kgWkd+)6O13MM;eWxt2pV zM0ll4js8xzw^Q>9fj7@0N|}H=zSD!ro+~?Vut`()b82j5={PBkSA)-T9__0%r#;>r zT$$>=G2xB6DG`Hh6880XBXfS&Mp!<#n%6h#5Ak!?Cf$H*In_x9*r6lj9MZDM|Bt=* zfRm!i_Q$(AO!rK8Lo-uV3^Ro82{7caNEW$@0aQ>|U3GmfDDEnlaDBV0yXy>+a}c-) zs3a8w0xAwF5>${NK~O-+C^=^&{=Vl{Rd;oD*D!NgPk$4)SnpF5;luzH;l<|nx%yw#K z%5sA=cw}=%NBV-W0ezYOp)tFR9f-1yRa{E3-|2K zxS0y1urX9%j36lLns?cqF&PD%d$5GJg}2?4QBMPLF?DhL08N!PdvJ*8)#m6-@R55m z1`?5km>(Y&4kQSL@T|W#V?7#k@5{KEzE@-lzMor1lk2BGAKI7E4d1`qm(iBwdI)>N z2kp;j&fD(CId%}#`!jB(dO>y_>Yd+jh(G?*eqpIRd;lxsTlfbDu*klJUpkQS7?rNT zu-_BcJP{N~581+>KbX-K-!~o1Xo-h%NR;k+DC4g*z#sx;DE_gSMVfh<*m?9yn=nBP z=MQCcA*?|e$G(UWJbgH0JQ__qlJO26bB<`ln#Pt8u@1aUTvdT!$T%v>y?->LFUmDN zCVs6xmhk|=;KSkOC_Y9M*Ce+W2XErl=W7X90+)9j$6^|cgX7r4y@iiCp0N>+r%q%% zM1Z+)n*@sQJZT6fzWoID2yNjHoXqG#U3l4o804DFOkoS@)%ouYF~N77M7qQlzTi}b zipTm>NKKf*Z#b>(WE+S@;I9z{N-&4}pU!AbfePz zu^)>MoyWAafM0^p0%ih6x!gU1^#q(ZmgB?`a>gE)e^8P)I7F|ZQ{ zp6aX4(SW5EtHRbZ5%kim@(-%J3z;OYpdiHUF;$Dkb+RsHJYZnj(`!_CAz2j!<+W%DSrZn=5W7%t$r?$uVMM{yW?gUuX2dgR8}0AqBxjKJ6;&^-C| zd$m=!y<(+y8FH#AFhp=+KPsj;w4%6Oiu{#?JAgetxhu0Zz$0!9!CisI`X-%7>}-cgTsFkK1nAU4joHT6;V+rLQXEcO z{EP^6u?eHFrpYh1kSkw@HBtERwe+y!(Xq59*gMJkzF({dF3$B=z?z|h9X{nnCK2*s z8s}HZz_Q3+TFQ|OP+dMzUGzH;Hld~52q(;b(^77$g52}9xTK5ab-=JdjiGrw+{#oJ z2QX|s>t)wce#YIcS{d=x;!mOVAnL(LuM?a{Zd<0At#Y4b3ODF~xwC4ImdP{GV;)tSVX& zW1P`CXzKkovN{$;FxKoga)-FFMw?l}Uua_*E9%d}Sg)h8qJ9qQe%nTV&=}#@ywkPO z5!U@0cTUp?E8^${t)5616thaQevE`dZqXhO!FWAm0$hj^559H+A^P4RRw7I;-yBE2K~s z|KJ8W9T}I=_K}P%+Rv?Dfn0cmxOM2vAR&p@T*akVX(*~-S)-9QAaj-Xj}>D>#TNiU zsLbW>;0x_m(vVz(a)lg7C;a!#azh`IIN@`lq;!*fXUb*ftOeClQ(i<(>lOLJp%z_L-q#logv*g$IfMU8Fc&Q5Z(Qp4gl zRZjtATlJ3Uf=h2`4lnE^w~vD*#ZF8#ST1yu>&3xRQg!n;H>$@5tu^*^ zXSuaBjlbJj?$EEFaE-#)9B;IXKka5!#}UIt7Pgr=mr= z85)OY6*wnJIGW&3cgT<2YII0PGQ>c`NxN(zWxq#(g)4>2lnl^uV{cq(GTOKB;GOat zDO0oqE9TxQhn<^nOXZ`^>iq1Taz~`;+;EqCOGO-!A*czyI;dg}AAgtpSQLtsxM8Ot z4IfZ=V^GlTq%kOj&liP>!W9I??Z21D-);=c6*nw8nkv;iD!L9Y)}9Nw_$feMv`KQU zsmZ2y%RM?8O(wudC@&j``|g$-X?t@a%jkAtuolZ>3vf?$Z*fX75|G?ZSjhU?J>B^w z9HR}q(>-#FEC^4WjE0pY5>Y~iO)|bux<}3~>|KFQ2npzi7L&U~=O&9aRUBB1)ioG4 z>nV$kRut(R3_4YC6|T!VCKM+Y$>_8lZL1Sy>7z1;#+L(F@ssLHqCS3!`i}Veyw$yO zKlts7?v=+&Zx%oJ2YH-R`jDTxUw%02U=11*i4xG}vp8`b)Ef?h;SLt}d_Yc@q#4D1 z{wUu>i(xDNOa7ZQhhP7Y+)Mhf_^XHH$`S(TsGh}^{*cR7&B3J;2;PVnML*`U-F-op z#*4XlG2i^+C|*n~HdUFvDVLr6*z{sMUi9m4YGd5yT(-5J>BXA6@#Y(IodH{N*);Qu zg?RC?xehjy<36J2Y9Md4`@&OABVRnE_*i@5y%E!+W`;{qoP3hJf` z60{mUELRR|_n=Ia=xA$@cB~nc70vwRVVTYg9Qv@_i4Fjv$_HGm{~kS)&U~?^P?k7J z3PgnS1BjN2=uR8*fT`Lsy&8`^BFlAYC02M}MAaf;fhr6!S2$}_=<ig|JqHiL1{_b zCi>V171?yU3Yt&uCTAi8`O|K|DonGj-Q?zNw3~rQypm%yYg!su!E_%~j+eMd`?znR z2grs;tSTgWJP;yV^Rpgup?t0e{8i%j<267E zG%ca^#hspz>r0h4(Akn%h^>V&DC8%DnMrZ*QBTSZGN2zFH;Wl1jB7#Ebx+DUe>`6! z=*ki)6EwC%*i~5@NtMu|K1@3V7po}l>-eZS^uc0^Q5b00ZY@d|PvKspiz}fz5QspY z;~h`QF9!)Mh%n4;4uCoOl&nbGi&LJKJrWI-?zPZ{>JvllUy1CJ+-E5Ib&e0~Dc>Xx z(E(_~f^2m@fd-|jgC_brx{dVXVt%ft+_Hi`MZL7LoIMMVbTNPMSvk{hAS?~{z7#$y zzgtJ!Qh@NFSum#(JRo66Ljg~*famVN$}R~Evc_ee;Pct!5+Il*uWd@Rz<;J-|=I{4H707zT-pnu343}}xA*`5Lr z@YByj0xstLp9j6K=WjhP=XgnoJ3`0#n&;)Znfe5Kpfj}@CgtZun;9?24P!G*$QIc@ zTlhUM$StKo#lv5aizGZl|B}ZzX&291FJb__!+gVwavnV|>d)LljBh=Epimx;(e5vl zYy0|yBXp(T8jf6anKvww8yk!_vI3id8+ZVBk(>><2N%gjgq2_~xkk!>FbtZ3#Z7w2 zh0clt!%zmmM+6s|FsUP#`pZrD@!oP1z?9!duIs|{&i?WQxBRA}-=R(;gX(aFUJ8#L zB6q0_QW1B;f+r}p9i~gon<5Y5??dE9&i-Nk_7J%ZJ@@F({GoCkjO31?a>siG-q4tr zCV-d(e{05&_IDcH6pC+Kjaxs66BbrrpU~)VRYkaR94qYI>I&oHh2o<_fgp0w@f%WM)&T5<@IaTWm@0QB8CrFkOlRX2-#AT9m8u&C5}A@9 zyQsyDO)d_cRraPhPg21O?ih?8p!*+ zEq6sH#e3eCpL1h(+k@}RO{mim@5@z*NZ)^7&ZXb$@RZ)-XWoY!G?mwyDOZ;U^P6VM z^=~u)a$=6?;tI|1Byo<@W(g*|xrtO^Dy9XFQ#e&zB;#R&cDh+TY}3Xt*7LbD<(C>l zaM0*yr?9zu=~7(H>cWv~LIw&!bEr3LG04EMBQ&k}@mcZ{9t>vv9C;?f^~L>)<=JkS zL@nmYonZ}}BAU?EI&Z&!ugULA#Lqu*Kq}6B$#+gv_*AbfVdz>VG0;~P`e=U{&NHg^5 zQn@iH$U{rz?8e%ubpa5m*eD?OwPP^~C?lgpm@=VhVUQcOI*9JtYq?B*3EFSoGDJ1| z^PiW=_3)^)96$}^&6msJCW!G69T8am0vMH=%@eOzsZHDoVzfDMx!j$I=G1cePtq&= zo)y6S>wLrtxvDgo&sc#VD`b}wU46GwZrTvM1!ElJhAj@N#e@UIFuVd%4;{&r zjgEaAt}fxtSIO<&kVYwd*88%GbIQ}66XYbekxp3!%ND)wUnP&pF(!`o0bvJIY|h2b zpd!p&@#NLATaqU7H`mCw<8gA0{6OkkVd85?XfXf7w{lKytQ9(M&X|4jg&K9wNz*uQ z;E(|kd2e^ouo5Sm`m4|?8KV2(d za@H58sHZjqJ%Cv>u_3k@Q|t7b+83;I*n{1ayS|qjRirBVtifnl&-2#H_l4Irply9p zM7vWEvqp9VP1|7?o6!JTWa4@`OC@VcCX|Eo8i~)xU1;{3^>Q{5+`jd~dabxY?n=q; zqYr!8OHTHkc8zsQ7CjD?57{7B^)AQ*K5!xS%nkB=(sJ(Gh-mV1&Nj-=;qmcCxpPI_ zG)-sy(F2CfQZD}>{~d%eJM*1~ts8`O#L%X6KY#6{6ycqIlxLvi zg&(2%=JCcglq|lnmQs~>+9cP^z_NvQtb-7)*sca7^v4Gp!pu$b&GbCGNp4Tiwwq;f zp3GzVbCmv^r$2Y=&(IdFTr2(AZHwH3>c6J{UaCKj>(8oN<-6$nGh4NKw<9Hu|#>d&3} zGq6)D-%NjY(Vv6#=STW;tN!%;q?K=?KOfYe{q*N7{rRK*bpNcCZ=gT#)1NQt&-e7_ zdhI!OmsY%v{=8d%zNkN^?~-xRb~@jo|Mu_J%3q^D|D-<$>dz1L=NA3x-J_Lnyhm=G zFD|db5{~#EY(?Rm(9dEYDs47~Yzp<0k(vRk6VJB$G12&@O}+FZ0(uEBfmmFwom6(8rdFVC(9zE@H$@a1y8 zPj1u(z3N^LxtF+=9Q~3+cJC(v_j}Uv{Qdlo7?xDMvP~j3zg|Br)&T~l^MV`TB!$ z0~JWnHq{Xd2Kfl}E{y8(A-QU+uW@l4ehcgj@)>&<2tBWQ*{D9^O4Ss$$%-Bq(DTe8 zxPyoIYlq}pjpZMeldUcxV#nFGVXnvj3<7Tu%-o0XHT80~fVx>_)Hq;+|Cj z?A8+k*s~|(_8BnxDriClck=-?uFy%|`J}w{wi7k9Ek16h;*tWU_JCRNi*S1)UQ(j^ zD4~}33nGQFL)4&+awsxwK1P660E!l$KPBIHE2JKY0$|idM+Q@(4*tnR>zeEnA7HoO zJ6%-5MT4OzzR3s}i5;k72vtwZS@lGiLKM~~7qi&MnFFgLK(p+IR>m=qWa@`yRMsYo z+S_5&dEu;F4U{$RtbDK0-d^L|MYQ)YnNrk(Zi-MtZz0Gp+H}*Ei}a^}{Wx4cSlVtK z16n0;vA|FXb@;!3xjLnFL@~?5I_Kouo&8k){5iQ(dQ=;QCdG-~v*+Z&VBS9G<(yW6 zd2xulAaQ+)Q!%qyh%!ty7}$mHf`t(*(pcCPTFAGbm(>=6XTcs-qcn6HZ15RQN+BAe z3yw9uq<|JK?Oyjg81N#V)!>4>fY_LdW8aFUII;05jg8%4WBDT3co9Ew5lbUec-u>I zCNWjeU@8|VF+KDKfBcfX<@TvzW8YkqtwKY>m;z^%si@E1IGUCSkiv7ArKC*tMwq{F zO0LFdU6u=SzYb&}o5ptOVBiJCV)_A_8g3H{8&k`k`nS4MS)t>rl$vbU*ySlf7ofQOsSh6O%$S}MKc zk4s8@X%pv?a<&oxL7Z|gP8S38pOaHL$*G9Lfac)|1AT*2iBvwL85o1yw}QUElMhP(id5*y?V zSD=4f^bZc_!?`*1kDvbW&_5OFpR~{g-odBjNaOhvKBa0VAQo3KBe;h^9Bv*p0G{Mi zRKQm3Q`+Nk!KYNgLrn!RgL&&zrJA$I%O6NZLr8?6pS`_&Vye;_QR9uN80tiRIaO&& zd&&u79OPy|j60fXy20|H38?@FFbRlL=`R7XfZdO4RQWS$NU-?(`g7I5u z9_gN5l*csso(wyw7GB|M0P8DX;w!i5)ouEfJ4AnzJgn%fPfQFm@?SxJxChJU$3wp7MhB;)$KW#L7ax$WG)0*B^K_*kcUcO{)U9%&w}oD|b(%ho3x=Ha;UY?e z25V<1)gFU&ESMW?kMKOyl>?F`7+RJR4w{--nWPzUR!Sk=3zw=Jf7Idb6!u*m!kZ(4 z{d%#B!#Pe>=zy@~*$kyVQCKY*VqzM51`OG=PpkfB#2Sgpo^ zM29I8`D1Khb?dkVt*or40q%y>)SD7Z(;~wdc~VG3q02@jTz7I*Rq}-e31kV0vkpr? zJioHiBppFvAE^OfXhjgb;|s0gk5^XuLwz5sjL~i4RWp^pN#F37GnH#GYu{!nwSbpj zGL`Cz=Bms?#s=98g%Pi_9Xy75Rg{MjSRYe0XIDz~o`3V$Y z%?47LOe2ErBz?wfFI;TJL!eb$Jt>Z?L-cb=w$d>9${W_%o|?*(J8{bzjR{2|9DmZnvrt~TDg(k2S^z!IFwi^59PZpbkK%8tH~7oqb{pmi4aOQk#$yexWCd+BTkaUik5 z{pka>#nz28%1lBT2VE?O!%d?vXT@I5rq;$r6q&MH(B_)hrz6qw+hZ?BMSDLQd$}W8 zvgoa7cZ;GWN5o!Uik6%ddpVg(_7L6AioHD>t-mn#a{nS@U~9xnq+Ltw+vv;fv6s7| zFOTXk!G{w2f2rBRhJSfijYg%d}}5`Vx> zWG{U}pEu&hKDFWKVOkzdq&5^kF9j6nC-$-$y--GD1 zqWkldf84b+9X1fHa^yjO1jMn+TDT3J;$9;P%hS;(Vhg_L1m~ATpnU}}ngXD}1n5ST z4(tO^mrc#iOB?Z{uL=GqphJMnwK}0f>7aA6%d`%vqHs1j~1n_Qw#8DsXOu zBB$UJ{`^j&xbWrzr3I9_Qd`NR@Nnze=x!bFT3eB^Ao;i2N`2NB=4wYusC7ruh|t|^ zBi3QyL#5CmFPh(#0s{(tkncrn9~R&1)sskVaMOg)WC@0^R7W`iaVgc+{H|(v0y8b@ z!W3P=AE>J|PX8nVsfWNufSsJ~!|n*o&#U-MLtI!Fl76>XU<{D_bg*Ijy>JXs?d~WZ70nQj2AXyC+*#6Q%|W`9Vc{>5C>wAC_u|m^nwm1rufMTUQ|!{T{S2m zEbk#KhN47n+M$kOh9TaHC+qu$utZw2Pm032T%;+{ z5C!=qRE0nqRe(1DoHl^7HIt8Npwv`m({ewJ2Y$iplp+WF@I$_)fl^&6=11_T4f&2m zDB?#Lg0#L&q2$m^fit5P1sp%*%^E7zs}-mFL*IJn&`Z*bAsAd}Td%Z^_iU&rx1)$Y zL94t%w@DxyXdv2lLPgo;(KSLxJ^puqK_>(qsQ?0gek%I_hZPDGMib2Vx`s;KLc=rn zViIX_63WOI+D9-#=ztR`ivo%Rt*2BkVo2mo#^H>2?Ixd^O zkgsb?I8I{LaZ4w?qiH|tbkIe{J#svGgjW*Uz0&A80!Yw{(9d-wasu9bp5MoW{jXf1n z6De6Fc`$?E1dWz3)5L%VEzwoij*eP?D6Cz1N*@huHY1~zwx`97)|gRSl<-51m8yk0 zM_tlUd&CyC3$e?-{87sXG*JTo3$bd84gT-n#jrX^ms6vVIUw%d7G_{P$$sf)YZsP4 zvSp)Kq6BBV)R?6iX$o6L=Tg|WLcR)vij7!Hgqi~trJLa;(5c{LVK$LAhwRs69{p{i zQ#1uS)%jjyI4AL2i{ug}3BzgN$3qXbDqW)}e^?T+v|UzaHmq-oFzPH!y8hS(MQK2d zYw`+k8?0kHHGI}JO63ONa$RnHwNCIno}1)Vx8~koLrkkr6if~D9{$U+OD~hS-3v6 zEK-!L35`01Miw4~nL6X7Xl#=0r=q|QM$vZZ;zVyYVobu+idUAYU5ZnT7L_wUN3p~c z*!lmamb_cDf{abA{Z#NBEfsNCCK2-*KRfazW{i7UDGlKw5RM^qI<``32Bwi2uFD<1 zZJSbq-?2ui6f1OOjgnJnE(BNDRgto;3?X8)PWFWH&d}UOjy6G&)bbL6w>ZrXW9cU; z-s)-1*815p8iClC?ndCx-pXmoVipd9c095}QT)QRgPPaj( z(+H!zTL8u4gxMyc9C+BCx5zb{x2^eo%5B zNdVV`b$fa-sBAqB3HwP%BFi-BT1yxJe12O*Yod@{YHNi&0kV6qQ*yiiI>>(Tv#*ju zmMWYRs43&9|MjVSL0hGE48WXrR|N2dcF8EXOsLz0BZra#_)MI`xd&&qO;3Vh9749c zJuHMD6mt658ijqUgv@Sd(u%w*Qy%X)G>4Lj*xTet;Tk}hu4B+vgHOYya%l?5=Z^y^h-w7&WL8)_grqT++Oi$u0Yc}I-2-1?UjbtzvaO!!&q5M&ZoY5 zAH`XU1!w#H?3eG7L0&r!UCE2py;1pBMiQX;r#C9q!P-bl(kD%To1%fsCuLR0!s-D5 zdt;e^J%G~-=UIV;cHpa4D^>Wmn=F`4TAT-OR=VE)vPNH1o!@T<4l zb>CoXl+4%0lX=o=+j3x0pFDX;}(A#5Z}^&lY`U9Vju9Xflnz+d&D&wVSj;V!KCh9My~x z+O5{n(r(fUiR~W6sdTGLXm>c;z156|q!kj|J%$7HKDM@tVq>={S@&(Rn{T^X79=0{ zv&$Q88o$SunCxm<*!m=hVw$ya{_XAX%5`zPU12-o=`?8_LW+zdh`g+7)N08NOE>u^ zOYr6?0B3evjtfTM9?Pvb1^H&z8@OwW$@O zCTqvBTGC=POYyq!49+oKo2<2}{Gm=30F#zYZ1OC8x=#|C{IHW!yU@&ANsCcyPhnP( zff_dndtK~=F=LzKCn0gh4w?$b%~%UzJeNFWzrdKmby}9N_{*Iw=uKLTppTc3=lpE3 zjnIAaP9^OgTP90dF|qyge#UJiuxS5BP52}-ang#3?O*V-z3(MpLr8eh{wBNjlU7V@ z|DvCLY9o=+e!NmjS~juCOE^t5K^XJ4cPZ8QzB?@xowRIXlb3O@W`YQ&ChNpebkbtf z+PhBsh~`U3p4P15Bw=YRSd`$MqCyRLTQL`vY(Ttpwj%~2gE;D`^t>0&`d~i!UZo1z z%s9Y?j75@K@5^-X3I)uyWJqHr!sG$ig2>e|KWV^8&o`ma1Zq^@5mC0l~`h&>C zz9TIJn3F_;uw~t9p%1zl>AhfUvyv>bV{>(l=hMR#w|xQc-#;$xF6-(VqT0qQ$FDytQ;2e zkMDsAcbc!iN69fFo4s-C-U`89S2Ou#pL_CIF!y|9UnL7+i!08&J$$BU-@^EH#XbSH zG{MIK7l_F-QFFGYi<5fhyyv4zM$C=<(|trisA?7H($EkPFa3$SlAza=?2i1h)p9W_6-jHyu;x}xIVZYo?d zLz1b*CEjaD#SGs00VNejD%}5o5-z081z1}|>HsaftjwbIV{e2^M}uCbO6;fF8iNjw zTAm19Yr_;Zg^l4rSzvHXq)uSSj7LdZXSP-%j+zugZaJgf`yMILs_@DPx1 zu9@gi18-?+K$HK_2H5mNX)feon3d2t5BprZK)fZhI2eW!3Sq${Q2}MM*OFEx4aBGX z7vTJ!fB#=G2|hT$KG$MN$ywGU@T`)h^c>qWhW4^baC`XdCHRm4>tCJ{+*nCE&tdX106GUS=lkEly^R46vb^ck@5Pyk$4NZ6V|v9#JBOk{yNfn^u)TX8&m+ zWDlQ-zAX0I%Mh${5)|y!n~`M|LU#RIX?ip+$4>AK{|ALo(*EOw(3k+bxS#~;D1q_* zCxwtbnD#_6Ho#Wzv=T`J1N$WmtYnE&Xk&#X?GC+B!o#G`6A~W8D@wuJ67M0s5@2KA zwnA+HO}v_8lXPbfb>wlSYJ1__iN!F@gr}8wi&t;no4nr-qUGbQt_V&Fb83dP!+#_m8RG+NkLCi$ue(~^MXUAH zlBLGLiTBG08PZUG$3Ue9t<9H=1-t(J3*zCN0A2B6Nt2dz&Tv168Ex@!j_jeRc5ba* zcUG__LiCrjTPwh_F?sB|^M4-`;l>aW6HYU4fbPC&M1d((a%ZeH{xSounYqtm|aUAw8@^_z9YIM{&=CuGj zlIRv*wO42in=EHBaECT<8~J3{JISXt^AqHgc$Gvxy^gD+-q9wntUPJQlGUEZic%Tn zlL-t{vM!{pMS@Jid5z!L(VEHhDXklTY$gJ?)Dpz5e=Dm_4zLU5EJ5DyX)i(Sy0d~c zCBRm#F9rG%1+X^RR#pZ21Xwl_#I8H51euBe%X~Yqew74Sc=Zy*4k#;5rv=#2?eV}j zG9(E0=9Mbp%p4su%K-s?$#Nh`_o8=_0}DS*kOSgX5;^cjfbG>2cFIK#7^8sr6niI~ zI2uR@^L-@D%P1X8z?CQ+-VCr$b^D@4T)`_F7)ga$bv;O6Gl)`J9VV#afU*+OTLHGO zG@+Py=Crpuj42?6B9M;@vP+1>*mZ9Ob9#V%Rw^qbOQ_8&#{BKTw2>)x-CJeK+W~eW zSzb^={Gp6sMhW?9tP(q*tT=urz!qCfi(e&EAg4S3Ls9J_&Plq%0?5dJC~{XV&FJW7 zdYX~NyLlJ8;Y##wk{Ze-*~tx`FT3$z2ZhymcrU<~l(X?b;Fj8Wu-mg7I)9UKFW^Z>s~~*`UHOd zMI}cUeMLu;i@rICZ@=*?xFKt86w(+R?N61_?yd>01TMBUCY>&1Er8E)}>7BhMi z6=UXzvP&-;FvMci!ZQh@5wDVrW)X--*evCzO4#7CtCA=fajInU;HWCuem41Rd=X%m zgeo*wydsm9G#S?oDf?vDK|!QdbS_~s#H%EefmMWaLNAuzWL!J6?4yZ-VZo_jl=BIr znMALWj0T>-&~mm?Hosi<(L}*WGMa@K5=J9lB^k|PXsvRdo0X%>KAI>P*3np2MV18E z;e?$5N%*1+Mhi))QVUzV{;f*k>j2wd&c*=ApHjnV*S{6cr2)34oRusIno`4Q*S{6c zWym2eXC+G_q||WQ^>2l9ITo%)s0HT3ipcrZm_rC75v21*h7?%2H4hew&w(=+JI%(ofWK~;Np}Ddu8NQ8?fxUv!-VJjG$n#o$5E` zI+WIMGQ_WweMG{ykR7~=%y2uP{P)LahWGKYSBxERdUl`_SyA^+$C=<0_&=sARgFaO zUC4q;I0VHINTnF2WsM*<$~jj*N;{aWQ?xt4ewrIUMZYT3EC&3?R}Xv=pv0n~75F{4 z@Jrir8O>TKC*X^(9{40cB?W$OfSt84M>zp6-0Z7kjwC=O1%4kQdY}F#WFt)as{=j> zP#W--?LGSe{13kY@Dw``g2|x?CYzlbNxDx8<^d#rY>o$0l>4=4@`>`A+ z9j-vzS+KcnXwj}aD_BPYZ1$GYfK_?}<8ldL*>z`C07nCCL&Et_$!M?iU@e?o_S{c9 zFs$Gn!}(98F%(L>PG_)+lYMse`pOO{EAYn=Ao*=91>r_qoyboDR8oC)0_iBfjin%* zi>m`Z2~bIaKZy*w-^NmyfAwUO1gNCIp9-+Ie;Z2y_jg>a50C^X4S0*Ca2mk>R+hr2 z1b)d%BT4s3!90Vb#eOSGK}eR8!L;k%szlBra8sVT%@BWNDd?dH;Sk#BI=k+zVEz(d zyGy0(uEJ8#LlGr`Y1h3K%ySTF<%vxS8h&DLDcE&q1?zl(oi7zFmR=CeiZ3YQcLzWOtzeFY?D(Z%*(x!C4> zT)_~Thl2$d^EUI8s_LW$5s9us5*r-cY{Y&#|8XIIYMwHbt_!AWKWf$J9&jqy%t`lv zpO)Ct4dQ03?vNvN#xAaYbw}s|wlo~Qn<8rNc7<@8C;MEP-{?#YTwz6rsZonK84vAxwT4-1wKLah*8`PS&Q}`Br_7bcYn6Ctff#e$ z1xlSNxXc1~XgYD#jsegK9Wzg%R3TjypDN&-j+=sUn444lc!z%AoXiwDO%nHVMsPze#a(w~9T<*1Tf zc#ckw#;Jo&ac@-UtGpa1j;N+fB-s1@(1i>{DD{hXoH(CQ=OsEkCHcXGI8s}?$uN3* zlsK4B-0i3ze39e9A+B_Ss|RHZ>Doh_=P#~Gi=BFCKYr8UIu=8xPmrBXI9$h&hIR<6 zfNV>f6Hm;l`F+jMIZUxy8`W&ry>Z#aS9xL!(6@;Rob)P50_P3=aEWjE?o2)MRoN3b z?VzxVp#DKNrkr=C61b%nL3aIHd3`{TeNfIjQ;WW~7eRL2S-~0@WPQqcXDWeZCxVi8 z7bk)S1=&(RW!5~pQ6N5cf1c{fYKDLcc0 zY{n{W5Wmqm8gYXt)C{JL9hJ2EI3YGN$X5O4X9-@(j+*tK6Jqw`w-;igf^1TG+EG0c zC$%Iy%C39sebBE4*+uOtMw5`zZpAX)2i?|ug-6mVP*?t4P6zx))4g=B1=;GKLD;{| zt${}C<#%hK_0r^fV)wyJ3bOA@y0WsQ`vfhDoLAqWWd1L_&({_Oyv9yNWe5(CLo+$Z z-l94G&)l;Z?H%q&{OJXj7k>L&oTmiYg4k4FYi@`FB_-b-X`SnTH&H78e{!x14FfZV zCewNO@i;J}zz>I0zwmXZ<&sS{HONMmcJ9+m=ZYr1h_Y-KY(~4b%yZ&JlqFp>{brE8 z{DsiOj>w$}*M(#1&Dz)!wzSMVNu^}hvKThR|6MdKuBu6L(RAx2$u62UPfh&Hm~`-O zbjj%Nf8AA{Zw1*d!4_uP*}j;_F|(T{amtzO{_jF$`wNlnry?yzXqF3C zOL-$~qC_)E+@z=DiMX3_hsnD^_E`cMk=->e{uJI1gKW^3$=nIPt3B?3u`}!JswJ9r z9|hUwUB8)GXS7~^X5ILB{LGPq?1(1iO`I0Hz_P;X=-rKUB`v?aRVj!wfafHuPZM## zY5;#6WQzo*9^HmJM56a{&DdtWpcmI>|1S2uVEdJB%r+V>KO~i4`_2urw+*crFM7*v z5B)E-eQi+?XZwB00X=3tza%eyy>?J|C_>%>K`X|Lw;g^9Zezx-`g6?69LfyG7p;)-WOTW*xK7$+PRuNSpXV zq)kj(ks!3it2m*p$?WAYW%b)Y%rg7e9CtVVc-hk?Y`{oZr(6+aD@!AhN}Ep)bQ8Fx z7D;yfTgl>^AY1ZViKGv%ERyWHw}QDc$j1FvBFPMt?tcWsKy=b+Ay69Cw~63u4A{ADbz$DSLz|Xwg-C%_XJEbsJL+BiD`l%X0)k`qG$4 zDR#g=bW%V;4;{eALODh)?;rqrp^=gSm@To{_5c+AN&tkLsILg;OHv^Vv$?orXt^WQ zCq0M!Ux_)JIo)C0PKjH)k)-TbDJ|O9J+wO=&$)F`_y-Im-JgQ{>SpHq1^y(q4(a<# z(E?2n>N0nC#NuDRtmHfZY)^@W^r^F566?J?skKc@u)0&~Y!}*JA;(Saxq;C$73ePy z03=|@2@mZXQ&EjbXT~&ED;K^QU4#U}Lx(D0hlIa(e6PDDHbTRzIe zUiO)X9h{hyE~rN63~wA%Go>ZGr&A5rKyZWVG-dDYaUhP1IdmdE61xs;mQ$@~6#Bvu zUuuhfSv^1RR14@fdunN!rO;-*5QT`H?GQw8_Y}kp^2($#sE52JqP~HM{xd6m3U>TTuMvUs6HVGlMr`FM`elL(Zjw z#`ISyf*4amv$@L+p4iOu-D+-QP+L5L=Xuzu30FdJA)=#T`&}+b2TewL%rcTjvB1N= zeJz=c6r<32MZ&O&vvl;CkZ{q#RvD8_ntn>i8|1H2?SJz*CFrw}O;TXBg5fiagf zOA>n(6PIF4oI||&;sAot#w#zq3VTDd#cVT)E=K7xKn1Z`Lug(i5UQfy zcc}Lcclp%jpz78>HBXwwyJ(NTKJ`ZF5?|p{tN1k(qgLoJKjKqshv`Mfh=;gJ3SCZt zViMG-zv$!TEmGC%3om70gB>)=`V(M2BuYi6N(%M!Mx3aMhgUpuQgu}n_J&jOW_}v| zhH`>(z-BzgT{pW6*%ud5JwW%$v>ci}Fmy0PeV`y)LVf5RNUYC=RDZaJQ%pL2eeVzq zw&<@n@hdenlV_%>_Y`hS)6^i%B=-G1i1ud%0i5=?n8{O8Xka>Sx*&+yrCt7TKyS)V zf`RS%*&p^B^yL$;c3nsf`#^`Y3@Q^FlKRqEdwT(Bf%85Dmucz?&pD}ayt!NaKJOI4 zW|zJ#&ts7>UwPQXNhQb_ThbI#2f|R?$PEGz+7j}sy`G{h-wIZ zQQzT1l-I7o$pK&PVXvD2 z#|FLFg*3Xtv4}h?Vwp!-eBgNFD6#tMXwXRh0(tH$rLysDST6Y5^V>Lu%DRGJYWN!WBq3 zjg~Z#=WG`aXmapvFeW}s)ILaH)!~<*dOzFkA;wlTWFnUUOeeCSbQRq_o}*By||3!5!%B0Hm%pW~q zNv36j*f(g$;2^OcZJ~0VKGu8K;dl-zB#T)$Z{QdtH2$TT%1NitQcCj1H+a~__{K}o zHvB!x3xJu7#zkZ_dE*;B?29rtZf;xLO06)b+MJ-_(R)xqpE6kX)5omJ)Y7YL$$|y+G7PC z1yj@E1(9m<20^f_H_}tqQt*)SL@qnkXXD zS)6Q49RzrQe<*VbN@~91+1@FzK^OA_)d0!_o>pCLL-svYdELd1AlfT}LXtVY!2eub zty@tq1v6Zf;*+W)fT#svoLWANN4M6gu~|lAP@v@NHFHuPPLaeu1pqEB4~Ir*d~F0< zU4nLglk9ia$Z#oLVG*NY+o07`SXlT8ZlGJZ4~S->=|-VhyiE-?zq+o{DK06IE1_Ba zUh2Ln$`sbnLLYRv%d0MSskVk43d|)zkO;{DAn`Sj;}XZKAScgX!2c3JGw5c)~8t@&^~sv4aO`uQ8OCl;X}ge zqm6}$A$)RY0H?g5okk!qH!zk%e)pLgHwFQdz?YLPbYR>$5jCd{z^2&&YD6_)4uwyn zFoF^}RADp_Owr+4{HchVXZUHN1K>bEWXDazcWLdo0&sm@aE2uy;Km?QqCjd6HiFEr zun!ZJjjk|NqPP@sqH7m^8;JVJ7$(Bhu=0pHb>eUXKfF?onpFqMT^@mxecBl}Zgx~0 zY2)-amz;&HfGwUSqUzwedvesf)zdxz07^q>A}mo*365Q8DPMqZ(o$Zpo?2B}&l~3= z{IQh(v!2?Z!Fu8|fbD7Kd;;!a@52%W<2^tq!cnj{LsS4n3Jl1hb$mlTwT81_ke|#| zbDLwjsQckAcJhKmOw*DTn_`&Gc4+YOc^5TE!AJz-Y3f(PC8kYL=f_$|gUBHRqxOO z;e7S?(sKSWK9g%su>LHut)n~|gS!Fmeqalx@M3;3U(IZ#7n&!r1tU#`Fe{>vUVL@9_=>QBsVfPQDvKUKxBCW_#VL;U@kh}?{HN$x^tJDuv@c4FbD z__G4lBSDHTs;x%u8JC`e$;=C5Jqsiwd9YQ2RwG0c6Z8N^Vd{$eULMK8F=JE4&)HLY_o>EuMAw5^8uG%GQLR#2y{bl@D zu{mhM#CPlXw7TlO6*U4jh<6!Jt*7S1n6X1WwXvBQomXN;1a)b=CT2WaPtCmsWHuJ_ zKiA2QCo&_=Mp9P1qdr{ZrTj1T)mj-FY1o1YL`&;2VSI9Z2#)WgEJQtg;bu#dve2~+ z)XWw-7+YNIb2}D-7<7{;q+`mk4hrOYLJRoN25PoElw`E<%pr(jUw~e|=kpq~$VA%!K=gs9h52HANU=V^!YNpXT$%6RCW+3dP0%7S}FCl~!r%_m0O`#ZHmZBWJDnZh)@ z@T#tlDC=1hwc<=Is^&&o_XABKpTFRRP1Tl<=(T=vuv3?H!D+5V-U6X)A)cgC5t8*+ zcjyh7zKO08O2V|Pl4yBc1Lk!xDLpTy%e>(=YC{TK5sYt0tlw9{(9|-sH6`@IHEJV@ z&rykYla&xw69(XCGeCIgN}Hae%Pafys82`4q4(QVTa_=wOtqk}un@+6KY~M_r$Tk? z%JV<20eKvhbqy8Pq@cuWH&b)!^$Qy3oH;@(f;pNg5n2@ti)DHzkODb_noD4l7yrGP z+FHs6-1O#@gT3*xP@X2$wX(T-L!EDe8YZXLf{j8&2MKH)lbo(X=|#Xz@5TsxwQ8ZZ zcOs3xsD*le0KJ0fSNT{|2&7#t)P_*`N=x-`WQPoGsWy^U@%b&)I`nf^E42}z#I(>* zk1P;FRvF zl9fkq=6c!1jrvR%8Pv$Pw^skwd?0yE1WaHdXh;QWp_?dnz)Q*&M8)rsBOMwt6&$D23MrnN0YD1|i((vY2`pW~$QLY`viz zB=^mz;bO~%Kw728W@u`gfnc5efCDyvg4GH5 zv#(R@wAM>bHUWS|>yHqk(P1V~n=)tTSif)SWqHwcs@hz0CsC*#ycs+i!SQlE*CAGX zo~h{r68qu}r@-FI>(m#n9g$Afp-as677s|#pMy2!_QC@(2oj;F%NV)vacK=5ZHx!oVUTc!D4wR z_RXh#L+{aVyfcHVzKQq1qgt#aJP&Fm#awbKQ*%-BYo=b`!YB!FLc)(A<%=J(-W#o$ z(eJ9%4tWSTn3O=_?0R@RoA^W5tJmW(`Fgbq9-m#WX8E=RvVf(~dcNg)wLKPsb8b*y zkuDX_yFqQ_3|!75<&Jh7q09X2jp|tKjSFa{H{)+oe~~U1kG@$QDpA!AYKQdE8rmUi ziTH-35DRobXdI4L2eoDv)<7NCJDNKY5GOy=*PUVUm(}YY+Q=_=P^+O>wT^1-Rz|Uq zRxA`N2K|Z}L=rCMy;XCysi$#i?28G|biAmeTA6%m!jXXCtu(OuTOHw(EahK!RBK8D z`QeU8f!WBtw~8L~Zxt_ZyH(9B+?Wp5A&X4g+D0A0iYJp%V-Zpf^ch$YV6PO0qJYQA zT(`$aGsYhv26%Nb{%~tfzt#bziBapr55YH0&uLIX^Cm$$HI=PB2tmp#-KNU5z$WCC zhS635we{nNqAhDvV{cb8Z!tA;(Z{|Bs9$h2O}Z`OOM#c!-|TEd?p6% zovKyh@7<=-iM(Imrq(34G)ZR>iV@M&3{Pcmzk!Z;qubTWO`}blKpCT#&_N+&L5Ykf zPz190KW|rSwoB4Y6#Eg6mSy8amQ5kR_L`<*b}Bn4u(jiMRR+wb(OAqA(Wa5t?WFnF z)`9ScI;pkE$fl8epT;Kb*V7~+%61+iKA-{lbyw^ZJi(=W{bV&KP18PGi1@a5QnPa5 z5CY5H$Z7y$L7keqBA5R``Uwrht94de-i%?2Mc5a~++u}r(fmbby*f2PufE}Bul|@! zuSWR%&g!*!l$2<~_6jdMi|j)Yq=-|)5#H_&wF}vhQ|?d)w#4O;Lb`c@&o)AiL|;Je zL6J6A*RBccfwKhHHbWwAf_0AUF5riFKFN|7Mv7Aq# z#ON1z-v!PByv(C71@wgnyCUEcEzy)pw7|!vRHB6_!BC<=D-=}Uzwq1mNaDG2&9Kiy5OMNS9xKg`KCoENT>mK13Y=YdvcjEB!Bq3N;)_Wn!-u4S-joZ+jxukXY}on=v$|F8#4NzXz3sFMxcQ5!JvIy zwB)c1CL0Cx18}2V9W-S~uum|;Dy}NC`)`y@vc~18Q69(S*x2#s{ge=tJ%NDtokskgf%di zH|&FHY5?2Y%|3xoFGSX0gJMKEKm$g^*2@ zf5jYZ4t8L=NMea`gmzaXw)WFhNf8lFOl49lM@3PHgxnrc0Lf~sVt1r2#D0dl6Agy; z@@aoj+qI;2kZy=#crRg@p9<0oqYdq2v<>=+VW4`o$J9rp1H9K`YNKrMn?8C%P#n+k zZytl`wv<aRZwae572uXq;~!()^e5XC6l$ z$dCN>$JK&{!@x6`MJqeMox)Zd9ze8p*aUp0H>mPJclA~v_P*|F(?|Q~N4%hCiVGq9 zvfzT3^(N2NNIQp)#7H6S#o^$XApC-OcXT8@=nkzH^hQr0D>YmZi6}wA)5OCb2%Q{I zT4*>w)?KYkJe<}85&Pl%jvi{g+hYg=_(rBBp=Nyw`yQf7&`XqY1ieUX-x3inMIfC9 ztx4%kDG;m6dZ^jgMhSWJyCUGJ7t^HoPbg+021wACeIp4$$_2VqWn{HH&0k-6z#Lxh6~C>rZ^FF`JZV2>jzowdOT_ z;6YP;O|y*y+y%}_2sNV;bhW0gCW^Pyw6}}!mREm9&E|)nRMQF(kMaND!nzDq98C*8 z?`h>4nu7v%Fn~Hpzmi2eS^_Vk4TlKIkUZ~OAqVfl?!u6g0Hp?MR7KZz-p2p_l)BIf zb>H%7m=1XU;b}FJia}BF&;{P>X;rC=Q6Tb%QQ%+mC}upZ=HEv1?0*<>A$i6FkGks^|!pInoDamG%Z^t z_U%Po;>J`Tq=`E;jfb9v9(^gBH+)ttsI6&1ZI0kBlW3(iX2_G+!b7j~;5lCOtSU#Q z1ohXDxKJ=|e=}OWYr`g?Q&=}--aV^cOJ?3lwC?O%fmi>l8lgF4e^oPMW?z_k%)^9+ z$#lyznSBNHr5;`u&=>fYs5|_x>P>gj{0mc&*q-`(Sy*0pr?0utJH#zD!U$1eS(HO0 zPJ0Jq5>1PDW646R`J8$eLU7MN2gyOCL9G*5rq=OOUrCC!j(1p|E-3*V7dvek8z8dVZ0rOyoQVxZY3#T z`u>^eJK|LOcSsq-WTQfhOogHr+lzlw*UAGbAj8%lK{c_J0Re}86?nJ5tG#HR_xxSu zG|xr9@f z3-G$>RS*t1w4z^+1(3y+o+HB(g(%qG8f}C~#L3-5+xd0>Qq>;YA^9P-@D8-^0PSON z!YW7KpDyv@6)2w+Q%IB)FpZm|t?!YpC))?=YFT>FL3XIbz8or6rm06aS$FWm@dp{0 zeBr;;T0(nKMd}iIYZ*WDFZEdM{@6i<13#rmN(}RCtO+rO=H-=MRP$?LOxor?;nPw& zHWoxBe}vdS%ua!Rz7*{LUoX-`r}KT&v9PGK{XK?1W(fOpnmjv($)>-mNCLiWRC^wb4Y3J7Z1YozBHZ6Z~kQns*Bj zV$=t}K4fNB9iY;;*1GU*%@iY<2;|7OLFj1TU*Hmt(sa%nzT&AfU z*65<`L17SsAU5)Dk$NwAAk^b17dy8g)nathRG-wgY=W*lu-Kri@jvoMM8>cu^m;Iy zMp0!C%wSxj1FH{W0^|+NhF_+FFHz<(B!Yt)BJ!fUn&49MqF!qHjXDr-n1F~Iwht0O z>Y;~u2;6r~C12GCUc8}~+EkLx^FVJkyWlb)6ek-6VrX4xs&#+{LUN*WZ?(mZ$3V?q_>kSG}cee zzCLQ>9z}jQ*iv`aOaxA4^+;_d$!q~>re?TS6&_nZwIvxZVn+}~sxTCaRE?0ZGKx@K zqaHa@9v8hxhwE3# ztYk30kaTb~FY+DuoDaJcOdb%;{{-e0rX1`TNLBG}7Y%@4_iE+3*Gb~V5h_G$gpWDG z4#Ew2t(tXfZUrb45%pLr{PvM*1HN#8>V=|TIzSEQm8w=?pxQ|rZMNTO8f345YO^~( zFkCLfXCW*dme>iy;d;r!xB;DUsoV)uIb%VG`v$4a;E!E5NUcJvk5q1$i=8pri)!P) z4pLi_|40QU#tOi36_ih;6@sHo+p@rwY4D}V&v%fcEX-85D8P>lQg5w)5I!#S1i5U& z-=)7P6A~*9a?_k8Y9^;TIofCUu@I>Z$5%EQ!1O9OdM zcEoTXqvkJsYoRU=E=wb}5uxIkrtCQk5%Hyb$S_#<1Nj@n)LdG&T|P|Bt3(Xv%<>CG zECM9_@-Q{KsovKcroN71kR*a2R4L8_atsJI78CXsnVg-5M;ngZiySh&Z74K-4S|-W zA!~tx&{{GSvcm4#E=P{%`n}%k!Emr&v+vgA9}&T9Q|IUP&iVe~YD?n8{1KWe4i!|% z@d!@j6<<-6C{r5R_#&S;BFdDR?CVc`Y`kzjV%nJAAvB~jC9g12O>Jgj$f&z%ZXqVa zj24I?uW3o<@n5X!R^!YoR0n4^;QL0ZmG1mBUl)=(cTo?>it9UC52{4DP)JrlV(3pQ zh&|`gf>AcSbrcrsfw+;QR3&|CFydi9V(Tee3~3#%1jlLaD7Aj1cX}iPCMh+t!hsyh z2#&);7L0fSQ3;|+c^P=`onGANWi07S8+g~zYRxK}gg#0Or-u~;%Zb0zNcSS#K54XC zwaNy(qA+Lx>6Y|a{7s*WzaOm@N)K)b1RZ|jUQivnhJE+CE*%g{G-!^t$%@a2;F3^v z37}pQe`kol5!%7~bn$l|@pl>`^s~mQtuurqCsYYKe|HOX3%@j0a6@IT5$#M4iyMZl zrh!cS(36XDgJR|K+nEbZ^a_q6D}k(6#PZ2{)is_$_(DQaELRBkwOk&jk#Xm7>TNVD zuZ~k6mBw+`D{3UR1R!$%kW50Meu->en4IliQE$rBbV>_KDj+guZ(j7Wtz;*@_6i~@ zAmqv8)O^0}6}2K1!;x3iOsWAQ5jBWxV>N1whiDkcUmLI1tq$O*rS(D|`R|1`vMJiO z@C!pt_;NCI5~ZqNASX#nD8r6pLs1wc8j8l`sp!|Hq6U#_!4(2DM~hL5vJi>*g0;8*<132Iy8wGEWf+OG1X! zZBFxWyQzEYcI(!?m4jp`LaDcqP)SmT(xJgnl+2Y%2q8n7sDzaN_xrqS?X&iwd+-1E z`+fMFz4uzrJ3sF;zt8(q5Hu~G+AVc>VdkK2k>My({$M=CqgMp|)o*IdWLrFpd^jw{ z>(twmu{78~pHGIn_bGjJvVCRvU46;ZcE0s)Q8(VmQHkrbGPD+a|u#@w{2uQ9!9T5p1>yFdy`i&W3)edgB5Om`(md0S(siv@E$aHXW z?&YO3?Gk=;&di^{y4`0W1&9pMLWN<5hFc?@go|JjdGZ<2=Uox{xevc zP^-&l2x9^T7&O;zb`5`mY#V0wP*`}9;VYp6lA;7#^VX@d8T8SUxhtDVbUx9HUN*~a zLjRxNkiLDEopYw)b^jN2b}L`))0dRO43ECB4ahrB`I=_X^3OT75;`d%-wZ$w_OTp1 zhL8?~NVCHUxg-EN!h^if_YO8n&@6(!8vuPUsnTYlc*2iJ$oB(~!~IS5=}UI20iO2( zh?n9Sf5;vYI6{3@*dxDZ;V33qCBLV-+Yu`hPW;Ac)5Ld?R^e16clbhIH{0$D-?$NQ z{pu*}`ql9}7G8=cgy3p^@x8MV5ca9=`m%k4vBL_Ahzl}GJ)v4yhi|m=m@$`QgqShi z7@FXFgA$k!MkyYKVIHzpKJkh;9ZX;i!k3uU0F_G6jF^o`It2RYCm;aY1w6RXWv|%v ziW9H)P81I7dqrgbNC#k(9i=A%w!px;Fy^D-cfVxB7*iFA^ORGwOMm)`-JI$NJ3c<* zcf13Gi4DcepJPHnXbfBPJ}Qg=)^@pZ;r zyXD$m%|TF0Ya>fBekjv$#B4?LYH2KjGHknf~WoyVG@M(?506rpHK( ztH3mak>$vq2sDFdW=Mf%@XSyydB$dH>2259t+@Ny@MnxeKX0C0rw5JA-WdKbD{rm8 zm}eI?{us2tOmvR7ZQ0-q#LxMpV3@y%P3PN9i$_&2%mkH-8mBX1D*~`S+0cXL!)8CK zdc6W9Az7^oGTdl^e}4nZycZ|~1D7C4b~aOIKv-TgACas$S8-&%Jvm$oY(Zl*e5BID ziJ%l%c|mq*zLkfthosZ2{MKuBBifC3zGe@D-T0;jh?O6yN8*F#<5>&r7ERzn3Q-!y zrr?1javb&shwc-jO^hJrb;SE^)WxscB?YjFAd;{;DO?IVyENo(T9601sb(mYD{u7Q zUbp+vwEXt#cHJ`)re(y3d8TEUOgYO8xN2$SWO0pPgF*tpf+m#FniY1OTZ#Qzk>b8- zLJ=zWzwTRM*KN83hXma69tiFyDS&7kg~>NHp2~P6(jdmfP#DHUeW}M+*md$e+p+ue z?_%9y(+d@09DM>62fuTPUp^5xXr>1$H9?v=lGz4i>M3;Geq1~<9PUtFFoKOtnT4gx` zWrEe3P$Uwb%6b?If_lPb``W3T>|8}U*@3c{;ZW~-F>Aek>Z5D6t51Iu2#@2RZ`ygaKQA)&^*ndt z>lrT22|;2S`6kYQ{TJG+*S~4kE?HU;y-_#dmPdrxJ8j z0Ccphf2ml5P_6H3V~t}1UBx?fp#VkTqL->QvEe#1=B&o_J@7S%m1%G_UE^b^|4OTMqmI5fr@yvm+FNF%yQB_uDJ^HM7 z?KT+8-S66`GvYz|n4u32w{rBFckK>!UZYVESU_gMSQ4TBy7O||!BSZ->2}NQGi%L9 z_(M-{T9P>fsUKW!w{0wi@yy~EP`*Sh`0hodgu3DMFEXO+T5h*Wl^_+}aXI2b+OELr z&es>Lu&=`jwAWYQf7q0TJIS*Hc&6`JX*aBfUWVimd#PuxL=gKSy?&*wC^oR|Em3!z&vI~* z7CQp2t?&mp>*oRN4FLRz*97o&GRvb$tmr@hEC+*GC>mo8R^OqbT6za zim{&aft_)Y*9q8u61^PfGrUBRI4obpGtM1C(FdoTV|?)J*6~2emHzm$SJ}_weDC~K zc2UiWT1;G9DwDBS4pZ%0WjD_}EEF=!nyvF!+n2S6nS$pltqNFh(%=_w1a&Yl_>n1o z|BM$7{#7%Rb5`5eoaYVV>4>}Tah`_3ex$4Jk-$LW7UcwBk%TrD(1VgBOQi;X>8sa3 z+}x~(tbxUFr=GmVc5)ymdA=73=A#>9i(a_~7OpS!KJ$^W*6vwDq#Zg1A>d>f0jCJ2 z$zf~lUMAQT1j&$Sz?{(A*4hO;qspnAk>Y-L5}6fu2UgA_t+Z2N`Haq~+phz~9HFmW zXWL2cjn5cg_{0XguH_gfw$zVQ2_y78z~)nl^j#sV37e>P98VzYJ&Ag2=O9KQ2)@Lx zTA#OGjIL!tS}uD9*FqSk^XU{!JM zhNv2LR}l)MC^bW@U8_@~6}*+R(E8jB;_tb617e0@!7`GxyXW~#OII_NXw%|DGrT8W z*e~37K8y+ZMU8_62AJ+f3z(yh(jeF6OPmgbVlHuvaM+t2ityGSfoK2&zlQFNz)3vO zwLY}30Q0)-L%WW@!RI2?$p0$5dggRQWQ?UY&S~IgjZ+wygMdN~CS=WH%!tUo2PhQ* z%A^l-k)#oBz7PpdDoU&+!=kK4M`cUc=gNfbmeWLP9BmbtVgLz^)%5 zm&iWuQt_Fknj$#X0e-rdg`_L&Kp0iQW{9fr9Tiyw1xdDM!?wU6!LSGImW#@<*Oalo z;U~9)e1q~9vR);}lQa6Ud-0;&eR)Zi7sdU<&qb8#+~4;2PbM5TPu5m=vX8sad$M3j5ic9U)g9EmhWg@7(*s!3!e}qG6xpoBw8NQtg+Nvynl&mI+qi(1N8~>Yr;)oOjxbl zbT68eic~D|9d}72zNcdKOvEWPG{`aDcPN%fMNuBt{Xenmbd!Kne_$U(+-Lk)ZU5+? z;VN<9k}NVpt?~q!j}#T)glY!DRA+nwdNxA8hYyZPQsWJTh%*pv;&=6dZfsy7!Wi@5 zpW@iQld&N)r#Chj-_TPtHi({;p|n{^1LZM+YTh8=-A)WN3j$kp?|C|;DWrK@pl|uq zZdi~sw&M|Z*$FSBmp?W==TqCMgOLD7vcN@<&yF4B1!#Kfr*@0;K^%P1oiD~m-RGY7 z4jqQv8t#~(ac^UDV?}VrxBy~R;()QlOr=nVhou0d0(M1x#b(A_) z!)tVS6Vj1DNNT$aUa6j&?CL#!FN)1Er{W={fyOKi^{_@3%VvT-LJrrHlLB@swqr9sa^@hoVP z-r-MzPe}$}WWaTY&31m1xmgnHk3~g`_@8af^g-oFxyv^>_%Nr09mA!hZWdc#g?SD=@ zCD2o3*h;{nr=Q5+^}21g(*{c(FPg08#W&TomS z@v;bK23k6l+|sIcW|~o4|7E*f|MbCv_%IUz!GZH`xBx~(R_<|I&g)ZY?vE#}?AzPz zBB;oMNP#_LFgXkJL=P7}Siwd(#C5eD5F1wNc0260?M1c#>qzJXgqml>df9I6s4Pe@ zC`cQ%*g0D%pG@0<19Mt0++nw_jcr`H9-?pku7BHM#C%q_CghHSZZ0x{p9NR9_|h&) zJCRlpu^!f!e+im8muFvO|533M4*n1{N6EwyE2nmYL2x0KWVz`B-Hg8$ruM)>r1tld z?y{-zDek+F(xfTu&_h~ItBV;6YZL{GfwiY4Geye|gO}HOHjBEdmGc0$8+#zm5v&1a z37x&uZhHAIMTNisq^a&>tRSP&(6)#>^l9!f^olPz#mITYh3i1F^X7I4>0%ctzye?i zCBXp^N!CfmY5l4n-D!6LvVXMGZj^PbNJ5Zd2N7Ku?XLmrN#PM`Nc?Frgb6Z<6kDGv zx&jDki+W2vxsV$IjNAo#=NS9e17RGr3rBLs=w-X0Z?Dup?>g0bv$~Q0eZ8;QE$e;v zZu=2ZLzY73uTxS(oSYVF2p;NFLxR7N4DM6I^S`povX{iT9w{_YFVn-nvO9*S>1AKp zbuu5qreK`nyLvl*GWRU2nw;u>KeY;h%Lao;rP(6A0Dn@EHzdBtZlCLwz!`;2OrVCv zNnqgL_t+&oOu!lw@41mHVY0CSm+Ck7fOL-4JNH2KU#it!yC2e#+_=|1v)wy1V*{zU z*O>S!9yMv8K^I7jlWMN4Rd3pB-%m=n&%Vk~y1IJZK3E%s+_{~E()|!Il# zz5NlA^d^Qep1kIN+i#zRHJ`uV?!%iQ*@d4|DrsDRZHe|oa+8ywh=e{GfUcy7J-@bx zT!dvZxxvVCmP2902`OOt(ipxn&k5yT^p&Z{Gya!}z~;g9)<0kub{Sht)lIUci1eJG zgXS3%Ng@u2U{s9m3v2d@U6s!$Tn*30pT|mo;e#z4B z>@)nTag)tVe>xIX5BkpTNga!|EDZo03A=M~U#=IwM`;eD;KULTCYVG`zl7a22ZDgO z`T&B8M*#`FDgX^h^UdeU68*FD%u#m%BqHBV!2%xz3ZVKUN!5!3D&Gs1CiJHPXjK2b zsb1^v?E-6PoPsEbH|T$w^V61wz1Xm|DjVB?u^(xT5i zXkWrbXAOuiPXJ)i5BzS{lJn*-Il-O{fCZMEU~(SBUvR=A+$B+EV8IECnae521nbPu zgAUo5=Xjj}Y9>kqR-7dy&1JCmR|Bw>lc_fxvM;FqdJX?z#$Y6b)2)8QPWeb*@gokV zR_LWa+RYmp2#D_p?HyS|ELm|%rqsh>=SD>M!@A~A@YW8gq0jsY?65-r{U^IoEKv?3 z&D5XlI{6>)JOC(BqA-qpx~X?Uf#va*ReIx3wp-l{FYfhNH~blK<12KZpY4Vnzok99 z0*Y2D6e6(gSgZ@MjsE+vhuoaXO(AzoRUFkbf3{EO&L_Ym+-Yra4;{hIPvGB)pX~uX zJ)l`tfGVd=POM@z1O=f|4@^*aR*r~Tsa#Jx3>vsXFFI_WUn4NbOA#oq>;8g4eXQMI z>}Hs(+kUaz7XyU*8G=*_wGG_cL>Cf%@^CFy6w*x&Vl3ADVmr-G;5-%1Re*Zq7F6>M z8imX*pK|lcw5D-wR_R(t>{5SPLtgKC9f7ZNh5p+SJKnyc21@h3r!+IIkr-t~nX2}* zfMZYnuv(cso7wKuPs6q>uEZ5bz_VY}$M6vuR!_G)3hn2(KJTdA+?@!~kSBae{a66J z?o%WH6B3Q@A)#hKRTqLd_6#LM5DEG8WZ)%7(ZT8ZtE14w4%Yz`&l*5=GZd9|zJl{+ zzM$gQskshQkrz@;q-!3Z0X*|?SfX1$;fE>F>}B?{|D2lnx$IMAky$h+GEz0*~{u$ zPwC0W?c7dJVgB6Ex%Ds;8E*bRV+GW!J$Qr;R4(FzM1SC*G)trp9k+YrAIJ4~GRChZ zxQ;$u5OfaVXF)zckzAVo`)>%79-*iI249}&PIdIRzag98bbahMq~rftm;7$`p>5=R zOSRE2{EowY3yMjF#Hu%I2#?x$j-F@2Xa;kK^|2~MWrQbay`N79B-o2RhG1$e#d|;C zX>uS+*xd_%jrVm&*Q%X841}V z*PXkOURkfmx=~fb=D(p^$c|hD9Bz6#5 ze#x*_bJ_if`Q;xczdu^#{Uf6!KabbfxfpP(9TcC)yXJt8>fuWWX%!QCW^^^i=}KDLbgSg z*Q=gvB3e$YjWuX$L6AZOnMj&vslSJ3>#12vl61bGrOv|fu=?4mNVklsbbRl_?^l}d z|H@YRdRR<7X<(X}*Zjbne`HFoR*>|E_0QR=09{wrREfPdy)YH16}3j8Ba{LWXl2dN z7gke6%|=N)LQg8xI1j}Bc1M5DCSjlm6ylwWDWuP|2ajIci?eqD`M4#mcl=Yp z=v|~{81_}{gs8njf;-ZbLgisM)dY|f)l@BEw7s~dinm9q zS-9gIGG1;Pn5)F3XUKk{8VI^1iI|PB;R5>QnkuiD6onf%LrBQc6blK%P}}KJ8(~du zvF`!fCmD8VDq&%U?8Jn$Qm&D0G20oy z?_R(YcMnmc2(*xUc-0GV61g*ty&r7EW7XSh0S_kX-)bqwYU;Aus^i%oCYUReB|QEB zj9IB3#cU}*EBv2cR4hRjrWyju?~CwW&#bLV!0a~E1|3B}@7 z8dUDvLJ-@s21n^Ls9F!RRm)tD=K_6l#8fAF!@wcIdDq!$cJ8hktOPl^T*%=tEg&@# zf-XRAw(AK>-Bs^t5KB2J&iDurgGo4%N}}X(;axcJN8J41@dsgm=^!Am|97pc>Xo08 z@uH~+_i|Ov`*fzqnSFq&toOyL01Lew>_Ag&jjtDHA;}3@J1W3%5dkNeF2e{x-B}lc z%6qzVuFAv5fL!&XVW!P1`RI%F)FsH|8_rV=!(Zv5JasKO4tOfGZ%}BVs!!yp2BlS= zZm%J!7+_n7qS1);l{nio5bLouPdOKS0Tu@N&^<_})@->HgINZoLcZ&PUGyX0%@1Sd zx#u^%$*RW95;G0mr5KU1x-)={oT6uab)B_N1Z4^HgYyR2*ca7TH`iEKY=}2bHv)E} zZq-1Y1446e1C`qQt2?6bKLV@X8$q9j*F&=LqCrrioFiNanPx^|Gwp0Hj zUlm8DWNNF1%8Tes-Moe>)nDeTqVPhU9#;(@lo!XpkXXB8sP;%nJFQtT0trA@hX7#F621#3wUjImsLmgPxleyIhBBLli4-r)rV{cPvo( z?ydv{wssd&=X^U|GL{Rrk2@j9!2HyDfDjDLU>>b0ZG`BjQ-`R7(Hk-H*&~z28ve;h~$W5 z1sp^AM@Tl9E~*H?w2hoPG0cOV56KJ^&hrZ23G%W1?Mp&kBGszgcw4($uV!%zJKT); z%=13u-e)84v()=E$hrdwSA|pLNTpx;KMmsS^nW7!1x@bqf3}pLyUkCBQVU;yWq!+d zg#L`spBJfki$z(I^@8+~Ku6Qv9pk*Hg+Lq+O$!vVU)80>>U7+o zyvisPB@5>=Miyi4cH*KG6)*8BMX2;aztVY?hAYwyQm2{vhhnhuDY|(H)Yr+nXNlU9 z?}9NVtaB8`=P`_3R2D4OeDuoWQR39sa zlmdTI88rN*`ph!buFu<~V<4BnYqJ9W1E4AY~;(2oxqc@OZlROFnf3v>hde_Jmu z1H)UUx0I>+e_CefW0WM2adVV0Se)+vFv#DBa#^nsXM+X2&$YYD06Je`p$j_++*mcC36RTkB*m?O19mxmlZlz+JfgX_lIutO)#96`4UkF< zaMok^K8>`t`V3$4R|$xx&U+v@bc6n525KU-25NmR4$8Z>+Y>N!&nD{f+D7GM1 zb0lrAHBqh06IVwVS%QQI?MTd}q*aUhVg>L4#i@vz)j9mDNE_y{h=Cr37fK=%+sMuS zCvN5Pq?*cpc_srAyE1gX^6)sP4mU=6Q-}62eF@j@BTZFZM7hjt3LIRk_cT?d<%pGg zmx~Iy1Ebvt`OaD{$P;mArS^cpizFYkMXy5`PDFraJPwVMyZT)(xX?fV%z-_f?tt}x zH5ESg3iz*q^&cG?DUTe}EUtS%yPqG!C7`s{N>{)~`4ko-Io-6&3o;}iOysmo{o=_j zcw`+ZCZEToq5^|CdqhdWPs2X;%Fl#VmjZ#fAfhy(=wx41_>Vc4pfSAqcJ!m3Z)xANWoaB zpt8^dgX0MqkDG)xknb{u2Z|XuN+BJZEhZK(vT>}>^rc1|mz0Nkn<6RJH~N>Bs*yb% z#HJ^n5!#R<2=>qFR;^UM$a7h`YX=pt|2$!g^+B%87S97#4M<}Bq#p(-MBo=%DHo?S z_P0`vV4$hqT3uCdVJ!(C!m!NTrWtcR&Xc6;5v@VVcIjEIA(;K5H?&symzm{FMOR>u zmf)1MljSe48IixzNKz7b@C$9j{AqZ`5&4-x>prxfblt0s>fBodQ?5ONB%&fn0243< z$b1k83P82pMJs6VMe7lFYaYMhgdC6=^M}(L7y~ovrXnTihBnH+c?I?krWQ@yVaQ_& zs)jRy8;Pz72$k6A8U9aP#Dkwwk<_w+yG)>Q5QQy^;3-v#QzHyDY%3bXHHH1#s?ODx zNYeg@yZsBMX`I+rHDZpSpN7G!9CCmBKmw3!xIY1Xuqxt&MsrwH#vUUDpY=&CBru7^ zFdB3-n*uB*=0sa{ad^4D_%wBXJD~+aa~<61jc#*+=WfCd@b@YofNXIld}8&k{^B%M z(s*Ac@~Xk215rO<4Jrg>fb?4!LS@|26EdxDAN%mKR?xGrX{Y442qfWLV+e{^yUbRH z3PpIa`|lbV6ArOIonj!gTDZ+f2<5FOHg5>yx4)^;HI5&Rf^+-Zx;qy-WC>&Eq>!fI%O?~|x--4nn^e|*&_^c> z8({j(4yxgQAO~TPgZn$ElJX^HW@zahjl?8dE;C9#jfz9HLV&$8F4fc^(l zdo=lFva+9&{PO*H!pe@d?(2qe2L*LZgkE#;ds&sXR5+_&lrUjfEBT-01Y~;rr70uI;-YTg8$ta z^m>JUqBBGS(lC&>QN@90o1(UWqPSiLus4+NH4@0Nf@rL$*1VIv| zc*vQESdW+Z`@1KVf2>H36g^*!m+SaGK$A@?Und1_?n`{_(l?^fjK7bY?*0NYaX<<2B zseN)eeY~q`9ezl+aN+5jrq6caqIgD+a#d-^ikNp^6LU=bK;LMMT1*b+=`+c1fMvu} z#Gf5-A=?^_?vLi)w{Wb8#T+bSfXdTY2-L-v_qO`dsJjD4__)c{x2a*z@aMD zobE>adO8~VK(aA7Lb9@g_rvZB#)u~f#+wsiyWH4K6_*FAk60&H??z8i`%3BQ zq5o97;FJrk1~cUk2ZDCeaH7_PXi2P@{^BfEsHdEz(rW`*W39mu-3<5D6X|Xvp6OnA zmb#?T1kro*+KQqd5B5%v(r|wm$h))nkRv~PtXBGEzVGs2Z$nLrx zs*H1@^{+i3RnE~3da5R%biI2*X@*`3!f}h`ou15Ez&RdR1JFvNf_iE ziE8E^^(Pg=1ib6M?Ufmv6~&#d`+BL%g7?U95_d-37sPh}-EfgleDsaysAl9sguubk1Vkcy<>hl! z(+=n@8VKTQm>L-q_)1CQDh^};_xK#yaYzdbqt-E9tG8-;&dJ3iRmzIQ!9E-b^nrd2 z(i4iXs5)vL)gyYV4u3Fr+$yxF{?c1r$j^R#lqz46U@%FT{cwuG?5YbOjOFEU<#mx* z>BRPtkmf&JY1uyFNlz?8twO!4kLpkiRF*rYIli}}?k2$u%H9lSOL)0-x4x?IvS;I1 ze9D9o7qr#gusq$G*1RxM58r2*@0gLEcRD}BR98M>` z!DmZaOxpG^n{xip4EIAMt0UpiHP1!GD|FLyRsA}|U7^mX1()fShev~hec7sgf7Jz1?05H9C8g8jOn4HqX2#7ju@Is}O(Q7eC-T+r^@pu( zwBFNSwQBt_0(oRn2|Y4WwM%r%VGF(eG*$8yP`j+>D$Y4iwTs|7*uj5N`GsTSR4EGN zoF<87f#~%E! zP%p?r5=64JOtF9|5LfFDz_=ZXM!c+Ix7HYXeqVzhz{Ni=69 z1|zaiz?O>J1^_GhHDREq3d0Uxh#-y`y2FL4XC1lboU)u7$X%l!zfhePenGFk5RUt2 z^>F~UK-y&(h3NQ!RVR!K5pby=WnS(m8aQyE8Wt^g4%DNG2(Qq0Tm(G?blk&CUL6=%v^^Z zDOZ$(CPIAI&c!Mlm!gA=0Y%{`N|7WA+@tGDVz-D84P=&y1N&a4&%Ia`wjpYwO_-30 zE}ovr_CU<%_{7}DPhd(InDXq!s%bTmCI~o{Y-Q`ku*BfX+e?7Ixb@@`W%C*c(lf4; z>=H8oPz@;7pghn~HU;AgGNp+!y7 z9WGbb{Rt^JDKIcmC2qEfW6HP`=FZdrd73ZeDT~N}ub2goY`t3f9AEK#aai~7tLoJM zF<)pSLfSu0b-x~-VohyM4c&d4gi+@Y!04vwE(5^nr|WA6K((2!?;oH}&)nn@!JqWv z0pJOhy9i{hQL?_6yn*n(ZSwo%?SuC@f%iZu%qNx%j!w*BVpnjFz|YC4W|Y^3SEv>_ zKj+itL%AnLE4oC3X?=z2XYq}u}{$Q*FS{AzH^unXP_Q{;3hRR^Z;lmt~k`IuR+)sF0Hv%)orl5BzB)~IY@=p z6mmC)WRyF2b*R4RTA;#L`mSqLOYVdedG#{G{N`V)I$Pnu959b@(7=a>2cSoE3m!3y-pQ4zvUa3K?H@d zE01vX@cElAzFw7uf70h&uiBYS$||L6k6o|&U_rNEuX+`EbFwGm?gDPZ8?tXjdV%p_ zf)lf@2lH91SR`(zu`Mm>h0 zZ4vb~_Xed>p!P+1+OD?Vc7w3=qc^B}sZ5nBk8`3C7Sb-G^(QJwWO@4GFRFf@ zU{8?aO{5gllT0Ez?omcR&`+{!;F{l~`KPMz(wkNPW)wdX9be{yVa8EI<<{}U554hb zbuW|uvXeee>8l3H8vkpcYE(}AOv9ceGQrt^UdRfiCYEHO-$X!&%4T9ez#^;`xX0y8 zF(RWwPecf~$29=}@-yP+(fk;qp5eJ8*Jkh#ume~M>?=v*4=i!vE$T1ldHo!MsOaMo zfm$iH89e~9F_#V04(Vddpb+s4lw_PbWB>^jMfwAhrx3yrH{YUKt8vJzz@~XY_AmL> zqCgm!ITz}Nx2onf@Dc(r&#cm?a9Ce+D{O(|^pIOs?f`f$AQPb=5E{a@+}Ogr478Ua zSTu141ViO(kZe{XuQ4NH^T%UGlb9~~ZxB$T58tYKgHE}BRkd%14+GW85`6%x1*jH8 z7%mvnT;i80EDt2dWekUDJbpPRW`V<1$85$){_zv!(R(*H`5k`l*00>JN=T&l{1pzX z5xV)`l&Vj80eFa0CH%~hPxooGj>z~C`ntcVZr1>ZJ@F0@IL>M~r`hzL%d`rF;hjE2 z6(22D2i34O9*rFc5j5pCZ19!3`)#Tdk(I6M6!(LXU|M?YZEy~)&o#?| z5Avc9()e~L{3~?7+hGSgTrvK3&{?eEn}3I0V4S|`4(0v@BM?0YZ90Z3vW>kd?xR7* z2rl<}fXji3R&rGDmPk5^!L!Xt08!RaC5()y?GWWH>mP{Y{#2LzLlw04MuLN+Nv$NY zUyw;6t5Hsl>VN-3bqznF-~0!L@QD8EAMhJIq8s0-TGV-@lu1^qES3J-o$A>--H_LoiH1&Hzb`$h+ZW{cZ12rDVD0(KtT3`5rh@hSs3@R#cW8YbXxawWD|< zJQh1bK8=5e1AQjVeg82pd;~`#N6awd5ICui0PoQh!k@C4@F|6#1EY!(aZ=I`rtnl z0*xYf0inVNZZ)uumaxbAj(@AJ`QBC;0d3k)esliZ@2dXr-{wq6&wGKz zloRqH<>H?3z;es|a(^v{>bycvDObzABc5OtFcSWCu(3U%7 zaEkS)E+3-u>pxm5h(=2xx5z^XroagVD5^V{falF2syGpnN7ar&*ZSZPm2(-8gJ45a z+;4o#>PtQj(>#dQ!tPfy%-Jut!Qv9UPiL9`{zr2Su>T&$qK*2hq0o-M)(;F-r{~PE z#kCeO@+2r}2m@X}R5c(9d_NTN;YW1A1L}gT7bqzluLF$JEgUsRHyfr3t07o5B)!y( zh`(YOEEmW1qr+75R@mQZ6d)+8Q_%Xk4)jJ5+yl|f61eTLQhjiks$Kp&!A$=#I*)^* zB3e3F8EGg47E_r=DuS`iDK3)vpxvl7#|AX57nQ-zoD=>-&7Lboj6W;`g9^*^v&H!c zX&EL-Y}imL)R~wy-zFc0^$q(ManmBnn^MLG1NucY*k4DQ#O$MmFb?i)cuw8=+bvYz9Yih%uBI$;PaS`kWD}U&%3Df;&js##k$;~#b<|BlF{Wup-5wNRfJ zrJQV{G|(gkquQ?_hgXo+j-y$oI)U5Tdqs#z)`wkgAh=ppKZ8&p0(PI4w55 zlu=bvzb%fn)Yl9HbvXKvYF+$YzOg-jpC3C)R&9>?VCylN+A3Ito|MEb=Rd5b!f$@) zVFcQb(C3a;DsBu&lfwnb8Gz|)hr4XiCG!*!c%$_X*XA%+;!C5kCO_!aqt*2=X}1^y zx7f4#@-gb>2EM3=a_z`xFfm{ZAlO`nP5R3*>T1wX_Yn}WP5P2Y)ELaxSC0T8SLo8Q zsw}J08?Fb8RlUoDN1lesV9ds;a!9Z=G7e-7SNZ-Iv#f!tV!TbZEdhH7)lvw6ZZr;Y zDd_MxrSd9`uYegGhKCso>=~N~ky9AH^S>HiXN`m5^#{Fk9C*st+8(cNDEx*xJ0^^X z>vix}%o!AT31&;dZCsPbt7b)2Jz^2-)l!tCizejm0*#)+45#0XR}JOzTc6fui*412 z)8H?F{RYV%#kz_e*Di7^aoG3ApYy11purr#4UbwUMIoWy{iteGu%I;dbtIet*$?}Z ze{clB`YxY=Jdv*8$Y1NX9#yy1G)clBX+W$Z_Rr9r9#els2+pj>RNop0^5YS5A&Xyd zPBL|ZYGy1lZ0vP%qb?Ivyc)1L#?J0}FcmrG#!pZ=dhi65l6XG30@?TY+(|qdKEemB zx#sln*N3Mj9`n2Rc-?|ZXnyw|FGkaTCH(F^{wYv5zk83j1?uK^@9}d1 zXnr@3+dLA0=6B3(3AQ=N5WLV4_v2@A?KHzUj8ke~7>A~wctR;E6Lp_Zow`7tjlGu= z4iCc2lV^NHRG>5vahHW|frZ6O^uUU?h1_Gs#=erE*b|;m=aXXdJr0_#p6(?bI0T#^ z9e!P_)nPg&%rPuU{km3fei9Uqo=4`ebG~@X=~Kx z`SK9h?$+#YmFH(MczXn}zm;ae1Q=3oz|S z;cbo@d(zfmyw%4hDoMtiKS`a%#kygVI^zs3)`pajw^Umzm#W|v_v-($P@5;IbN|yq zRh%_hZ9RFVnoof_ex*KdimKD#Kd+R2V2bKjWoWtot7>)II-cot84QNYAM%KMP8;gwepfdizwB1J3r~ zR25G@l+9R><@(?UIkokYsp_0+KWF2?;8k)f?g(9H8ciMgvT5pX_*gj&GksWpjfasT z)pU#LN~Q9W*RZ~FnrftPn+}J_iHou# z5LeNf8iI(DOL&kxerAFn*w{y+7{~gGfB`D9U&m&sMs;_W7Jwf~r~n%wMG&g6Tlbow z&M;Y$F(J5z0}LJw{i+|Ip^8WmSn0#CP58#Z$acmJo?KSs!aykp!4^r&U-;E zz~PLKUVwBuS=XKk?QF8{I}^^X$rVq}R81oI`N1sJ7TEmzEY-RCddTlGTeACf@0TDw ztk+{+QiV-@ddaJI&Af_b(!%n|RW=eg@g%y4bS-+zORAJ1KB7Va1FFr&`Nv_Br&oj) znW>};l)dxSr>IPf3x$E17KHI<+G`O+;AVfKe=POFw+bLaaCvxJZlq^-LlRj48KkyV zA=+jVWhpQoNVm95SR!(;7XCjx(+6g&b84=n8%5TLUW!z#(b+GnqV8BD*8Veewo|T= zXgK045{m&Me!PafNtXqDvizTVE7h4E6OGLE$Fh2Ba7o}wUH280?;OP_ zBu$SQg-8|z)#tr}vyi{yS|yyN{}tCJLF)WfAASWMh!eW+9Q7Cm2_G^gagl*6?CT%q zsNBl^g*#e<`|I1E5Yxo{)p)Kd`0r4+%vGmX`=$owk#1S6=5KoLTpZwrO1gC}4q)M` zR9wi5;14Jq{NuQ6KwU2c?Jd^N?EHLzp5UnakQz|f^7Z0`mI;hWw@!peoeJL z%W!I;*rYf6Yn+k}L!MqVU7oyLy(=2m|8-H5QRx8B5LoPA8 zwph=aKs__U-ce;exk8-}eE6;cpZ;z?1E{P>I-f#hPx7aEyJ%*FkNe&XVSuqSx1{>@SML!U+^ zQZRYpUa%D#%Mk(uX-4i( zz+L{f8dOu3HW$9FFc&t*+NSrvty*|;wzqq8PPuhGb2Uog7WQh`t$h}&+}hZ!1q`(v z9C0TdPIucd7GTgaoQNH}SY;8RCM{M4H6aDh2pf79HDqWl9wU0;`^AV3MW+~$Srz^)h5~#@W+rlX{HzG@2fa^%o3IB?#hAP=oBSz z{o>YnUd*^(!e_v83WlMU=+IYs-x8b;U8%E|s=WVg6cKA@#d%9{Ab<)2=Ba>FG;NtG zq?h85H$^1*Q}xHo)CjoS2fU*mw4-+L_Z~IPhN|aC^b4<7;-_C>w%ANU#Twc%TQ;~M@Zw~z``wH%7xbeL1HP^3uRbe zK6l?=An8;m)Z%LM2aJh_-xV8W2VSXZw&#!!RC)Q#GC9mTG90Up{5K(U;)X>Ar@GHh z^$=vKR|LyVXSG-9umHre0ED5z=*c+--B{bG04)3n(gD_Xp#kn5j|6}p2>`=+kN`HY zSH#l-u#W~{7kaSz<5j9xc(Kk~4S&Ony5DNmq;ytU0q8!HGZ_}bP9ep5Vu&mJvQt;9 zMkE*ns0aWgrfvIbb%_sv7>3`U^Crn3mOg)tvUw_sH5>{6;DS;!6p*+`SN`Ls92u@2 zjf6uctcOL81d1dkJ}~Xa0`C_Es$^w-5!ir_UIlv38rAs%uM@+gmAzb?C?S_2BHjy4 zj>2J5L>G_^`FRrg_~BEO(|4~`ZT#VnJr%%;wd(T7FiRJ#Q(d@^uUn@&KvbB#PCez9 zn{ldg=dD+58_zB)4EF;SPU+|84`)Xf1>oQI7wVPus!gYt%jBf9kCICRfH+T(m~M~( zkf>|S$4C%)@TbMP{s#3WZ?a(%?;-u`qn?iTx?j?-7WSacBfXa&`0cm*P%Z5Lhua67 z^UcNHj!Ud!()Rf|0O4;Redb5X@n;G56nRxNa*w0O!c%YmNY(WL;DSm3Aj_x$cu{JD7#(4@jpkGi<%AYiv|S;R*V*jp~l;yqVvC{1^4f zltTp!w8P!NG7uxnA$JVaj8{n6_}-K6B;Y2aPbGQToLmQPIyaM%`H31qF(rA0O+)@F z>p7pOdH#f)3JvtVpDIc5%UZSoPgZ(d5cde1W;TAR`u|Ue^ktxQ0+9xMraJoVBj?=7 z)zAG5!p_&^Q9j>kt^(WAU+d27qnC6c8+>3BZOx@VcDqcRPHmYXfJ#yGBwlgB)3Mdz1BETJzxEyu} zeS|{Y@cz{gJP{MvU|3bs>s`NACQOmny@W~nr8n|*xhKcU`@Tu`U-Caz^;-b^S076OZgRsBVsM(*6;wFX z-}?zdNr+(qh#>(8_%~4`a5Tp|NDjAUR8QNc8u*(Er(G8P;^B9Nzh^gZQ~9;$mc{M@ zMl+@R!m%0d?m_^Ozdu}YaF4n&jOzvZ@52$8_w}HC>NIO{ z8CEcaxrDD9F439#_H;+-&HGe)K?EWs(lb)t{XiG&hy4e>xcw4~cH4f{>Wb+wa*=Cs zG{;lj%{~iS=g%8MB{O+k#lRp~>8V7_A)gL(qqn##hL4K03jy^hM`OtN#E|vz{VMOG z;nmtnloIYhV#E!|iaRaMosP`Mc!6`{()1XT3n@l?L@hYg>33^vT4lG^`>XNSuT}1! zyna`txND~fw>1IclM;Q$%9?=L!ZgN4q2iE)|H9%511DmwLKN@un8A>Is6YQ&^{*?& zXL-y&xanN}(VpfWz(V|_|8W2g+rxUw0X6@$*B~fjS9raJuRGWmYNkSHrEOvb8$)R4 zgWssPGhQe&t`a?VpQ>AvTPv}Uus6=qEgi?!4}FVsfQ$J(S8w6w{KPJ!b*aH z$XUmWw(v~`D}Q6Mr1QU1P7NFy{M;CPQMon0;-2r+fVA+jij6<1(L|MWwDEdM)iruV+PPd-)xd*aH9$S*i7fkI_R5N8To{1NEP$Mnrd;M6*%M<2ng9IL1} zqKeXN&LL{VXgLFMWDj@Ct-}?EeuwQbd~884rvdA^DeU}xP_Vtsgu{n;fAX*nX+`$%N# z6lcmpVi&H`yVIR!eR+!)3~1KMY+OBC5J3|NAD{&q0UfZcL9mxc)++c|&>L%TJ@I0M zBrgLB0B!-5OME`3ugq{R$_9oqVjj2egv)iyET^7+13q7!6?I%)lIffo{*c&JR}ac` zngP3JXF4rGCBDdXoEp=sAWuq`b7wgGnf_PIX_&Y#GD;eOj5xsAEqZRui6cethcTx! z;H+#Xr^LTkGD@QF!Fg9rdXIPw8WYn>(Oa{f&RxxXqT94VU}13Wnv4AR*}TkKM7uw( zj48yED(lb6z(M4i?^9F+Dp9pp@<=L5G$)TtN1v?b#8ZhD5q&!bs6VXc6a$SCXSt%N z54RTafR|uM&Fan#y^Kwf6tKoX>?LCyBo5^aER-||#5mbBnE}ip7+*?1Ky!JhH;KYCho;$KPu>XT`RZ(c+6(%6OXovNn2ojpd}m!1ZU#X#@3Y zqUF3+i?{uf+9RqFW^?6fQ(e={o4_2^*yzm zYue!gVIDm*yWM-cgJF%rC(B|A`ax-Abl7KfLa8?E`)WH^qVCPLop|n+GKuwpyE6&W z+FGU`-ls~fuTHTOmA*d5Y3*;vNjXl>w%CrTSO6&g66Udsk<%pZ>~94Q6W~Sy(LH#< z&_n)=+jX2mUe1Yv-}TX zy>L51FCvq4V zaTv$-^cl)IFZ`Mwt(<3ZHUT892dLTIj!?6ij?>^Yj}Joklce~Jr67r=bdypjL5WfH zv7 zO5517Fi{k@U-qcytvc(#TvLjRSaa)$PmZg~!ti4z3O_eE9w>on!KyZvX^A5k9rO2E z9ZW4nOGL`mi0O}Wo&557bztHP^~4n>Zn(f}I@~j*+;0F_{JwAOTZ)Z&ir%F%?NVCE z-7&Q!b{#e_T0VtBS0#mwr_h!A(H(&_@+|J@D*oHb$PlzOF^na3h3-_H4%pR3nWJSL z+~-a;9YhD961V^+1K;%z^_-$}I0+^io^y(TmP93Q_zUSxTg)O!jL6(0lRijLvyj`; zJu)xDJ%j6eRi4xKG)SvhLPwsip^uV3OD)lV3Av8XVzExZ>VgPn3#$W9{rh>&O+ibq zF+&IaEMFsKm+oKRacc8QKUs+!Y2+u~cJ=T25NWEuH=RK650Y-wck+Wy2%afVn5n%e z+dV);&?R4+B+*@4QPRK}5;^^q+6;d##VI!-DICXZ5%;lx;lO>(pDbDqAPVAA+=8$U z7eG)w9Cwl&S@dxDF~FFCQz-c6fHx6U-g^j9siMOjEjD)VeU+YL2PNJEW$JO&^nwEC zG9IuQ_FRUGV-6GE=V`q9g-%DrM_yX!l!fQ(p@q)*+?WK}8*$(GSx!YXas!bT5VE+Q z^o(t2vBpytS zAO|?N!$iHS*pVB$4!oA(PLH_zp7sp2zK;sYPMrd{jLs`@F1+l;OtgYk-4KD7y(=ih zbJcK+37P;{n};PEsnO)`*6b-5zd?^Lz%6GAvXhG8U@ILN!_7R=<@v{lc?! zpHkMO-*Tq!5BHn<6ed_q}93 z!0#0K`Gfxx>4C9U@{DKVYQ^4T*fgH(Fy(&Z&BEK7IS=7likq9G%_sHf=1#MVkgyok z6BA+t5-@b=`Wu0TqV${A@g%8rS_1;D$P$EP_E}*nUv;EPII58L8cICM;jNU zMxY3vZ3q=73ijZy2h%k{H57Oc!58A{(07pF+&gaIJAnxOax%09XdE#Z_6EgZrZ_1# z0;${D3mae=xEq5l#7zq?tfQwkjxtgt4NFS>+zm6uEM@)5F2Y%1DnLo!Vkd==p3LH@ zTM4knkI50eq@`0mht!Ouovp*xC582uEu97p&=__tg2(;5Qf3J~1k&|eVa4a@uC1Jx z+wf+15*OZSn~EFbjUf6>5raICW=~RJBF3DV*fEghkk%`7ytUIPV|FbFy|`db_igQT zvM1N##Yxg6qy?NSf^$DKQ1_n>oxGy86R*2BJ7!~d87hRKh_o28_GRm(tsT{P`>9{= z$@Y?&p@+XMh=I0F)2-S#b*ha5O+a;Mh8wJB52rxi*~Y2GT{g0flUHSz&1>TncAeH( zQk^B*CSntLKu?kziCX}r3MQz%eUx7v8RhZx=9C{z%D z0yC4b4C&?6F0c9`xi~?k{XlW>J{Ax3l2593h0l`fGEm7~d7^x>%xj=Bu+oz%2)- z48T<8W(!Cbhm2&gTGwpvbZZpgYWqMljne}ZFF{gMQ@ZNj#ZGU%ti7X`PlSGl{PlwbrDNCOZ zkwou5-D%@&g&~1@oLtr@B*91@x@`xi#5Tf#Y(BDkp_gdq?;V`l$e{gT2d5!yb1!yq z?qlBF@zXP0xPfOK6&W;`$yvw|dQ1vQsP6($zM`-H+!1>2c0Hz}Q@6=(=Bh2ndD<}e zB!rZi=p~J}pJ%#~*b7OBd0i9)kWu=>j!s|Rp7qF687}T`Trc{x#k-p!10xEo{TVP& ztwuUhXLzd(zW9P`=7Qo2s6QJpBoRCiQ_3?^xb~#5lXC{N|EoGdj`>QD=;Rnb2oQuJ zsIuZB$ec#*D2oETK|Ns0T!qgevHTa&rU8`u&S%0@`H{Z*Oo%V5J@Mtqlf{=U6kqD; zMQ0l7x)!Ap)OCZ%Fvre>IJ`x->Fo51Y|Yf`MmTk45j_fuPDA#id3&v%-5CPZNBZ;5 z&e-q}0EZ&oQ(d7IT2sziKCQb9e#Q6<0{)IvKetVS^6 zy1)Ts^YoNfUkt06B!}&bKoGmLo724Mj^+s=h~q#!(7uz!ED?gRE7o;$N);tEZ(Fhi zA}geXu6CBAN|&MR5;Q;twP%7DKW;)2o1F zrDvSw6eUC`Lhbb{KeY&D794281=sbu!y_|CpVd7{gyNapWDzP8o<}26C5uoE&o&|y z?Uxn0|JhFWtV$6|KYg~-ygVT>VSRjrh*M$`F{X+j1-g+Cp_1WG7NK@F#zoV{00t`S z{s`8Bey9PH_w)h`I>i(PnDMeI5m3Q?8;C?j_5)FO6OeGw;U zqBp+S6@)TkR}jh#IBuM#IC9@k_YCCT|p=#cKNN4n)+f_RjA~;e6cGCHRci#yMmSZVpkH_Nt5J@ zU4Uvuu`5&nJ6ei66We^Yh+Y$WgSU>+>;L2w>;1i*ScgZN@aT6m_DK{%03wOvw-iPj zg(y>b#ENBq4!avpl5d*zaq8tk79ar3bqRokg-zek2Z%gJPwnHhteeoc+@Twk8_>A) z{yt8A;Pr?quM7G*wX=O90)uCT?%LNWp&AA)2ET6W>y)6y2aKvlMay#SSW^ zQpfv!ovu*LGx|CGcvU*buqMs@R*qp}=>SUQ_Yl$_%;Hi~`ISSRpj$W%;@Pagk{IpI z9JSkrWa0Xp@VW5S&F=@T-}}C^A510ky){1Co(tp7bbZ6Quzjr5Pn_!%HpEF}w8q0= zY;zoCObTx7&v#yb^p&xIQ1y?Zq3jz1=%p9!1rBLYS5h2y?^A+H(nYkHyz z^cTLkR^DGI&71w55((l#tFIZ z0A{}zaGNCo|Cp$qZgG9>c}|CdF|k0mksaGB(80w$_C~^d^E`O5;UPNTiThnfruDsz z=-3PP7~U!L7z94K$2)vjbJVu7!;tM$08YK%@9>^v*vkIWBmmx_*Prj4cE&`cQiK7( z-5|+JQax=y#h@G~>HAd5IAgAoaB{EoDx96_el^*%kHY3N8pup1r7_uKgEI<@ z+za{B{1KmOpGyG}k~m~Av~Kt~FpPw+(F>v4jM2AV2%6OILMN8v(i zS~LKK>M@b;|9MnzUUeCc`F%xZ@J1dH8Kz<5EzuF&fKL6;aovy2E_# z^8XM~Kflyz(e4irb!rtvo!-PhPi>G5z0A@2GN)F4W&o2sR+)sBK4kq8A8W-vhvV^| zWLhsZiM_ym?^^DP`5Epzawq+k%P=2L>a5G12GyYyh%*nyS-A6cyUU#drX!0A_*U=y zBvzd1NB(xXlTU*KYZ`+X#%HSfK4xC-E=ToH;i=kM!Z#oj<*INaO+8ZbbxbK~U#S38B2j)+nfdhyjjwx#;ptDW1hwXeL!d6CC7GOl&r z&A}-Ug2D))66hZ|hf$vh(#7@-$7hMYW;MJwCz8*%CXY}anou2sEcs-P( zXLR)&oNqwXOm1IfsN|;FqO)#v>f^%k(i@%As!xd11eO)g;c0@$^({9#cca{{8(|Qc zrnCMGKiE`V@@MCrtDcBsdt`8c!47l8iB9oHe!M6+TpYtqkC&Oz#`9n{EAwiI+uROM z$2njj{(Kp}ZG)9XPZgN`Gn-WKd5e$|Qx0pRV+BMz~p*dN14z#9LY`e)( zDS$tGi_ecGmAh_pZbJ6*FK%<@X1!|M59zo#WpYueqi3@h(;7GaP>?{V6Od&@ec$I(b|9YT1JSO@;FV)0jC3d&8Dlc!`N15PJA&HsciFH(@R-1a#o^e!MP(L(bF!rfa|O z-%bYxC36cu2YD+XIV}!IPUNcXB1@qLPR$IcW#y9U86aeGZazH`Zo;wzw;wK+al$7Nt>Q1 z2gmwU*DH7GJIs8~1D*aqaFl1CL{PLouN+pZ5A;3dPQ2w8Ok$kt&itwbGHTSJ{`qS5ahp-|m||A!*1$ zRe*34wg4dstAZ#rj*PgYIO@31qVr&Uemd$n^JEeh2}{@%Qh*2|xC^q#1QZpJ0E+C& zj))=x0^&00DCqb9S9RapH-!1#_tVg~t7|!R>eQ)Ir_MR035+1U>48+jjgucbh~bre zm?s2ae$L;2-8W41;>L?LtPNy91uHiu7<@3IBYD-lMv?F}W@Tjdjed51on{q6B=z%Y z3x6I^bJzW<NZ@oD%NMenB$gv&|J~PSm9nnz9ikUH7wCGHV!A>y=tED$)UK~=w`<)LjV0yvt|B>P1W_WG8Be0Hq zrtTOa`dBN}tP!H1g+U6~(bVrfZ!O%@#96IJdiMx%$v_Wp*jlXc1zz8BMep1#%0xF8 z!D0_#%Wg(hCSo<$l{2|uH$V`tfaBp1PijUsQ-2x>a9&fZMvCqgi}N5UCL&NGtuoPh ze;gMCnuC13W1=@qpd$b9pWR&&pY5kWHmHSY!P3Q<*(9p=W|238MJht>euOpl3}Ml6 z9E76_=M-8UEClnDU>Wvs3K$bkPCz3G&Ziwo{(3jV*U$J0aT!$dQt}M4E$vyUhXIOg5 zE2lTx042#iU_H<)gdnkcUu~oQ7ao z^+g`d?MiZMeUJVVerptzsOR%hh$%@77KNAui=uT^*t6|dN2*{qu|=hi7Ofhe&V|UN zGo>O^Dl+vMjm6AsWoe>&s({ueNs$z4+Yr{>7@aJ!+`|XZFRIbVqRlm>k>mi21jp26 zG23GMXpfb+JI2w<%|JQ`!e5NK>sah^qyB-l%aU=}^sp83JCG64j_ZXqjV#QIo~m#F zHpUtpSsC$0wrq^(e(&krNTL_Kf*E)Ez0$P+1Lz1lmV<|B0J?z$i7*mW!I^eR$HW6YP>8MR(MI_T3#8F9lAk>+$q9D~^ z5}i@4#)*=W1$lu!O*0v}endgih&Omr|1(Y$6)i0BbehEEbFmT_t8Zt~F1=2@gPOUs zWsb$M9ML*eQZ=kUH>&b#k>AzJ_hPEM&{O*Plg^tw6e3|0p*aI52{ao+t3`9FG}yp@ znsx|vZKj>EUiPM)^P!%{{)BTrQlC|esd?0DQ94IwXIi@ILs>X^oiH9N!!Gs8crl>N zYjSj|JFCIDhpjgV2C}P;5V~KrngH$mN9w8x;?`gPGp|vi&x2q*x{uQe`<3K|kUh_W z2|OoF|o-ty1;SzI2Gx)Ihx8@N;Z<^NB zB@;!n));>?2hoQ1v}>r1p%Y^R2n%fkYk(-!nu(%($b6D^qp;tp&3?vnI%V7_2+D3h zPfuF1dGw-}N?`pz_4>|9F~0wKtdb~6X`LTbBRb|Vu{IB@st!7esmoc6%aW2k^`?8G zmD!1GR0k(Pj6bcylf@+&aag{3GEOeN;a-;Pp8mp<+yf1w;snYwFF6?*2FOOnCAw z>?n<_1s&)s;9JRtZ-Ks&*beT?fX)fzl%yTzKmf2W#rbs{`^3%6(QZZ=wypv4Fak~w zNa$PUkM%x3FIolw-3$Qb=wB3oa{1o)SP8h=P74ZL>39T9i&{hk>^~J-y4`B_ zgE|hqy{<-VDQ6(D9=OE?EEG~Ni;Fmu6l^mg?3k*)PJ>CxbxL%~H6|%Dk)NDbn~foU z(-TS*T>y4WL+AweLyNL%-yv=#E+q9mzzESw+kIt*7p7yPzXsQSsl-)Qg;unIros@P za6dw7>yS-->FZkmlIXl*p8X)1JB#@nA}H8ss$mp5P-P;Nba1rtlyr%nk`7Y=v&QVY zv|+Bs%?ANU^jQx)ekij*VM{pd=k1`Qj4D{5T!CmYO;+wLo%5)QjzI5y&%>R4$s=EdbANKf6>U~#iF=;@W{hOfbL zVh_QdFDT~D4it>tKZ1hr(i`Fqb^l>V=U?i?4AIt?R$mGfiXDb=Ub6CYlXWEhs?e{HS7%)1ja*{&wd6v+}J3WREkjOu7XolwgzoGA*6wUQQQ zFwpmN5`aHZ58vBZHBm=czhP$*g3}Q#-?2FO09EurN;uLTY4fgW>q~qcHj{lQ@uDF-m%-& z#@V7LP4ZKK8+-v%04Iw#$`4{=SlHcru8~;K79V)%s3kd{wyWZcG|;E6pfGL4MzOb^ zbv=gwmI9_xtZcq@y;}AR{|5*|7EybAsSI>$| z1uVsK^@4;QE5zTJY5fDwio6Qgg*EUsXcji3juzm@I;Ou7UAQgxB!oRK+>n@c-A2ja z56E*_?i^or<7)%_O*=TI)=+S9NQ|g%b47VEmN_Wj1THB9y=rbiNa>jP($m!IyqM~9k4gU80|#Oz}S-rdlI$dVoGHt!qRt4Z-Rbk!erV?T$|RB zD&7x{v`c?P-F0zJOLgBok$b-F%(Qv1VA-W==ZRi-1?xBN0TwHZLcz3nD10Y^AQVh9 zP(%yTvJC^F20jOi_h602t035}?^O`2k!kg6G!}2cg2u2d9$==$1N_a(3S&_pte@HH z)o)DVg7y0*aY&2yi)r!o`zCS0>X{a+9=9TaI%pH8Ni#a2zLi|Cex}9OZ*s*CJJVwI z>+0boSg(4fR6=4pRuO=oiShNDZ1MFotzP|bI;>YeQ*`}->X{Z_JyHgqL$=ITuYP0r z7ev2r_!mTvG5kBG7QJA|DmoGPp-I`hD}jVEs(1S3eE^;;Z)!2EFPHSurube&1p+SU=P1)qnb@`rtFA{(v$q zzIu}j^wK+kA10dmyYg_ICjnrJ^RUB!#{qa1UMNy(S=cBshQ5Sd&FR{?FNtJJEGp5g znS_jW2VM#f;&d<>5t&xMEOuD))$~`yF&vPNd{s=hp06#K5BVO!1codS<9Hu4?}DUo zm4f5)B5Ej;9dpW>&<~8;NcWKF!t(T5Yvcp2rmUjX%8|3Pl&(yOy&`T?h6L_hLM(Iiz zxThpB(mkQ(za_3k$IiYb9>q!eBX5f_){>fcMBA2LFBe(v@mIaGA8Hc(UesRp zj#zA?$sbmU8K~#U)mqqDv06M}PUMB^|E|$cax~!@ur_e~{7vxqnSwbszM#iiFA{m+ z*x4lE+negucN;i%&Rv6J)LrV*HKMRn;E1@P<72Ja1g*IG{8*n5H(yqgevzgmT*}ju zlXQh0BhoEvu$Qk=U$4Od_fsmR4lPYnJ@BA@ip z5oiVzzR2e6p?8TE{)~YUgUxI@mF#{Uf-(X0@41X|yA+1%G94%H8W^0I27%#>0WkNi z6^xlSel4`@d(GJ&JWMF3{mT_TENSB?jJbWlrD?H{}H0a6wMs4fo;%-f$gG z;0?Fn1m5PEZ<$_$xb%h})u45v70;G;!VCeYfZ%&H*)dy=Z&S5`7Quvq-1DI6cw` zj2R*?GQ6M0VoX7lBD~k>ceDAJ72W^-~cy8T{|S z-V?b^-#F*9E~GZTCz@XUZrn%c(h04b;_lj&;?7A03)fp;_vOk+VVe7y2M#nsIP}_^ ziksLAL+pusXbzL3GuNC_MOJ6yiHH5fIp|e%p%LsP068*A2IVRVAWr{KH_~&Yo0^3wKj2gCl3Z z%n|fk_gohNIWRyW3Ss?x5q9J}%`w4#A~%Jpr^h1h*RuS4wZu;tGD^P)!r1!_$0khX z2MB_Knc^=>gm84&t=7j(ftkf)7;_mf=a9uYME}K6$x)+wJuv^NWQtRp7B`Gjz1)~0 zg<+s@l8-{QA5dK%xT6rB93Xl>K_7G@{yEg$FpU7QY1t`w*C1heDFzF|8EHJL`!|Z* z%Q5$2NLdie6-A0~8|~W@=uwO1i`MqRMjTCQ-_(d!fUIWCsM=?sP=J9maq>CpL+0+=?^dNz2_ar=EbNdT$nm+7Z*U z%Rp&otGaJ9M27Y1)y}2Hcy}m0Lx- z^ew)SjN{6oTZLqAr)@>##Mji8t)f}CsLbcSzNx93R?oJSS{VXVPwH#AO|*)6-dwRw zbU~cJq1(h2G{1dsoADwcY==Ycho`h{1J}KmIFu1&Nyl(-LB#C zNp+N-*{lXvP?&0reu**_baFZ#1$ZQ;in%MqZG_+>TBWD%-wqpvou==__IlKaLD{S3 zZWq0YfrHzH4p&Pu?(EJsfnw_~wE=rry=wYQT;IS?p( zZcFOrk3@D6&emfE4mDIj9ry@ZyuAp`=nnv{*NgV}+TTcoi9Lb;d$drD z^Y~o}JEN1`g0gJ)XH3dwT7sc{u)WmlTp}F&YNzoz; zqzjH9Wu6dzpD%Nx^L?oRKTVBfLQL^1L9oU1z>;;|FN|D?@RJz;0<$mCGss|f5kD@9 zow)TM2(t0l2l9e;_0h2(MW!+X`DogycV zTVX4GsbwXo2Z}`tbsUL3bTkCb&KyoyzbK<66`^YK$aCW*?NdE=i4L#@xqFwmCTm_J zcU5SJ+jjtYr?XSNy-PHOz0!_d*h+7z*)7B^=CpGSRK^)5=nvtKHe>^BWEe!gD_5?s>}8WeTmS$ zdqmGGxwj!(W8)ShE|J5L!J~0JgN@@gu5l~SIQBW?)h1WuU2QgPSX|Kww~yyYweo-C z43Dw!o#8QgwJW^3X0OOgH@@$U==!ei-;0g$7it#L^P}$8>w{j{V9*Ej_blkq=?6m* z3$$=peY+Qn+2=~`6CK)oQIZIlu}z2#};zkK2exKYKs>5Zl73+(Ah8V7cFz<@?e*2h;dqf+>q|t&Rn&Bzi6I0 zw+Vgv-Jg(VAuai1@!O_bOQTZn4=b==vfcL)a(<0^^kZxg>(sQ5McDvEtt6k}_YvNA z22Fx14XveMJNSwYgG6g7s#Y+c&R45*0JeW-dEzpvy=cJe&;Y1iht$M&b&y;6N%#S3 z&>-4k-hlj_qa{(xK_qdrVQz!0??(>7KpLb2L*rfTseg4bQ26f;mAAEG`t98h$Yje|le;+EUB4&AIfR7` zAabA$h{lA670xu;V^v5oz5z-qK}LWHV{WX;UJ=3)G%x=;r#Co?jTmWhUU0~_*L{mW9e6Yao`UP&*duGo>&v9pe6EY>V%)}B0>z6PE zVD$dUnRZ3cVuTXC@Z|2o`L)oBi0ysc!F>YD$gz zT*tt9=X22`XF^6H!hR&fQwTe-RM=qSz5$hbSai5aJGg1Qy1%@VN`E)dWTHENbXfbo zfk2X-iCUCqzwt5|QlC!q8OMZ#2F6Q`JuKQ>@Iy0E1hL?t3FJ&-VsCh?XhBGFkFv;%AgbWvT z1E-szIs?MFyR@}DHL+c`qL#njJ;JPaR^}hq;#-rL#WaGj7 zVFmJudh00Mv#Zq3qvFQKo0BM(1^TZpJ|>#pLh%~Ik1@d1rCgZF2|Vw`6v%Sq&y`ll z7-88UBNPu|Y5G1ND0%bp=zH`6K&j3 zb3sqQO%X?qiOUeVsq{;nx$jlieF-Oyy|t6RM2vOoTUB!$+WGbBo#W6U?@*r{hbH@b zm3cyRzRrgnCvD#)d)rjPjTnjhkLbGwQ{Ae{h#4f3vxX6xUM>-i^IlRRoRL5zkeGiB zCa_LTJRypcW)w16y>$W_l&1<|WLf!4(v=t;TCT&IoGp}QF__=0sVBvSu}+KlPM?Nm z{{Pu&!NxUdV~7QsMW=)2@iJG!!-4(MqzhSo5DP|d7-C`fqj5>P6+0FPi1~B+BDJ?OlAcc2(H@qYatuQ zl1!buYX^TVZc1vdXCycpP?T@cX`;)PT9D#KX#3fQmY-fiN1CGBzlh@O zu?6W}10Q-&{N$*CS@IHGY%(cJUSt30sLEE-wSRV0`viH5eHNG8 zwU+&DT!VaPntaqcqrP(FMXHZ2Gr9e{)|UBY4d7$6?atVq;uc2n;U$z1AJc5vnuXuC zWwU#ug>~<}Y(3A@`$<)C_zxCgob!4J3lm{8O%c^MEOYIVjdf@3s>W(^mb{uz_SPD! ziWB5-?J^yaUO?Cm?dp9P^ z?o^hZ83$)IEn7JIAPhj}=!Bp}AQ|+X z)fC4%f&oGC_A;O-z#d;^)SH$j<*2L#!GC^a>-A3Mi0G=t5ovB#Qfe@%rfk zZ?6I3{-zkrC^1>K``sLv;!=|ij~9nmbdmKE8r&MSa=2%A`ERdNm{GK(S~$MgknH&Y zN9#5q-{(O-Az9{g8r1KsdNWzdPEk70z$c&_ifc=NhKg7zQi7tJ;n84~_@13yInki( z?i8R5^v1;2)h5xO!n~w|%Vs4P9z83Zg^n85O7^vutFKe!1NKr!O-{?WNX<@_O>j`Y zI92}MUgpn=WoA~?npu&ZE~i*;sJADjm#7cZ<%9T*N!3$TX2_-;-*CW#bO<%bLMt^_ zSs6k%r%ft$K5LQQ2J5{KfLfg)|I6O08E9n>*Xw&7j}&g^FzN!if+ z^YXNe&Oa}q1Lxn7MzUQgu&Sqz_n~`?>cB~Wdi-z0GUUh zM`M!jOc5W$-gpwX@#E>$!nv*%W=h#Co&>_EDq2Jx%9OqGcRN@ifF>Ioq@s|s+lgSO z?Cim#W^Hd9V)T@PD6IILtd6LV0b)GCL$CSO8Hdh|iK1&_0SBa?s|Jqz9{YpO0 zlda28s&}B&bAeK5Iwp;r@>=fPR7P4vEB3SK_4kaI<-eQCcJ}gIv;Hp4HR~^)SbzIj zxklJ{GuK;x@k6h_Z{(WwcO}^TfGD#m1_VGEp?z+!w;->9$AEmfO!D-8cPUlYB z@#^Y)*)&Gtcqby4xXG3v9`kIX$D$nq+`G z%yt*XgBx4EUrcm2KwespL-;y?H$l|_-yU!xk4XR=5cjl7k4pu7 z`eCTV!wT$D>6a1*V<-S)C(TGT_t$;*(NmB2j*I_{Y)Y=K?UryJ@zQ%E&@Yo48;P^d19Nn` z5DR2o3%S(#ry5!yODdLUb3ciDM#F>MeIa@T;?>56_Mi9wqYQ@`;68`uEN4Y7XEfg^ zRIfnP0tOkuiEn~ip$)u{D?yh`g07Awsf7+yjbPAT)n117EP;J2Qfc_3{Z}cZgjow2 zbhI%wu%)a5)Vo{CCsGL<2*z2i9*W4Pkm?l5YWttrYEfNEo_f7dHfjG>E(8%hFx*Y~ z5F<^C7noJ6>)`J|QosB5?CkDSUeF@IxpU0RSrDCLUP61$u?vcfkbirT%$L!XJ=lWY z0!=ECvf{aJmsTQ`8ktWFJQg)BH!+%J2FJ{NzEY2%cPFsi~+tpQV;Hc_%uE|HkrN^hs98m98f5RVIiIp_JN>h8Wd z*@X@!Z!E9hZ!eaj%^4`9c?K|jH!g|SM-q5-n3ki{aOOOwi8{3=t+jf)Sf=(cw!U`*DlEdf zBGiDH9IOGa5ch&LsI$eg)J56!yUbGLR4Q3)U&@0)J>Pd)?0UF_5v3`{HD;zEPDC zlhNpcF<2~SLKud$S)d8}MB_gbgACwtQP3yFl!51|i5+Cor5>ywU|9XS-}i+<@TV}) z8mJr`h+9w{QA+=2h3dNwAl)6Ryrb-x>)m>X%d8Bmeq}#TGyYFUDPX`mrlV}dkXn%v z8R>)@kM!+d-c>K3J{6M+i6COBxyIvM#OG2!#fr9S>6HWhOgsU(ms^Uyu9@RFb=;~?>y2>km z^tp7{nORC8(40qYgmkjy?(s(S{)hx+#%qCB;$>5HuiY_uQkFMF|M4rEP zsGkjKaj|-(i!8*wRU1)(wOA!~l?w_PtsXfU9hg!H*bLTxiG*~>+GG#3#8Mi z9?6i0pt9b7f$SjnWdlPFPP+jFFr`@yW(&g&TqxhjS%nHwNDF0NQ>)d<3*}sj8+WJ4 z9Q8s^c`?Ne{qkc^d7-_@d%3)qyv^R2t(>ZiE^1dV*$TEjXL`vZJX&5P??%|6VHe3} z_GV9RS#ptlxalh0VKfKxiONd)@W7YcS*`l?mg5@+=9Av?E?7po7t2}J2Yg(j3NC?G z;R`k75_u0E+b)r#p{2UDk9@S{^PUP&Yc2FKIHFm&3lZ4lUx?D$r1qOWvM`LyIhR6I zUZuKU3bIbLw=wb9G?Q>2|I!cH2IRP|wzl|wgYE3EedAL3v~8_XH}{t}cdsl1 zy;hc`rysRoV`M_^tzWJUIW!M-)|3_k>kF3!lho(^We00bt@CSH9=6ujx>v|v3FzQs z*Ff=Bry5@?%NuPj)vK}|;LWwyTq}QPr+-uml1GS54*8n{Wv}i#y-eI(LCofX(?_M8 z;517yc`aub%x72^i(-=OQXQ_B17P|2(Dkycba!b{qO+qEr4tAHnGjkAw1c+j(4OKZ z|Ap~r89M>2=6Wn*J8>|Hw_Pgv2H64?wYfo7Wlh+|$WHLy zNEjq{;BD_9dBFubC$=}RrXQlo)JuSi73#Ggf-Fy~fE4!#Vr?S0i31pu(e{8FW&g}Q zI4C7f09+DmW1qfJZU)Bg`;Bae$JF0I&fBNf{YGxFKB=8{lN@Pf?F0VVu`q;@h_kx3 z<1O+|yWQ$ih-5n0H?}zkO5t!+L?(i3%5bLC3!*U0;}H|JcG0bJpbb@6_zro*I;@W0 zAuqFz)ONWOgl-+H{moraj)J$nceh-N$E4rMdDijT?!T9Rx8WzW@Q-pRZjC9u2b}1n zdh#A9=zmln-6Jo?TlT%OH1EgKBJ`Q@L=zCZ0hmjUq=OgUbT9bbN%c2mvA$9--iyIH zuD-cf&i&O%02;}OWID(2!z~96l6gQ>neZ2fpOBJ7zYYCD^qPDTKi|p_@~Pc_lKrjC zwUPglw^-p*rRg<;v7wkeSbkMPq|jhFtVhwUgx?>qjVCS@aDPF331bz;*?LU9Iz&F7 z<^e?K-`oBy+huFGA{ZTTEVt6MSMz7t1~^;sXPKP@tZaEuKH5B1&VWsC6co@MQGfUg zM)8EI`HQ^nLR6?d{Zb;CSEKnzmge@0KEQSbz>#7yt_AAS^l=&BPU#NHNpw!Af`_n& zI;L)T2)%^s*F$nZGt>p=vcb^TrA4wI)C-a8)ub9fEc1}P_+iQ7Wg|_7pkH@A zESu(>L{lIibWFo(IyUZMIoUc<+u{){wAblEFopMr#zTQWG?*00{wJ(dL$qY9%VZ)r zPeW*X{I4LCiR#_I%Hh_!+WwEq<(5@l^LGff)vEX3L59_8_}^tuJXV;;>A%ZM?8nPg zkN=VP$?7t+nAkq-K(NFE=fULfPF0y&@jtRh$H&XGOoNG(hUt{*JYEL8dUs?Y{utcv zkiQ?UnMB~~7LVartyL2nE>l-!WAAs%~wc{Vy zuzsVuRmf}c7+E0)GOc04 zQF4`Kovr;>mHfSxdA1a56RA@3+1h)?$Y*WqtV*qxE!x(Bdcb@EIs9yr)0;zbbfFw^ z&T0u6Y!EXVQ~s)InQYr5%hZh%?(GhH=RUMt4~$?hb3a6gb-`|ykQQ2fBj0ix``K2^3d zpHE?pMw-bonpyDqRP-~K@aX3wmib ZjJpdHy1k3ZGOwz;M$WDfVs8imR*IH+fC zjqGMVL5t823jiU|pK6cS$j)S31=HpAcswv&)^r+2c+ulb?PiHlc7TIKYK_APf{ZTo zElT6#@}hK9-ff1wG?VS}YrjM`So`=4&;t8k_mmvT=anqeH$p9lthWDL`A6Nt{CQYE##ODKC-Ik$ czh?Mrj=vW8E5Kh%{6+9rSi62+(G$1+U#x^iApigX delta 530722 zcmbrnc|cUx^*BE7y_sQFcmo47%$uEAnHkK|n51n!^`*_yG|kqgO`FfBiA_wS*-hGX z&9Dk62>8$e0l@`CMMXgYH^2pPV^I_oaYY0{LBw73d+vL0ge?92^DCV>cRBambI(2Z z+;h*pV@{-IU?<`;u*w=jm@wr*9J7_s5h4Lzb((lqs=_dgz(O#ALbzx+_(42A69X3U=X!9TbHmlO(Sy!XzhGrktMWn7x29*Z!c;x98koiQ8H z_NcgmFK2x9`a7S`p7}Ra=tblK>+`?<`Qw@I34BUE%U8$o)nCqdZ}yC@1XK9x_dfd+ zc=$^2h#1gke>q+7C?hsH{EwmPzd!NF)9-vfefF0#1W%yWY@X$4syvweXvRMTPaJM<^B>Hd@$vhDpP=Snef%Xm@Sh^eU-==& zcJvIJMY$O>-WU825;Ma5aQdh3fBE6`k7fvd7DSjmWBU7oX^2gP@b&DOAQC#5eSh}! znV$-tMXaIPFF*Y2SMPuJ^{0ZLhw~-xfedDTF!Mbm$X^IQFrrDl87KZ^`o|xC_MYHI zocx`cpU(Vh=Jbze{&R+)ih0qb$5NRYCX+vHiZtWbn1IIQosNiR#XiNZusva0HI14% zcsibfr{VMPxp=3rOZeP+tQuQ`W#IGiOnjSgi}3IN{M%pu_4OJ26rPNI^4aV!d+-lu zKED_H^ozCF3opO)+EM(C-~9Ud85?z%mT8ZP;n-40aYfhn>fIu?tuq z{9S~(fi@gng}(PHrdU7ol=oG%{7?qFGXHlBko!1M9N_!7Jr z_t)SboDlwHAHEkqh&SV{cpF|TJcpmhd+~mJ0G~%>5Sc_4v4{u|$wUf~N2C*ri9(`? zxP&hu<`Bh12{DK-C6*D(i7WUpK7!{H5Ab**kw_v+i85jczl<*+7$TRrfG;Gni5#L2 zzldMONAYX;7(So4?#FN7H}PBeZF~hWmna}s66M4yVl}abs36jWmx(394q_uwMWhRd ziDKbVqMDd1yh4-+j}e=Q&BQ!mCs9LW2uFye!Y-ng$QLdXo*?RoOyN~xxv-nqLSzd^ zi50>gVk?m&93x7Fr-*ID0^xO{On92uPV6B3jPM4rQh0`_Cvt^1iE`mt;u>*|Xdo5~ zZxO46=ZQvQCox~RizpHf5xa>E!Usg9@IJAJs1ha!Hwxp0O+=RPB(aycO{^C763xUu zVxjN?u}FA_SR?Es_7i!+yF`VspI9qAKpZ3v5e33~#5&;s(L%HmZA81DxI`2R$BFgA zLE+9`Y>Y!u!X4hWA5cMJCjn}mCX zr-jYJ^TJ-?)EmOLe*1?v|MFQ1 zBjPpU4snHet$3Yyy|_}mLA+61C9W245^omQh-<}l;w|EB;#1<&;xpp2;&bBj;$Ei3 zs+ln&9u=<+SsOAIay=xd|GC)BkXs?SSc>FM$lZ{8A>$$ULy{$PB&m`#_{@`JNHQf^ zl59zigpo9gawQ8TizG$S0ZE=@v7|_{L{cnSA*qyXkZhDxNvb8AB%7HRV=Vq=$v(+` z$stLLq*c-;X_p+99FZK5bV|A;mn0V?gOVZ1WyzT2y5xrBrsS67w&afFuB29SPckmK zFL@w|mnKLPrAg9cX^M1?G*y}=O_$D<&XXRLWJwoG3#5h8BIy$8D(Pxzg>nd;$zY# z>2c{^={{*`$cm7D>45Z-bWl1Zy(S%#UYFjG-jv>w-j?o`-I3mv-jj|??@J#@<7El5 zL|Kw7S(YN3BTJQ~$#$?xJH)JB5#$q$=l_JSxYQd|trP-H3=D01MFuSjEIfua!piWEx}#flQe za>WV-r&j)4tW;61SfyC4SfeXlZC!=*m!0dFZOp)y#*Ecl=9ry`h&v?}sLZrG%x0 z%@4~8%MQy4TM)*C<%TT`%L~g7TO3vpRv1 zgslx*7q&5MVN^y~AZ%IKqNvQU^YUK#1$m#mF|0|^5Oy)FBd$?&J?v&!0+~#vkaNgX zGL6iXq?2>WOg}lF%qAC;x#U8!ge)Vg$xURAa)EL)Swq&6b>tRuE7?Fck~_&=t4Va_-` z{#B~gsx_(#)!OgDbt?3?p8cy-ZBT7gRjH~~n^c=sHL5z*7S&eOHr0034pqIXLDi_* zsoJI5qiRy^RW+;jsrIW5s1B+Q!FP+QRn?|yR~=RzQFW+}s*b68m7S_C)p6AcRk!M- zU)7^Jr8=!TqdKd)pz4Fai>iLrfa;QJP&K5wth%BaR*k5xszz1UROeOKRX0?(RQFU5 zRPo_;aRcF(!Uw~L!f%BShmVGjg#XLD4PFj_~J1EQnwtaw8T-EQ$z3wZ_WVLbE;bl( zIpRvhNW|5M(TK5#I}vvy;?)W2M0F5{k<3vyiqh2S>N|eXJoN_M1JQi-Ty>Uutu8~I zt$)OOM7BtG=@zM%sk=qJ;RkhxbZxpV>h-( zwySrj_o^Gzjp}CgT=6z_y?Up5w|bX)pL)Ogfcl{Nkh(?Ps%}%as}HM>s5>CnqduiR zt@a0bx}d(O?pI$@4?g7bvU*rOqQ0iSp@uz5eOo=QW+bVRs8O)Z+{k&68Iie>izAms zW=fVvu81s+EQ{P4xh-;gk-H*yNA8JiifoSD7r8(3K;*&5Ly;|!t&we! z?U9EgJNy8SMjngojyxHe7?Ki|6p|d(5;89;BPug0J1Qq?K@<}eh{}t~k6Ij65LFme z6tyI(II1LSS=92VO;MYpjzx7wbw%BX%GAVb?nf0U5;cjMBu%m=MKec}s+r8}R`o{p z-Kcv}ccQX1jOMU>q2>VCW&a{gK$EA**OX{VHD#J5nqtjT&2r5O&2iC6&1%gW&00-` zW}T)|vtCmo+N9a6snMj0Yc)yYI?Wc%4o$tLL9;nl??l=BTDaGe>+xb4=5z>C&9iRO*gvPH0YR*6S{7&iOUxHNBb(nm)}%O}{2h zd`UB?8PZ(QjBDegZ)k36#x>V8!-pv}|fYZq$^wME)e$r9}{NwKyB>X&NoiI!=XYgcGX zwc{dxnRcbNT)RqJ6TL=Tpu z_GnLQ8NcMBwqHA-y`&w~4r$Ajm$g^4!`cz;Rqd$un)bT(miCVJuJ#^J>b^E18jWzF zHZeLWIypKeIyE{ydTw-D^t|Yd=*;Lj(etCTqO+rOqHl?EqZdXmiVj5QMdwE^j$ReL zI(kiXMRaBKM$yse&ghl8-slU_ebN4l(f!efHAB%O(WB9W(RZSIqLXzgx;eU3U79Xk zH&=HmI!BkQJFQ--Ual_JZPzTm9AR1Nmr-aqT8z5p=;9Z z)ivw(={862*B#b%=#J`+={f-(*PYOH>xOk>VPj!Ex>LH-x-+_Sy7PWrukNC*UpJt; zq#M)?=`QQ;>Be;rbn#RIl}IH~$y5q8hf1Z=sB~&BHIK@mGO77g7L`rqPzxxA%BAKS zGmKfrY$Ib_NaY$+jPb@qW0En`IN!L43Q&1eKDC%CpbDuXY6(?Ll~6gx1;(Y+GHN-s zf-0r_WzSHimIkIQJbk6s+Ovw))|)Sw@_QD zZPa#Z2USlsP>s}1Y8RDnEHD-tml%tUyQw^5z?f=GGwy-%CTcHyH&gqlg1ExCC2_@Z zMREJ71Jprkx!+i3EH|z;t}z~>RvJrwZV5gb(kuNTN-zSS{}C|u7f&C9iuv_ ztDLP^YNV)EVk5b&fhu^->q8K5BrvL|vvz<5tJ5idz#mM%|!> zsS#?Fx<=ikZc(?XJJen39yLzgryfwhiPtCS6ZJ{@WPOT$jy_eNrk|^yr_a!5>gVgT z^y&I+eU5&Cp3&#(7wH4~Jbk`?vA#fmCgOC&`G|87h590WvA#sVRKHBWT)#qJsxQ;8 zWL}=)^VjPeptMoHQ@=~UTfax&q~EJ=*6-8r*B{Uy)F09>q+0X=s#Tv)wdo6}c6||b zSYJ#X(J!Ss^vkKE`cmqcekIkZUqyB4*HFhHeFD~z>zBn{(O1L`>(|Gvi(4Bv zqQ9yi1(vP>OJl&&bztcRuyhkxx&hF)T1JhWUnUL#|<=VUZ!r z5HRE!@(qg(1%^UHkzt9U#IV$`%&^?B!cb}`GZY(E8p;i;466-m3>Ah-!v@1fLpf0e zT3Bt^WY}z|G1MCB3|kCa4f)hILov18P~)d|7)q&n;HSaRXxM4kP3%<5A-=W2dppc-(lx*lj#%EK@Gk_ZUwZPaDq|56I6N zH$|Q^o;UXTjTel4#*4;&Xz}g@ebtg8t)m$jrWZYjPa&1V}dEslw|6NN;ajK=9p4VX{L13T+@-Ld8Q0gCgkUv zvP{{g9Mb|51G!w&LenBsz?5goH~CXdi%kWlLQ|1xiK*CBVp?iiW?F7qVJbD1nO2(0 zO{+|+p=1r@D%Z8L2*?J(7wjz=|^ z8cjP*yG+%_-KIUJCevP1vuU4czv+PKpy`mQ(b!^YHMN=A{iegFBc=}1QPVL~r>V96l%D(+!`K@aciiDfpa*&l&ifh0i(ooQFoe056#P z%opL)5BUMeUjjG?a0uXKfL8zx0~|45H4jTh%^i|!=A)7^^L2Bs^oBV@I$xS8y=lH> zzHRpB%I=sCN!z4%&G*c8@^SNh^8<6d<)%5oGH6P)Bw6}Q$(9t$980Pt&2lX=-O?F( zJaVq3&^*tQX_;@yvSeGz%u6iAmOgWdWvOMECD+0LW|?KVWrd~GveHs+xe&g}vf8r7 zQejzZSqIqbE%}y0z}aBgXsPmBs-b9`WxHjErQXtLIbb}iTnY_P{W2b|fbvpnnRO)(%XzqphpPb()vw`U1(dI~uCuP^VI>bYSp8hZMnFUe zRXnT)giY4X)*2qx^01DFTL2H$Z{^`ODBo_~VXX()Xx(Yu1#q`@kF^QlUTd>;AHe-c@NWP__%{I}{96DK{%wGWp*sK(Lw5lJLw@T$ zNFa{J0V0m>14Ilx0EimI$8Zf20HP5j0z~*p01B4cz6`bJ7c<}44-@$ftkdjSvCU*KV1On*$KWFY2JOp9eOW*}xLrrmNm=1RI1&ysn-5=pQn+6t{nwiH{L zb&f67w$7SnOSf&d&b7_6mBeJ&GHt74=G(Gt8)LFul?7=dG2t4Yoe(Mq90IyKRT9!M4-3%eLEAZ`)&QvhB4s+xFQG z*bdoRY^}Eawl-V4?Xc~Lt;2TIcFfjk>#`k?AGe)=e7CK~cFK18E!!E}S=%|=d0VgT zg00VX(bjJpuwAkZ+JGrwydG-u@raj9(-=1yHVWvLwgnzNU!oJqN&c5DWY2RqyV6U=|+N$lF?3?X1 z_FDT^d!2n7lx(-}u-Drg?2Yza_MP_K_Qm!+_9pvYd$WC?{h`vv<&d!N1Ee#t&yAG8nIui3}!H~jWn_S^P5_Ph3b z_Hp}td!i%Bk>W^oq&dd5%m+mLuD-z`;0j9Sa>fjzx}uBhQiVSnMcp6gm#b ziyTWF#g3(pWsc>J6^>FznPa7++_B2B!Li!0#;aKmebZm4~IjS9-9Ge|Aj#@{Z zV~b;}W1C~U!@mQ*>KzS^M#oOaF2`=iK}VBgFEnd*>;qE!9S0nTplOSv)zRi?cN}&c zadbG2I*!4o)6wNP?l|c<;phR(Q;yS)ZpT?i>^VoT`0qK!9rqpa&ID(oGs&6kOmU_<)12wfxy}q{rgOeC z%UR{fcIG%2I5`}~nG2;0or|0S=K*=1GvB!w@&(R9K40Wq0{IeWG1M+|E_E(Y;tw>h^vcR1^v4bDdA zPUkM?Zs#6m-p?%lF6Rm7NoSAql=HOnjPsoHytCJN!P)1$=ydCfWIyy3j*yyd*@yyLvze1vbj^2V zxw2h3t_3c}mFrsQTI34AC(o7dTI?!t6}pOCOCE6*yGmS3UCUg{T`OFrt|~{FYo)8) zwaT@|wbr%Twa&HPRq5K`+UTlsRl7F1HZy;iX7}%R9dI3V9dfm}_PJVJZLW6LVONLi zsOy-k)79lV;yUT-ah-CVcAa&dbDekfx-PiRxcXcdUHz^B*Pv_2b=h^rHSD_N8gX59 zjk>P6uDfoyZn|!{Zo9@@cU<>e_gxQMiS8tKvOC2+#~trZb*H)0-E-aZ+!^jn_k6$m ziYv>V?PlD$?uG6}?mTzCd$GH~UFZ(D*SXibE8QF18{JjzweD*7W_OLd)?Ma4EYFnG zxwp8ty0^KvyNle*+>P#??p^NP?mg}%cY}McdmoFr-+jP+(0$0=;y&s==I(TNxsSU~ zxI5h4?jE-Ol>4;Xf5v^*ea?N}-Rr*K?sH#s_XByYRXE{$ClyF7M9Y$+4+?BmZg#_o-+ zk8OzE6}u<4DYiLwf9!$SgRzHVTVh*d+hW^ekHmJw9*sQ~+Zo#xdm^?w_GD~N?5Wt( zv1ek>#-3xoe)i}7Ii6HcnkU^e*E7$P;mP#O_hfmpJt>|X&jJtQ$@MJsEb;_A`JTm| z0#Bi*$g{*#>?!dq^(^iVQ$7;_SPlac#XPsxgr_!^*v(ZxxpG}_4 zo?7^>^VE2@c(!`BdA56YcM+vaWe9`+vbc6gh;N4>|qo!&0*aqmg*32(Qz$9u}#=gpLy_MY>e_x5@( zcrSX-di%Wt-b>yg?`7{5@37yyKH{o(Thyp`Pt-N<(Wo)+b?*)DP46vlvT=^_w)c+r zuJ@jI+sN(H_dm?o9?^so$H(D%kX9T=KHdIIlcux z#+U0`=v(9q`0{-DzQw)*U!kwaw*)@L@G0>v^)2%)_pR{xOMPX&mA-P{al@@@9j_-cK1zAe73zHPqkzIy1s!Pn^9;oIrk1#Im0 z?eR7F_WGKEoqfR0e%}G#LEj-?i?7w!=4TIH`8s`FzT>_VzLUNl-znc| zzpvYO#&_0t&UfC|>$~9V^Ii1y`v!dZ)FodrHRvm)hJ0(N%f2hVVc&@Fs&CY{I^vpd z%y-?##j$SqHbva@-SXY`-SOS^-Sdt6?)x723N-O?32}*WNpZ<>DRFb+QsdI%(&OgF zWyH;k%ZwYf&5z59%Z|&5TM*~Wjbq{#LXoUKt|6{5ZfD#AtS|0j+(=v{HX5hEtMsx% z^)uk*o8|eb*fr*@=Y5f5aaL?9E-+)yDFw8|EO6lxa*SF0{NJ!%M)iV*P?3p@=Y=O9 ztx?EB1ZDwQtB@-InlUp@7Lw1&j|nltA0wd2kE~*wfUHvB(@-sWfni>l8nsew5h-xF zIfNGQe{(Q$C3E@(Q$&}@DpF$504gz5Nj5X87azwqG0(kdmo8OX2)S8A4l!T8XrHl7 zK_5RXq3baE@(P07uArrKKTD9@p)g}~*O-wU7AY_};>s)}*Fxim#b%rqn1yuLCcO(6 zs00F?nSi013AW2^N(%R(77+}ZtY`N8@^!3_(Y*9?Y!~zP zOVN%Kp>ioJKJsFyymUf#QeJj+^44f_2ebGki#j9JA~6en0s&%I0v|-L^`$3FD`0pE zA<#F&b2PKWEHaBpwhSaDAoH1+U)k&hNCb!>1tF(T=9&;PS@y7y<+gzN_*XU$kC`ij zd7{f?EMh5#2xTBsg2tYQks``lf93h{1ZBYQUG!bb&;SQ))Lky(6O+>`d7_N@Vo+uDt3Qi8B89Fo8vDvh(8&uW^eGnMlUHA|W`>xt7m)sj08eN+ z%mQXm;q!9PW&wFv%4~Qg;vse7HH+e`fJ_WW^CYkcQ6&XSKAi5Yb0Jv^=repmZkCX( z%%5Jf>TiUy>Tn{I4w*$uK4F`};JaV~ZZL(f=``b^>=MF&1u`K7v5Hbj%%Rup-xmY^ zIugTAUuVVezpv{-4F7ojdF!|eN!Ki9)c_+iLM$Sboi$OyoPFKk>5!snM&p8&mlOgr zPYR*^7BMn90<)Bs%p#Xcp($W6?l-*pyGocSDHu7CIYiFN?wWv%50{%|Ft{(?pq{N# z&;m4*>>sOm0vIjWkf7cP$VOHX01HIBGnj@}IMRw;-<>}BlQ@i??dmp~Cf3{{@tRRN zZ+bCA3>89qTJRFPZvH>#LO@nBvwlOV!F+POAp#r+ZrD8}|A9>T|=P|*4pm6~rEqFl=lZw;iEE{kje+R?-pbs!zV5(sF zNapNJ3&`7I3x-cKv!)O2Oi+*0@fh6zDNrdoo_vY25@76!mng0!*%5+v4A5mzcJiLs ztRv*>aM+vB4udQb;0AkOuD%(e0*wL<07DIn-$Fo-OyuvLB#wkw82V-P!v*rs-^oEC zL3okbXkb=Oc*s5=^P0uf|4z^1p8B1}4?TyV?g{iIK|F}$zvRb=_?{nX!6sV(i-0vr z-1xXbavNcr0!%Ka*R7zA76FWl+kIOVayb$POf}h|kZ;8>A;0-zHfH$u94`&iwTcFA z+7`33@z`52A;*B`3&Lr^sbXfoiEJ&%5mqED zg`g5(Fe-9;m=zd*$r1vnteJ)!m}THoNZyZRYdGsGq{U#I%pu%h2@&Y!$RDS`)@Xh^ zsIgJZt8Yh&ma}}%eA@wa*>CH3ZI1jzZb2IhYgRce3XvCJFbJeZ-{t)|;WXMGCI|JB zutP9&LqL21Zjf;)Vh;lZF=7gG9OGhOoSE{!6ioiF{#WPQ9cGars%3~Fu$-JkVZs^kqF#YA@JjgAA|Q9MSZFxQ_!L2w$Y7-*2L$%M5NHQx zks#MHfBC;hksKGisdY`J&6Q$nDE)LF-6vFVJ>n!GrKk4YjGS+*b|6>uNNq}KnMP#Lnb+W*5VQB;9 zHkNtFll3wfAta!1JG6a)nf5Zj8M4Mq2uf4SJouAF)WE6;^^V@X8pivQ1>6T1r3|)R z`6dij9oHF16O9i|4v660chm%Q#bhlCs+&~}<3}q{2O@nqex~Ui_oJ;ccGtvEOGwK5 zgbx>+Ea}D5;6zmnblbQ9d(K0{ODpz9kHx>cfnWVw> zgciA!T_wz_cP;AbFc6-U7Sr(xI^7IfDkrxxr{0aiA4D=Y-hEWm&W_nT-4-4ng-itK z0?YJajEragJpFBUZW^Y0M8{afk?Ai7XG`*TWJRQ)TW|k4@wc6E7UU1Xkg-{G)hY?? zK(*#y0M}u=MKpfyO#V`z{fxPScv-hLyxoWg{1r`i>uK)lI z6aq{SY_QE@aC!tUD)EP=Ec<;E>qKsR{|S6Nf*E)}&fcQ1A|s=tD{G^9H47mNbU+FG zA~jmZA)w7aouMV`#i04%vx6jW)uL0t`!jZtd&D}`rrv{53}SiI{2>p%Nu?uMB)aA z1bu;2$;TC7Y0$Ch~c(-VAsf_VjU-95a4r4 zFTJktu_ zpRwNy*zZj+BP>GH`teb+f8!lTnEaI&Ao@luYRF*ui zg*7N&gmI@{Tr~~pb0{vrfKeC>*~?UX^v2JT`|%u{0}yLX(H4XZqr#r&i%u8c;j3~eF2pNkAML9Sz#er&?lGmRfdJX;sC1`Gs?KX1h-IK{O_ zTal_MLqQlKvJHe`##qa|o6&yiphu&~A0az{0dE4di@<^q(SBHgaC{@CK_i?Tje=z% zq^}RL>k1|l9n+u{STYM1c>PP2mI-{Sq^?GpG3ZLpasttb5y3}R3^|?TZl?ZI2R6#| zeQL54ieSW|sW=G~hlgO&KwL=i&_M$hAo(N4@tHFex)dXX2b?K*`!lnVZ5V?5XujdM zTjhUdR{#eSZT5erBWpy!j2z|xjmt*XFk_!Rixo3Zer|{5@qeFt;d}1q)|UbhUI-dL z_GEKS4Eqyr4WR&ycOR@|jNRbb*&0HV1Vc*-0n>pr74<=uF*iOp>)^C5Qm`xcJ~A%t z(P*=WfQtoZET5_kpylRS;Jn3i8~iBjDkNHwY%k38FYLOBA|wISb&v%lZCwahm-Qwv zn~h(*s14|>A}eSxYs!%LtzvRKg1pXHW=G=pBABOU>ow~j?8BNHuc$!s5m3>?afSW`#1a6LQz5g0$yH~XpZJ<(RQwt=z$ zSqGdfU>^B0=K1RpC?_T_D&*`|9~=NDLx@wz0JRUDYgn%isIU$LfgD{k&5{xTsr)a^ zzi*d<`a#ELs~GK(;48Aq0z&}a5Iwe%J6Mjg(}E-NXoEo;8E%F>mOW|1K-nz~$sYn= zlVT#Fh&(JFN3@S0M)m{9K&wuXDkoLG~>L zLm3*mWUTLvPosyJny;pWkHZP{IVBG4L*3`Vc`0-2tG_{z>a)K-gLN=#|LU9$C?Z&w zFi%LuMPzT36-)#QG~r52hunb+8zl6YfUY4 z=qm&X>p{d8ry*bEiN8(94l#kh&BhuT+1GZgh>($e4t=w5#|~8x3&-( zpFI@-H=zw-tlNTiN;!SJPKr>nMRdKGZV=OrV)9#U4~Ec*sPRF*aXoYPYm@Q2D9PBj zP>g>!;SUtd&;G6xL1$zm^QXUm8M#wr#X%r)*eAH{1Qcc$)A#o`|4Yql|M=a1soDBp zYaIXlZ4~MUrt-TTW&iW>|6ZkGM*gY)Z`Ia+%~PdBpcP4qRIwW%&0m@%s_XhyQ*RJI~bqdkPL^1OK)oK>mk&5A%yV}zcm_@)7o{nQ_Y zz#RW;SYuVEKM4zu4+rK@_q{5lG$h2#K2D&prA%7Flc>|i1WJRnaw@KdB?P_U^hF4y zwLt8Kxt8!gR?57U7>8vssfjUNEt|y^GwDeVERU&7I`}3m zkN6r1ZKIR#3eoCN(}{_XAhm(j0VV?CJC;z+swn95i-9>k?x!oZpoNRHj4o%B78&e# zGNkB$Lv}Fj$v?r$8D+|&NF38r-mrj}B;dYVC1|hG|56EOO_b8o-J2zJy95MNmGWnV zteNvOES~xOoJa6`VNCj*R~`cf5zhV)dU<>Z$6*2(JC;FY1gD`tb_Zn5fQ;;9q^U-A zSDX?8EI5sS0s16EUNu?B{3JDC?1+0#2~$l#4N_hS7ywR{(Gr^bAcZpU^o z8R^@jM_|{-VAqE^16hzDPd~UuA+SIFdG1TtRc7s6V+2?tu)|aFY79-kh<=K^%AA@T zg`{(1t_wtEnrDLVpUsOz%LB+fg;1j4^ns*y5=r)>c_tGuQE&(X^mGbm1l2GdoD7jp zRWPgOQT}ZhI#$BE$VK)R;1Glk;Hrf?Q$cfjlZY;d@Gh$+yO7GVTEgx*$h&7PAu_lK zw{8+{?{{!f0FP9Gt!D$)6&Oq)A)Mw9I{ZSQ%zJDh6k0z*_TU5YcFwOB(uo-Dht8W} ziv_dEXPBgnwTf0TT6bQwvfG(o&(~rt%$u3jSUZz3-@rKM>wXB~gZXwA;TKpn(>_1) z=iNBmsL&R)<&hgP9dO5n_5?oec7vdwgc(Csurq-sA>RS~V{mCfeTO*aK@+NKME?~@T5kqypWlnGPYQr=-r3)KRi$jW@0X*gPAC0(9n9A-@wow z%{6gBg7?)50l68YA)JFWk@+&$CSushD9kl0p#rWVz#ziv+R5z8jbR%N=UU)kqFQJV zPDXbmy?h&MIn?yS$#{KX9CRAL@Db>A!@}std44ZX!VLs1AUXz^Stkn#6xM_pojaPC z@r8ei+W`k2G*eK@``O5Qg>y@p8j5v*%XxL|O+Yh~zK9FRqMNR5i@2Mv{fo>|u;`E> zWzS|J_CAn~A`dSJ_;66XDFGw0+OGy8Ujy?4vpN<31uO!f4+~@#06LzauftZ^DuA7u zJsUv9N3;MI3`R@n)U7C-12y2XWh1jQQN&aS9z**ESQZcsLiC~C7GuesMfV_x5)+d5 znz9}IY!<@;rOb|eGn1V60V?I%v8D6#%#0>K#{VmHqA7>KO72Fu&DuekVgLE1TRoPHy)SJQ#j7h=%FF`5!VOHX*ugyf{sjuUgdJ8E`Q1o@Ef`K zUB%>4FbRvF1^;&U;;GoLn7fPrjeX4gy`T_V#Y7g)z+PegRTzhT!mKX*1(XaI{tbJT z`JiYzq??PriAF{d#`7*suos~&{CzGse~fu&$qX4N<`iCITb4LIpj&8m9^iCdGBOIt z0@1ZFkD${vLfVgPn_P@0^RJA#*o~S$$~XT_@s!EgjN(|7b@8g`+{1E3!#xqp83=}*NT#)PiXTFeLU0i|DWOs1z`A24 zhmNV>n!_CuXmXI=njkr@_n^-O?8A{21!XX#aT8O)+$;N;eoZ)Xra9w7kjuc2e<5hm zjx)bs*$MxA&XjWWkLbM2e$On||0Fd8-GS}~!-EdP$aBdPLf8OU1EauIHczT7oK(p) zuvB{3?>prVEQhhIdIY}TSoMS-1X<2N9F^X*o!bDA^@L%=p%l2T4o9{01J2C3@GdSN z&lwXJUK@@sN&s<%WHgNcK|L|ZV?5e^qEjAZp}>AS3qLf{CKwE8)$FYx(Q z+|FEC9p;B1Gq5uiKLgG(^phtdcd;wBP(fa~4Pte0`GsuW4+yben_u5}09_P#4ZTz4j>xpUhtSRNS{_ z_*>FF5NyUv_aNk%uC-2V(}Hz6EWV0b_d13Y7|s8D6J1b*whl!c@_`TI>?V&^DXL|H zwctsn@CIUsLNSFk#bgVn^uQ{DXjUA%u2GAkNi8P%o<4T^#*`@ht5CS*W;MYdOi-hh zd?V(;dXN7I4n`OmD>U8}B+Ey=iPcip?2=#}AaM7b(hdA}#z8poBRc+@Uvq{LQQJF- zh5F#CRKS@61>X_EeiNA!j&9dikY<6>9USZdw%rtNhYA`b7_H~w;94h9Zr~|1U6p<~ z!M(M?{53Ym=?rx=QmD3C{30Q&?O82|H16zK&ds0fVwTNuZ9t9-vgIV3C)FFaEKBUHy1 zu;E`+3T2zWjRfxT_mD7hGqa-Vc^J)L)niyLW30Bub>gU zV3HjQbOk*dV_FLgRo$DOf2d)}R(fv%t6{bu(6IDi?YA||iN=kQt(elqX_({TBrua& zOwuqrD}pZI2x*u@Ze~hqMCkO+Y9-%>sjl&0N0>7;7UaUCnyN|E_V80$rLTP%^CJfp zDO@rLoKrZ>e^I!LlW1JvX($wWGz3oJE?IenONJRyYJv(k7;LAA23-2-sdzj)W94WQ znmP@vAL4M-%2_|2*0mt5azslXq%{^Cp<&A#pgo^&@j{%lXv-7WMdrwsX~@@~v(+TI z!ah$E%9&TUMKZ5%eVi}Ht}ta=pFo+OttQBfZ;eG6_coOI$X{-wryLiY%~f^O7MU33M1TZfAMM*91b{*gz1>9kw{5V(Ij=`F@oy&5CiGr z7u%yi7t^-8Ko>V`*FV%nj>IHgWD8hbWJb0>{WOG-Vb3qekPi`*&-R$07Q(GPus~jk zQYwO#JNZiH&pUh&xXIq3@uRg2`aXFr^I5c(`Q+S5!z42%GB}wDDg*Z?fH^rL2c8w= zCz%%Tb<~sM*+3WM23LhH#!>$mmQ(eI`X6JD3`bZ6;0$5($bAjH61_{J4})fs56deL zYiWF0%{1Jfqy>`(v?l%%ApugC$k<>{np$*m&9@4-Jgh)^`VYAejxmgBVH88Qq!OFrRtCOVuGBCaS|THhBH0KE9BNUh0;kQO z6-7*U%RfEv!a_W0gB^6yo$E2Qv%$Md7$f-m!CRCXCcZTlOJpQ%&qTpfOsGdg7DIp$ z8J|QqJk)!;?G->+-S&S|A^y(A(k9-+!Wh^HL~&?wQKq z7IuF43Va%D;S+myqnqB5jT~u?x!@A1VV1O0e&{uLd_z3#3*rK^;L3jI@B$Nq1-YA` ze<;}I2l3GfMM$!5ZYP?w2b=uWse!>r%Kl|niIDj<` zssK|dLHxi?6Z;|*UAKX-;9@)Y0vU7$kiN(7n43{>wIP(L^< zUBvR`{I?`@5>8)AU?YlRIxulAN16rFg_p;$v|zb>e+1}K7%pJK*cu>>b3rD7d^tQd z4{X>XRWs|4$WZJW&01+J-I{}fgq&z5X1MD}%$qR7;)hRN#bDWkPhG`GBIqR!`&5-b zKO%DLg5)NY;2}v^-*6F)1sWF=2G;$a{^j;l?v=rPh{?W*}c z;fG6wCyZ^PI%^(=fJ>6!-N z5B2fHkfkKL;uxwbI{pm?Hd5bhdTJP69H2$WPPz~sAlR%B>41bD#L+ziTf#mEQm~g? z2{;@TzU+2PLEeuG_p*gcppeb#*=%tzJB>URjj(u9tBQ8t671kfqMWCbXZ9+ z(|R8H6?6xC7r!=8C&zJmEu7)eg$c|U{GnSl=mZDj1>Nvh_vo$<#0OB*_o0X({d zC_R)(_%deExks)0XCwX-YO!BTpKIXNA{e9O zq$H%ls-W9|X6Cc=(f(pJ>$idI6fWEa)=Y^bhM>L@p2$EBln4T9ebq_ zF?l6{o-HG?Xwh5;e;igGv=QRdP$_vUig~5?5&S0HFZDk4Bj6HNrWnbR9D;R^-UqU) zC<^>9c(uch27YvfrsD=WT%h(ty^o^yrVGEYf4BWB&_39PGpA@prd=>T5n$c&2`a!9 zI~*W+8&1~XdYWp|32o zC?{+@>^lPZK?$M;dm|WwNiB~}YAKwo*373DA498R*+r|f3avCW8R#H}c6RXe-?gwO zCA2j9c^b^%MPD?scYK#5tRsRPj^bzf(f&uk6DBXf8KFVI-o+E-NU-uB{dO^D!I>5P zu3z)QOO#HG3%GlDhYftNyAfK_NJjkL1g}QHs=}{jShE@en+@_e5}D|MUtq_WKMh1j zUyp!|9U{VQ@e0l*7r^_~8&Jq(4@BvLwOtdn*&$qY!=&mn6V+n^u6krrb^k3?1}0IzM33- zLd}jFEowMdfR1qO{xEnPo*GsCXy`76kB2djUeTzAqO9P_praA|N(o+nk1~I_@~Ec} zIYTh%Vl*4@z=GX+L}U>L4->&;v7Qei4guw=O;?0i)FF&J6QD{IWM=!^$+Ql;Oyh7| zfXxRs1lSp1rNyEb36Nu-8l4oJ(MuQOssj-7N$5dQ(OUGcVMMFBroVxlUT;{znYIp{I z=m+mR?`qI8pGMc-(*OcDdN{PBp8*Q!%6ok6t?$+{OGgbrr=I=pVZZN=TA{>v%?IBv zU$Z^c5l3+cPhO-z6yg-@(nO+2kFD z$zb&P3uLsEG7fBz*SoIhjot__3>F+FxAFhz*zBBAFHcD|7}#LjD!nPApmwFLY^O!UkcyhcSj3e2%;4hn$+ zA>gE-U%zrg??=?p4@b{Mp~b-!?u5cXqY!%wF4Ai#5oW=#_?l&g|ZKe zT7)34_rtKKgRg0h3+Xj$*x1D6&=~8>3&`rg&M?e@4Fv|mu%FWC3iet5lXwT*ouRHu z*i#8Sy<|OXxTS)F2GPu3tkuHrm%xKV<{{0phxc&tfk#xB!ITWaI~XGLoCiM{D21y72#}HeVF;?pV}XxVm=*<%+0zyG z2o-zy2$j3Ok|3=DbOjGd9(KW1B#?%CLyUX=1(wTnqa*kDhg}LTc`?>OM53Dp)(gC@g8@?n zro#>fehffAq(Db5Kx||ESwMi78ywwsDAq-$Fe31Bez4OYx@~%>62-=@syc##!L5u==_usK0!v zge`4iOW|M*Z;g(BLMRh0`14!^n;t@Gx!lUSHbzuimxjgWs4r{>925#vv zFCy)rFcW$l4kr^e8^DHhA38K$hQ|g0_yzCq3lL@kuR&rCnFzAvVsr(=4{hQshx`h3 za<+ybZyFACkRe%Ou;=6k7U;2O!2t^#(__Cu&||>fz;XlM7_c?~uNtvBO>X#9T#KA{ zs11EVz! zRxBKY;v+HG69A+(EHC(8vhs0yPa_}EXyF~rz&0E9<}={qAdxqK_mRo_0>P*&ggiv- z9=EkY^df# z2X=$Nt_I$Th5s_u)xaM;*o(+_1PLhEHyA9RrN}FQt7JRY8tCz0mN(E({n*|45paIc z%R{mY-GrePM{<4!46R9kLar=Y1G^vZ4w<~M?`F$_-b~;%FZdHfY*{aIIT5w~U}7kk zI1^0ta{o|964x#3k4^T&TsZH4?SYlT4rffsI~}}#)EumUHy21K2O#ffF>k5(1=qnF zq9Eo;F5eQ&_j38xV1A6tw*~Y6kGJ;#v!Yn~#R1bXJ@9my1Ki%y1J^m`nrDJYo1T(=Y8gRwJo;jK0?RYVrxXM>aZ++Hd&le$9m8Q z3v{9HOrdr9`Fry`E?JR(&^&*tpAVVmqpcuew zl%Jv^0kinlBv)748TxaSRyKb&;1l)pN%OoeNe#JdU5`Dfv_-WOwZzARVnj`niE6HC zlFyq$*OQ&`uVe3pnT$kL9fHepAG7EYQA0Fv1JyRg-_|*oULWEGS}TnCHlphx2vyNM zh&=gQZB~k{l})o*J_)yPv)Q9@*fo&DNnBqkcQ#^`Yi0Pbsipruu_sJQxBFo7a)BMpgy8(;36_b++!2*5dklMgD3U`B( ztior2yr&d#Sf)sbVvu0OuuscZ8n8QXXhN=Qz*5^@PfFJ=Fbc;kCr)6`B~H*;cQT+6 zjte-CR1%X8$d^;y+i zvf+`lAw81kw9VtT>Pm0l;qE|1-x*FZ(hgH zScObmk>dwKB2u9^s{W<|3R3f{*+46Z33T|7nhpCLZJ^P^vVDD4IZnM%1?7cCtZJ4D zAt#lWh*rW_cdf3Dxz))wjajv_NDYBBJP_DOzJmV2kwEI-967yKWcmM#taYR@Ytr~i zQUx)JD?ak4b7v4hr(q*3^XNS?JP|=2CTjOTGZ85ireigC7h)nguL*O{6zoqH?;KWB zBvp*Zuu`q^mnN)%S>TR&09*r5uxV2^m9eREe>0Y8A=XQ-&-0ZU+kJWcV4LQw1#h%U zoAq>94%z_wIt0m1>}1dR;CIyTcRC>~JIVU6O3rV=%3}%K)Ph}m7%mLb6#O;FO1Cba zRy(Lt_N7wjnaNRNN|cyNCnAyDW<5Sy{<4rEvnFAd=sY4UP}&1@%>x9S2y1@S{yrY^4tgWT1SxJ{JJ)s` z!kJg!!HJGRbcGq1Aj0;rUTDuG9Zy8R)3fU6a6lm4EzjJ_%I3nK2>lX*PpgoGeo#ZP z^eZQTgO2rhlp|sJRLKrnaBql!->bj;z;J~%3+)hkh|Nm za%{Ui*_L&v1V*9p5ad!)tdbukrbmex#3~Yb>L$ivBM!-dAo%t2R6CXd+e}h>R@)r% z`K+bbl128;RPl~87j$2uX%`bnmpdUJZ_lct*s%7jk}0;irC3-}v2QUKso1XeEW508 zgim#qt!>qy^o&fsja4tTR0X`Drg-r~gn-ntSIgYnSh)%(^poei^G1EI;w?xAVO7B; zx3SEYlT%2Y#_5A^kRU|;O+y6VprD9%OCN(`;=xPEkyT;S<++ZmLD~$guzFq{$F>HXQNVQ63fRg%CUZKmn(RZ_w-c8C zDRN>bmXDuXZpW@`zx?m*YzsRe+ugx#%cVBKG(C4vd9m>_g3weW@nSJvteZ`2>_Du* zi`}1lUaZ55As>2PY{iS=gFG+x;Kf=mj=>H%mZNV6mG`Gd2pQd(m4Q4s?q{?nLd$g%<)Lpa^b8JVK%q z*-VH=M~7-17Mc(6k#a1Ipj968izL|lm12fOTAs2KODG$QP84%cF4_JzKul+-p5iGm z)TvCaypy${-5gOf&jT>T03>#0*-agQCB*<>`hr|c3Mh90t1lWzsDjb<&1Csg)Pw(ni!acyBhAShN)Bm~37D<~wiTi*S=!y%V(7!^$N{Lf z;mB!%E&HDCtScKLtK6jx!;$m}OisEIMHXWJkAwr*zatn3VgmnN!aw#9Y|5c``T~9= zJcP)GY(P&1{Lf}!J@CVGVg_iPq0sa0EB76&)e*?K5+ETaQgIH-=>{~x@2JW||DPV- z9N_Y%Ix>#(I30~2!g%se*v>YuKfqQ3t}hv z9<>MoYv0(M4B&&%+ApWx1AmahQ{?RqdB?r1a#_9fHm9Xm#oy$LR-!dli?{A&wYq{c z$kuZ$N*rG2STS7QA&=zAB-L;3Nh<=r=>ZNc+5{{w-^;4{;6ndZR_?*d#*Amw(|F&i z2WUSo@9n{A;6%@xJyoNH4(gyD!P`RG@;f3Hlos8llr3 zj&vCD-B_EHFlw3o9=6LCdaUOCEDb%@>3-IbN%`OVS??5xU}t}2LS^Ld_p_=!=fo?k zJE^dil9@m-ARhcdas@w@&`sZp6n!|ejn12dp~W}SfHuTasxH!Sd5#hSjs;%8A@*c%M?TdQs}~emfV9_4U`ip<5#emy4QHb{ne7$< zdNsZtWeRjatoiKUWyPMX!aW<2KFLw~sOZ~V40Iyl#Ji!ww1!dsJfyVZ_9PQVNd>?` zZh=3bFe`3)yC-YP*2#4}Sz|U=Uh2vE`c;M$>VA8j?E5flS@B>ZrJvE%z{E<@KJ_^= z!rPnWx`$!OM#qBC&?mK6*q_K#53>gBj7;mrs+tvQtcQpKhicig7poNrqxBsohSkt( zflKOapkbTk_Fk+JCgF`osbx!;*jIGvNBdv{r2aP=fH`kTWrChEAq^l@2AsiN zRRA%sU9@F_imoMw}+ft+kb-r<9fZ~6b%Mo#dQDb&a&s%QSDm6JVX-AOM`_hx}R&`2+3@Fu#H zxs6oH6u1HhLBPf~T1%@6IK?^0tJ^B?@5Ab_Pvqb}kgT7`kNdDHlmmpi?vy3Q-c-{` z#{;@@U7qU0DrR{i^9cHUA!xU?GW$_h21j+nM(__8+AGj#>cFl+TQvMLD$?GqZ0-;? zo@w`&r|1YXqH5b}GS2DAM=4;ZQ657Qx#fw(fj-k9FE1Q=lvOZ%e3^-v#Yqs-7qDD{ zA(aS;3lkyNLxrHc8W}=u(Y+6WZvW1dGl6Ufrx>g$SQ7xK|FeVfjcB$jX-G;dc!G{k zVA)U*Dl12%Mf+H9*T{K~v9uOwVFIEa#diQz217uTN<_7r@~fd!_==Wik7E*0d0KHo z6Y80_qp1ux^5^d_BD=G=5al@L0 z72(6jSr4``cqZyGFkdrwvmntMWKs}J1(T(yMY|~6p@U5j;&$y z28}B?`ujKM|sn zC(Aw0USe0|@aNUjr_Zx4c*$O1O>aRoI+F21oMYX0^iQ+{?EBuAPK32$6DR^$QpC}B zkRFrz3&x-qUjRjU^1T;WwVGqpxlOPq9sM57ILqa4ag&s6Zj^X?SPc2%V9Y-B0?S0J z_=~JXsTrg)kir?E0FatGPj-6|Jv>VedJ&SHXoH7~(&^J5Gb4>#CW{}9R7H+Nk>ZPQ zu(4AeN6j;QoAmsHc^;>qe>Bg7G)*VW^A-L5qm9 zcHt$gisNLpm$96UmbbsmYSka7`fwQb9SL-fiMSX|K*2A}QFe=%UX28!0r>D`Ha<;5 ziFYk=U{WY|!gw`5?4a!N3iNje2EyD6^Ra66siSOUEG7kbLN%@L=07|=jN#K3=H+doyV|2JG8OXT{0!NmFh_^FAL z>kBqiG?X?|)IPue2g_mS;u$*kRmIRVUu7*CzQ_LWW&n%?2AloR06J|)` z01e8d*H~w=toD43iQBJ3froUyh8agIBWX}uu^R<@uS-ON#&jna2le%VaD-+jtn8XV zcj7g+6HzIjyw1iF_D=tSoheU_`wwfMe2tD?kbDQ)a#)RjmB0Ll%_gYP|7B%UPbF0( zi*iKWh?5Vf5@5wHsR9zkd`@vXm1dxKS| z6$XOD)sX7wU3-8!)JIKKs|3A~kG{cj5DTx8_G;py?ce0mH`opk7tDQ=#cCed#6#IG2C~hizcnH78N%&Af25 zW{-(fGMc)8JU@tu+Cw>Q#+XiB6yaYUg~V1>kpYiAn~|7_j73DUFC7tb_8an%!7Nj( zjt?XtK#{7bBTlc0EuL^lFoLM(Kw$7qy9(sS!K_vaHVfpP3?$nd6M#P1-ZEhbwpx=D zzz6$-m4>iC**{j3d&^XY6|VT&(ATQ2{RY5W8CIYqdlQh-h}iZf*>@OgSn6@A84>ql z9sRR20;x3RqG7BR8!Ar?WB*`}%bd4aX$a^ZZ-Y06$~WJ};x|-of1A~#{X4a6ttA#M zbu0%%1_ZQ}m8tKrsuVbJ%R6igYdn;rR+7n70LN6^0%y1*m9Kq(RjaIyB36OiFjOja=TjFMeQ{|B(aoDR5?(?VISvGZnBwHUP6X9G7} z6Ql%ck z+da)dW~gGC;ndU;9xa7*;?=*QcJkk$FvQ}dobCrGHl`20|B?}@s(&$O6r~>0W&&@n z?qY3$591zeKC|M1Ro5~4Z~2W5Qa7XBZkqM z1QiXd#8Nm38~zRfS%PHRXjUm5(uFXnLuHF`NT6?1MDt&Y}q@UtR@$`Y-f6W{v!suG9;!=&pyhV}eMi4$Ww z_;-vG6dvLRSF-DQfxD@wYQ0&Z%4d;}B55pa&1N);%((f*B39FYOuB?+p@SBpK-aSf zXfe9vkECEQXa4ONu9=#q9Py%e{^cU4Zs7^IqU4$bye0D~MmQZlQ?iJ^J*>E{Bpj;n zhzUv}V3Npc<5)(P>KwNfJ54KSe+^Yx6O<-lo5<_*)DG6N0OIFe4ZA|kzh2+HIl1j#dT1BQKs*IedUkJgn zze7N?yiU5DEsG+J!fbM6+Lw^dA z&3m7fR>5raQMJuhV>wtIbcH(aMEj(ww?jj3Qx!?-?c1TZ*ey`Hb>fEMp|_RjZ9u&p z5qcY-x0!ewh0rm)4px}N>c_Cdvhxh=><-H(X0TThlv^2Wy70X?c!5sLU~ged{KSWB zB#!D_{E!`@aMgXY*sQWTR|u`I7>fuRG4!W>SdN^{`jP5QBthp|WK<%P?Nr&()j4zY zoX%~%1D7%-XHYf}*ggeUMZZw*D7|KdfM4h(WVd>kNbh(KHa3)bgTDfUH;%9Hj5u+V z^~;DCRXQigx8|^iY4xEUqe6EC>49;c$~+#$qr5hUb-xF1V->`C1)&A`1^OFfjgv@` zq^$4n zzc`PLrgTe$WLknoBJGc`i7JqFKY~*(Pv(BaGVn74KQ%#OBxP@IPTYhxXYt1P%Zcb1&oZd>R7O>@D}h>%$M))RHy&lj`7Dc!|Ab&U%o;yCfm9zj77h8% zeD*kLy=UjM&OX}wj7>gYNj|iI#V1XP!JfuaMYqwJu759JJqUEy0`^+UHr!kZm*9b2 zu@vSH%jgt&|HrI9+I`?-G;xaj<6~Ae3E9ZFUgp=wkpsBqLPTkNBl|3bt>GK_uZ64z zAg3-w1P7ud7qZ)ce!?R4BRei)|1M22A&Tp8#DLBS_;A*2vfQ_b-SU|FR6z_IT^>&3 zQ-@+z{0LZUlzOr0Yxl)O^Pah=I0{QSt_7s~M+_aKyVCl}7-AomeuBZ(X798|*wm8q#t|utxtzviP z&c!lCx&l(0!ovwcrm{<^?Izq$h)K~Fa2o!xr5#jZXneS0hH@CIS(BmBn7z*A5=!Xk z3Q~OJzgDwH46SnvxFW+#Yw*%)m>Ue-q7va`Mgd~_=gEH+u%2E2K%RCafLD^(S&bn8 z=rpJ$a-Pkkeug~^nTb4YTwetBHPc;H*0QS4IK8^95LlMmtJEFTdlwVxMx+Rfj&Ymb z*!?$!Opk!3Xj1)V%lT{BgQmgAiX1kGq3UGyb*xP>-%+($=$WJ)Ek3Ed(8+NM}o>@Y18mG@=fXn+|xV zP$3U5w*DN4Fb>Pa4Xi?nyJSQli3HX&dzNgmfd%4eJdsZeFK(2QPi|mUQ|BvP3G8?l zW^=@Rz)X86ICTS9fK8TXH{$ToEa~6Gek$c8s6y{5rQvKORv>FLPL$>adu?WK)8sn2 zg*}I#d$+Rxk__Irl|8{`1)Fb!gu$Hay`9ZWS;RB^ddEmdxp@veyJdE;Vad=()b(?? zW0P(guoufkJ6Pq^Gg!kYpb@D!PQ_Df3V!9$9c)OQn+fC;@k1QpGdd~CJla1b`4RF$ zC&Od0H3-DpW968g@UCLhNEW;R1-Ky5y7L5?MpuN!LUtYmR2IZ9xqjkt_&gf1{(S@F zuDfK&&Tc z|AMSlr0Xhl`McO8ij!}(8xDydEjCS7oEBR~*51SXSRR_~VO66>f`{xQviBa=sp8Qje_!YL ze1tZrPIayS0 zSgL(@MKlq+x=b{v)PB~f$&?uCD`+g#YavI zef-4YOoomK)~YOgkvfAb0pMf@0c0uwJt^|>HQ4VEi)3s|@YTa?EK6L)E7G`9SY0&2 zRLdP@jggj-{>F;Q8jBAZHziBXE-3bsWK@1@iQ9R$mE5TIg`=Whq(fC)UND3ImH?`mQHp6sO7|Kd}d5 zE|dolv-irMe_{*RU^(stn}eTLKeH#gB07c4)|jCxyp5&94x-5=MViCfi1ni(CXZQF zBT<bOV!%5h5 zkIC#`SV780T#QCF(X7IGBA@-ey!i|K%<8AKeEnDUczGy1)c;5ho(|5oiEK(gph2t2 ziodZ(k?AS}H-#bc4PQ9Ey!#vLT!spIz!NF4Qo1Gk{fco!sd$-uioH>D6{6fB>!5~N zX4T5e6ibx`1@_G{S0@JL@>A>)j7pi`SJW&C4wmh0IBS4!mbBdU<;j+|IdF^4F(+#$D|U6`t1;2hWpt*(2X zwL`1>oo6+TGIPo;W=gpl>8kVWAMM=xsi91QiIHXX@P%BUIT$6$%^jx5Qjzx3K-e zwinrKR(DeZ>Q=X=Lgz-tAXE0RE?cN*>C3EDjSbw5+93kZMTkW=>BlqQ*o@Z+$XotkHEA*a_)uzkaPS{&0;3!iRQbV@P|!u?n`F*amfAU=0Y9?( z(V*h5PhpWyB8j(yRWgAQa2#V)il|3xBT`R%dR#sQAc%CmCL!ar;=+DDjK5L;b7VsNf9?D z@hLTTc6h1pq10hja0(%}6jIY*V#4X6(jK9I$q@^DF=Q~rzBV;O{`?oKCZ=Nd@BJ#g z$A*jtl`8x^yuu-Q0jDi@-e4`B*rO&eHm#KN0!!U~^9)xi=r{_e;@9zx<&Us!562d2 zG}aJxTQ^Y(b_2fNbcV1Jy_ctMuxht#G#f?2^G!*3oW^1ZM~sQ3RntZw^i9^VPQIE* zv?mQonV(9)BIn*@cNSaXZQLT|8CaM8@#*{}mOp+RuUidnRqXW9_<-UQF5v#zN-G+T zfO8W~{`eETd1i48UPPX>WxOl_xsIi+Zo>m?r zAt|hn5w`N%A&7GMm&NO*7f-s9>0;ccvGyq0*~bHfg{$40ng4mnsHU(C^6{Qzl_Ms= ztwZ|d86Pf*bzaUhFWW`(TPtEqM*24e;2Tt*7=Wpfyng9o36eiV@&>sW9*IGT4rU(bx@`%>~*dK}O3&RjVxhS$1l5A7^5y=mmJO$)AsQs|7+@scBRbX3dkSj#8}>?!YQca2oJK)#K2&Sk;Q6H@wy0U zl{aE}8tvCEzNf;9zci-k8Q=hPa)E3X$Lqn&_+%XKR~fqu2<{kl$(V-pVzN1Qm0;iXw5U$A*L&aX|g zd52O8A1>l`P=G@Y$SXE)VNeXPyePhKmOt4onNK4LK%KSD0g-QJYd+p=;>wmBK5VN~eTX`aWi|qI+TruX(FeDdN;zPRkNs zaz{YmaFrRuZ61hhfna5YWI`!kx%2^E5kz36C;OI5LBmq~UUbW_QvBn%Zj{#$#xu;t zTT^*CyuLq`4>xQ+#TTmF%jS}o=8H++P~GAIiW8TN;sCAQP>99ZZ9|+Js-*L6E6(1k zoT^~)b@~sx07tvfyQJiAHDNwf*SB6wp~_^-GQ2Wn9wit!0VfnUH(|pjn~S5zyZc@lE^6(>#`tzU9|_6eNHGwxWKh@<=B_9C=Y}V)mEr$n-^r)T^3nLYRu&>G zPc|-xtifyL`f~hM{IK#o9a?AA^1MX_gmj@E3~Ui;Az#J!a^c?_irLN>%WdU(_3*FI z8&{gI(G~b3sQ#%6n5cPjX$5`{^5#WX?tg{Dj~>_v4UV$2CvA_g!WZ=SMuMYP#{&D`bk;4A}Cf35-(87Vb!Ho`E6Al z)NA~O!9rs#D`fIP4mvRir#W4G-gvTLK_-8iwRG?y9Com0ARQMXRMK2+n*+bhLV5%c0o6o331_!Kyu<5LZ>mYc_j#y-__w2%}XS+(=V;M z*dhjH^}763gY_m9Ox-o{;p@-py8HuVJ(mw<^Mxd32>Wj6mKr!(#EDskfvVT%yUFfP zAb*8{7y;@d(SakQfrPpdFg3!FL^6D0Da=ip+Efrx+aBDw-danr&JB1C$dZ>EV2S!p zo^QY#fk&%1RN}ZtL;jL!2QoLi#lr*)lJM3t^nc?4iW3)29rYT8Kz<1Wai#aa8u9rc zBfYT}xr*TEP>pfI6}b;J=6AJs+BC-l5-xHBiHL|XO<(8%3&~x1wlVL5I$Jm4_tZE5 zaTaCZakx*n75qypbguZ&48@8jybsD$fDzL)`kH|TWSnq2_K~Li76Y;jfvKT!HG)?z zYsw39ou;e}1990|*?*!TWifSdvQ`OMG7j4-@J5qIw;ZR6lQf)1&jm)Sk# zu~xiHEhssmOEhW72<9W$y2I5Y`x=+&t$FnvoDIM>5;i7fK2y-kRg7CvEVyRUiv_9@ zVHw(*cguPh1d+!>m0z!8ZTKzOYInn) zg@FO7^-LQkd!RAJhN#+=PAT7fjhCxVHW*~Sq`au6$ZSuMh9X~8aIy_=>z{&XdMt>T z1M6gyTd~Bgm-pR@{o@At=B?OFz{h_p?_|h4@4-?A35|%%RyjCo2?s^*927n$2juW( z20V{@TQ!EjgJ0(G=h)p&vvKRN+w4?a>IeXb2WY6E_qbov#BUM!Dv&DzMh@6xz~(Al zHUUZsJsjPZPq_t`BtoJ=4`{-D>{#i${nQw5lCL8=O>THRE4;lMz&5EBO1+23pB3z&9N3(s9U*E=?MC+Z+J2IsM-_QTz z@?;0zr}S)c2oaH+7Q#6bDNgK%)lT;6$jhWobK>Rb%T)7aSVx{_kG7OzrWXKom|WhG zr|142p8;vHpj05igR+{KJ}q=cKr^#=OBMGTn&1vvfxKOwf_1|RIs=1(+!!oi1U?&0 zS-)1JAf^xTC6U_LDF{7fq?RlN^2JWPDLX0WbmG+#aU%=t%Rm!Hm+8m@op_xtDEbvr zhWv!9&`+r#jVLkJDN7@eNSkpukSL;770gA5Qxptr@IobJB#O77($LvyfWZoK_wBrD zz0Z<#DeSDA`=qIYvu*`+dwmqnx~cWIKz?{Tf08Yead+@E$}#c#`$!+f#k3-~*AO9b zfh5|p55S@LB+L~${u&2gcdt8m<>r%NTL(|5Jvg?MKyP1!iCwwB>DZ_lX&fKW<3h$E z+I)X<2hXl{h9+Kupa3RZ00mY04rlqKem&$AL9!8EEBB1D8YPHWB>uwX9gg`RK_p}A z>3t62BJ%=B0actRv(LnPkYjfmq9=iNj1Y5_(+5yWG7<$ay*NNzF7C{0H`gS(-~SAK zM-FQkaFItRf`R}MNu_j7Ackw`PcBP!;m=hYn}8ERW^@QY79uaRB2XEuJ+lk1-<;;5 z|3+zMlYQrGqzNxG<~J(53aK@(hgle?bo`w>qZ@+8B5-iW^gS-9FA7Nmi;-JUDkIi2 z3APmnD&M@5zfEg3RSUbXMi$!l=((b-e#8ops2CACnMKAHihs;NAZv#N+NLSBfY2N* z5e?{483LU8es%zpJsT}up`6f_*CIWU@SQD+?=!Wa9O=rl4G@mp7N#8u*U)QqyYZ9A z&{L*6?^^3Tf;yB5#&q9Yq~%f_UAV<^WOq279RfZp2`YHFJI`at74*p6{A?@5LFkrP zfpAE$-C18>hw=460cy*!qNp^P!~el+|DzBOUMdP48V~J_H}~)m?wbbO-Q7|9WAEXW z*o9#Ad-%(YPS_A6%$Y^GMlQRTr$>!XAP14m@5)P0=*DA$SMTNH7*Z*XypNYReZAjQ z6zVnIh|Tx$+E^OT-^a63;USI&_W>lDe0;cx!#*hM-H%pcjaW|X|=LEfkbTy3N!!*ohf zD==#CZsnNgz1)IG0|kUu^ys&tcNBbgy7dl6jMOH^boI;OmRxobrAygS`wmi(L=mU zj+U*2(G{LPh(!gfh|!!%$gv<7ZI_Jf$*a?}Ac)^0#7HxF$d?yd!aMgwK!yW_q&+1- zjqS;AYkU)qOp;g8Hbf{ywIkaIKeA1hP%{2u-pt^~_PGS8#~$XFF!LYi#j7@jYN8pb zG1NWgsC(e6#?X0;_6JiBa?2w)4UE+HV|p!@_TqJO78oUuh(hjpU7feV;4dtqi`AtJ zz_A!fl}RhW@{Z8)1X<+~{#5P$a4#wmNm$@)EuLuf!(Rz<$s_#k4y146dLhaUpx&xk zgoOxOA+->>>E;OGS0y2oxA*3?aA5tF-aJrE%a;%taM%`Yj9l59SFC(NF$GOrET2A6 zpRD^n@h9eGq&+{$3o^bBFOSf&>V0@lreZCv{t-b)5mF55clz+hXtf~-=p+{g zJL+;nfwM`ZEF%((lDsK%eE$J(;OClYHpT<)YYR+0F}g1whJ ziELxkF$^Z3dz9Zp3LX)GQ_=H7+bOm2kOv;+>10LPCz1FKA=c_UL=%PDI%sA2$9UJ< z9GC+|VBVv~KAY(f+?Ozr5oa7`L*Kg1)zgk%6F+(XddD&nGYH99&%Dg>^QF3^1;U5wI9 zkeOack(n#jBJI#XrgE|>4cj<$3AnZg7+JWKCaU~Cp_=Q5KtPjr6l%R2kz&xhkxa@E z+1vs3sNL~$$&)-4@t2;q+^WIkcTe(+z@PBOsm%af3lYxIY@%!z!VRd!vf@)P{1wQy zPw@+2-KJ0TMYJ3e$RZ{N6jis_GrV%u@wB*v+OXUMsm|Tf3M-!Bb=hM1!ZW-k7QgqO z;k{^WyZQ_tQ08nMr7vUZqC4IGlcW3c?A-4i5p6+{Bpgoo0Sh(s@Bg3oaUcyL;b`yFk&pJsY z^04b|?8s-awy%{1&%$pzRmMEW3o@>o(W0sJb%fY<%sC_rHAb;WLGa{rU~%%mK{10# zh1gPEB+J$G+sG(xGVnZqoItRUIUp7-!d{T+&+`$mGG@QPGs*2o^}!H0?}np9=Ta(A z8ms4D;M1#;|JGq60>la1bwvOhzQ~7@F_r)jY4P(wve52)k*_o_AzSpzas7BdQ>uHpGb`>U;caJf{|HURcdNHZN+>h^P>>Yx9!-eoc8}RFkK9 zAdHD?`)j;a`V^ZKM>9xsJYbH^N!ECs*K9&rsdcOn)wj$8hWa$4sWHKeQ`JA_eRQefI(FxOwb_t}+~D~h2h%2T!RY_uOlg1l#eaFwpqbB4W3m|*?VZa z2ahIY&4bH@&>lwqLV4^BUd7CcE0&nAoEb@P@`|)%5mbNNT|_T+{wWIA-C4?6tm4E> z;y5f-|5VAf9D75&bG;~){slA{J;Cr-xT3HQsmaz2V5GjSE>*F!))`PVRb26uHP+HK zZ}OU^y-Q-muNNH`xMQZA@Rr^Oq`igZ;+(wgEuPidX~*g?Xw}JH6@-Xg?g4Quk@BOr zcx^LT&c%rLFNb{Y>(xMUWlcHN5}f}$NV_yh-Q@vlt#XgZUcB(_eCnw=k>#jhYUm+-OSZI9cg0X z(o-lZAw{f0Y@mTCtBFz&lU<09Cqw}PJ3IuoM2OkW`%-~&sPV@Ed@|cBa|iOh$a{L% zAf6pRDuH$<;@ti?1S`Eeh*#>a*#UKZshy>vt9AXz=rPQK4Jj{11;rp5UZtu--|3qU z=nez55Fsy9v}$@K%+f1^czdu#>%qKgsnb|@Xq!UEJMhPfQV558c`zUu_j|9*_3a@INdVSG-E&B}NWy%>``EgWeXPm($-@{jmPEiD0QD0~J%~$93Ol1e#Q~32B_4<0@ z*VonSxmsq0S}|937W6Hea-rAz@fsJ&<|UZ-vi?vHy$e*&ejEDQ3t!z18>BL5ElxCL z9eNk5*9Qx~KB!&~ODY6COkJw>sry>)^%8JYeSOz9h2%|qbvtZ@x|j_&k%TwuxOzP* zxzOuT$<+C)3%_2iUY{)d`lNb|8$rTxk3%ETVe8!YGJhB^qi&5M9y&#RKqctv6}e*= zbn3nG>M(vs{FW%%=A}ah*u#R|-bUOOc^q=z;Sck)m?ve$S~dpNIM!T5v1-9yl{!~8!PuK$M!|1n;C zfJ=!y(G|4HZXM1WU|Vo)I8UqV$oRkF#kwMZS}1c^Q3B9tD1`F1cX@LPuOQqb6N)ba z1H&wt|1MvU0S`+^by<=i&KIdcK0gAEw`p?J2%bh;AHut`SitQgc;{PZdJ%qJ9GnVI z6A1!we-+aiRsk@{DP#}C#sXB`Gm^iUvB0>5sIsq%Ln2R%!X^K`NP&uU(9gS5< zys5G+20RSZ?an~h=Rkr|Sjf1DBU+WfW)bNT7UD6*9=}W*%VV$?tTC3iGhKW(QGDpp z+52l1e8^ayg)F>_#`0lolWabYFV@1jl8hYBd)(^`&!QwT+*3AB=}Ndfo7PKZkbumm zBmgluK4nzLd8UluPL^Tu)n>8*7Rbfpd96(BeoW3b%ciHO*P~K7Rs#SD(b&BG1z^llVw- zQc}$y_{9+8#&kD?UMJ-+8JNr~-!jtjCq@J?Ex_z_*GETxEj(ZaCmoVnePMo#s3g8u zb`7r;R{nc3f4Hp!&_H}^v`E)bNvpXsHN_w}z+h?;0yLY0(o=ZLTy;Mz^pbav`C$Vy zPrrvf9b6gf`S$5-@6))k-ls{My-#_-;sZ`+3R8n{;>0C5S4?5|p`$4zhl!h?>!RsI zvhD~fur;i}WMUP-tImN9MB{=Z$21dOJM~UhpUSJ!ydfM2`EUWzK!6D7!RAxesSwmxF-h6Rv+++bawQC4|r1& zeSZzZdH^&3MMuRWJy7ql)TTNpmwv$8P)G~`?lFJ>YA8-aF#yvCg9+34gLLD@)6@Av z3MU}YyhsNpKmqt2e0Kz^PE_G+9PA>ZW=H(sz^?8wDs;$$KRbl~=^6ar42O=7Afkt{ zK{9<7R;Y!t`z&aAGvujRaBw=cVyC!4wM4hV@VttJgf$}%IV@7tQIQ0gli}Y)l=LB= zI{llBWU3C5HDO4|L#ip)f(;JZjqr&K^pU?Z2D2VVkE;g9&*m!`8O-VOJA^O)u~kPK z)7j>D@vGB%`TSg7slK!F(Nd>u<^b_!MA0sQpm7zi3WmVwF&!O8^Q!IS+EM(M)Llf#jFe&Lg5D{6|_6(-@o#YrQAL@b>vUP!sk5 zGHvM{5`pjVi-iNiLNXj`#?0qqp58=OAjnaD@|%YRz2j6k)-jrH)W0T#9Uk_ z#aX95AMTY2+6!X0gf*LNisaWVxs{)lP^~&)fB=Gs0R9;j zNxg)V>hbo;#Eht1bh2UI&rSJadP(8HYOYQ!#%7|Rm-xKpCT zVoqnjpIyv*ksXEbe-bU$oFl9C^rgHM;y+I<=1<*8yB%!KYz47B8d8 zI*_6vJe1<5M1=|4mGB9fzl48X8z>pwOm_+y_UZRHv?cU8QlF|Lp+w3VFDa$LF|a{7 zR9-0jDI_c*XmkV4E#;{eO5yaoRa=gV_eIA*Eyq~ky5zQ>@PU;Oe9e-hBhcgiN{akX zQ&a=T#FB046L_!lomhxR4PYomGwDxLb< zQ4fjsE3slWte$Fb7SavSk^Z3!9$=lI&He+I=&9`sR$;JcM5K&e#ZzxByd;w2gxOa@ z^St1O)aoq8U7stZn}r7Jv(ps*8cau23=+SbCy>V3Atao#mTo z-FJi1FP{9+kxpdblk=A1L_kooH9Sqx65-Mk_LoZ$-fGd4fir5jIt8?Z>Y+Z>1fv03 zXGfh%9FdSKEShLo{)s`Y8?i1KV=6yb!;@N;(6R=AaXLlUVRpg5{|iDGkQ&e(fa_=s zd0L4YSHD5mVpE@8z-zVork)A(RGnKa%>$WOHoiEGVtfIwmI`$(64)_^z=A5G(*92F zD&XaMPsb%21L~(oiroz7_*h$9n<2vA+L3uIuM0yhHx+d@# zo${lEt8~jZY5JD^gZyC~&uNMdLPU+jbQCG}n>v}Nn*utO{G<}93y~@k$Hvfw_pj#- z0uwkIz}h0)OyQo>!zt&j=RNBivLZudp;IGOJcnzH0CV)m5mKsKj^3zN*xNtn?K;pr zR-CH2h|*Tj>>wARCX{56B38`dV&U(Q*`!0^YC3`%=|y=L-m5(l&i|-p;J8Ewq>j=? z2SAVgdz_B-0f2~Z4x{vr4SXp@D&5$?pQxwUSzw_-BQe2`CsOi#NFJJgp{W4lg#5(Q z*jXF-#D`{UvB%mbl+>eYszd-Yt=E{=pQ<5uXEKQ-y$LV_dYp^#v?>~rw_p=*kzQoR zwWT@dc`Ktg^XyJwR^sKYzoyy0L}s98Nx?(apgkV^1RAF+dg%D61`1e4n^R+Ci2)br zOfacT@#uYQ?n&g?8Dp26EG7~9DmWOiF z7TzLrzFK3PWq_uDhyGQTm|EbGbj`N%mUnu3Sj#->j&aI3J26=?h?0ciBe>5JTL@DT z8rE**mD!i_&{kfd7#T{2(fDmV>wb(P@@Anw0#TvJdOEfYnSgKt@*Vocb&L2F6)Tw$ zwz6T!!w7h7}T#ce$Bx4k&SCw^T+ zv&2!lyKMiT>iWbdj$I)=u5exX+YtY8}HzC8>o)(bcmNH zEU{W?idsZs^P{Bwm>oQ`m~|P5^-z1JSY+G0n1S6@#DiCZ(A--L2VC%pn~NP)sgN?K z^f+W_fr{9a&y-De@&@K01xCWDTK(nW&0UdVjL~7e_~f*m{69@5fGHqcR0cmV0e8O~YNA}Qn^#Y9=85WI zn6$7a$m6?t?=C7onbG`w4tlp!DCAGAYgmM#Q&N`IK?R?j4?p z#J827%jkJqZ>3=yI=GzEpk02CV7>akED;~A4j_wk3x0!C;rlu$zU z{E|1P+dn3JiHsGr;V0c;iqaq^`c%=pXnUO8{iP1aCR_ufi?3Zq@8_@Aq^!Q~3WM8S zi}V$u@U={tx1Xoe{)BK&Eedzoz@6I9??U)Vo3D5|y6Ba_mzNAqY|3-Mo2+duBd)#b zcbK#n1r8#ml{HuEW+0+%e1@aifHHGrau~bgDOPL{k6s^hLT8qdtZ& zBTPM6TL@Yn;AsI3jWxUwFaiU@y6+bxGKkeHBt}NQ_&u*sL6Jjimp*}sV@z}%B(%!u zQhd)#r;w)Tn5dlEi>?M9OAXhY!93IYs|Mbhj<`$JAzZpG}>gooIAuTwKe7? zkuCOeXC;6%Uc@34eeOkfM9Nn<%uhRP(tko`71`}DZ&c5r_Mib#yE@v^2e^mglg?+k z_%M{lpxk?ymu~J9y%r}qCk2-pDhEFGBRv}(4Wf@FIi<*7#G>RjTeQX=<#!sY z*89a(+7r^)Wx4Jw`PWh2(onV80|Gahaj7YWEi@q8J)oX8<{jg8%=-I-U)-P#8dd1Q zCbu8s9qT(?b;1J^LZd6h@aw2SOwt`v{D3`0o*eiC&u9UcHe$#Z*tkvxLx9z+B`%sa zqdFm8^e+hM=!nmBnL5cY`>(Q^R`u-8CWS+XR*U98@~W8wOzvzNh4~&(Z+mio#NmiR za?+2ygK7Gac=4&(Re3>g{K&hS@z@qGuFP|+VWFnWXO8oRhNW>*$3+twGW3p`ThJ4< zuJi{Ivn$P^F0ATA>6LhJ2pw;olC1p2)J08sezNJJKRtN8{NiBdvcgZiooUOc1TlH7 zGv~c+`R`Bs4I^2nI50Bm1ivG9dp$^MtYpIwjRtC#sg(3kY}C{81fmVYHw;EdXZT>) z#c5bQc@UwZt0U((LxO^DIN) zZbRVhKO-h)oP73YCx#UmzcSz6M}Y?%G7wSYkP%X*W|(*A&qCd%L`7&6(UC=jhpcdt z&%Vn+e=0$YHfF_!bV^XEWV%>nNbH^WfMtuLUNE(QMA%=-1@$pJb17w$@};fckOBtQLyU#2_0X(T^Q6w{4EyCEeq@lYKlVqOVE zz67h6N7@&73S=X3ur464{L0hn9fO6^4Ei<$SArz=pOo!?@AU z^E(aAy9 zd3sYEwT~8g>u|j~{JHw{F&V#MlqOlxRib>NTpF;e!X=ifK#=U*m1^< z-kdho1J<$ex@(23bcR=`L6tbc1ytaIDNtzo%ZJbKcAfIoY!DE9lU z&4uE)SJ~CcoQhp8f=edI;8}iKOJ%wTWw4k>(f>5s5*gUsP5O``BWosvY<7+}$33_& zp5tTgqcw+o$k?%CNT7hz)t!(r;_NY>*lpR!nt~+=MghtdiBIrZ!#3dY2Mw@A-cZLF z5Mp5Yz3}h|`0h6+gH=O;tRRXt<#*?KJIdfgLJ9tH7bM#S(V`R@n&I>X9#KsS+2IZr0ns&rf%DdpmDHr)vvKq9!#P2TN?mH@ts2G=4 zf>TKhNfv+ZDQ@x|xWofS&W=kKKkg+t8+)18HKJ##^D^F1LPn*zIgM#JW_Uk?FEknU zHVVNv?V4j?R4?Yi_OI+udeNj?_zo&QQD32Q=&y3wWnQiL#!gg?ol;_BKT8$^_Z8RJ z8h?ZuyER$-MnKfqYPm;1foCDx>wGkgo*3Tfuf3n0Mo&o#Z}j&Drldw6_B1-$X?2iY z;U_p*X(*Iy1QVk^qjo@K#Q}|19gF>wcQzqkGYlMN9THf?yPACPl|T9OjPCC_ex2{8 zh}DH7V?+}%)a+9miap%n&E0?T8no~eL`w&PhMa7(b|N2EyE(T&DjYP4M^0YOQHTkC5Ue!S_lfUm3ea8P3)B+^7OeJv#VBNIDAil#f6^i+ejHAOYzM|Hm;HvEqC){P?Rjd&sCAZ3v> zbqOD;a`AcF1rg}?f5{>^%aQf{Rt*`&EhSi~&=uTHZ(P`(VQ$5(wC-jVp$)HFzq@)I zv{8Q!zMzA@wawx|IP-W2rn2j87bvCU<8r70I<=$wkVeYvyXbY^TP~RnGe`Nk5dBH@Y7LGUEap&(cg_Rj*Egb=|Y zc`Ph)ZzNhuZ>2YLJS9COUr)4Zk{(4MOFbYVI@GR_f-(~+WEZYEWZ}QyrD^To6w78G zL}$c+%W*;nMRDn{L~dP+W*&C_&*BOR7KoKIlPtR`aFRJ4gE+`U^XRA0mOy@!WZhch zX0m%J5mAk^+r@T?Y;9X*a{)|~%*ya14Gysg#XhCS#fR39D2htV^`x0-0}vmrJhNtQyY?N~p|sVEPHE(pwkkD5h__EA8;_yn?LH?j994q6a~cDf z3Iqt;)HuP^my4P+3U?D4quqX@wBY2}k7GWR(Zpg@!OXn@-jM z+_Disw6@+}5Fv{^<+H`7rlUv=l;g`-wM~<^7&xcN9t+j6GS=M&>|hZv+`6LrK6p=A z>sCg`{HWTW3c-cuJrPbrgz;J?*Ojvx7#VP>P?18^l9c6QN(SA65$G#+rrS6G8EWTE zQ(AX;SU;&HUzN>ZZMi4@%A&Z9Y|MSub|3{=8BbUKsCaavllT${uO#**6?kG2?I7i} z@>cb*_!Am9g&t{;%zPbzV*tfE_tE??dTM*>b>m0^5si6f=!15+dF!aGdKm}ii>*ZV zsbEzIBT;z*0}+iN6#ovb!uml8SY2zH?nF)eu8?USodxml!!bu1behiP?2GaZATTS~aL>4hm`tv&XyqE7 z-5x96`<gvwCGVG221+3N0WNYU_p zPJlQf2V9-^jx_5D)4s)Uz$!aT(e~|0vj$R9PO5BmiD15X+?NuGQaA%qk@LO6acio-K^6DWKZ;Um8W ztbaF#t=YS)9O!qNU1Y}VtA3{V=z_;_>DJr)CX-b%tn|AVA_=dsD9NZ(`)m&mGXNCS zN>}E%=5r!*fd|k#ymCy2Ro~GF#bwi$LKMkk8CLUJ{c&p^ZI!3^bgUPQ2C#NP4#G91 z7#-)W&qk(~!3`Jfs#?no(gP*cTsG6HU2bxMJt-j!FIGNk_k)>O$*0PNnbs?2s@(8k z3N=>VTFt6sKnCF^fsjeh9n4|Xtm=pcSX9j_ovZBksu^U%hn8ac@=CGjp>iu=&_wKs zdF8;PgTuJi+pAPaFPz82Rzyq=`6aC?+^-n(SHKs$Ep*L1a6@c_hfq zQ2>u{Umc}{`*kM??krj%Sa?UAuV$F$^@rg)v=81ob zu46SSCI}YeEW6dQO5gIg=R^qMLbx=H$2qKRQ|nk&dK79KdWWLlowvk4c`2btWGq^c zJfVX$m{;NKv>{Pk^2QriuWP;0*a^LcN{w(O-ScJ&iysojq`}37{(N26%J{nxJD(_~ zs8eW#hq`t>>$a{%S{0@a3Fgr4G5|*E&rqCjD7uk$^8P)tpq|yVO))r&C__+KsEeR< z7@3xzBu>!Q+)UOE*;bWGn`xs%=cYV>9~A-|pKWDk|6QFPv*a3Y*(~kB#n53FNPAdv zih7s5jb7+n1+Dc-?cyYL$ZTx&tbzZN^01!6YbGbR36}D3#y{y+J#mXB|Z+ zfILcDIzZNIV6~)Gkz9mhYl~IW$@*WIfrd4(s;0wV3ZrO1xl8FJ1uQdALt=1t89!+0 z4!N^|Rh~8;glmeyrG!mJOoO~Q56FxB~`MFJ#c_bX>8S_e11gLJX@UbZnD9a)41MClChDbS0Fu7 z@iy~pHOwjlnrXd}BN|(^YvsF<|E~0F3vaIb5Zd|39~xVATxozkXg#EuEHY8WI@k_y zy}a!vxgm;II~1#RQBOS#B^m!BY+OgXqaW+}3B`-Ej+S;MrT7wo=I=}9d_q4EDlziHhJkcH+ zJ!s!a(P+&&d8mbThvA7ODPo#(oHuQWEuq7r%gxJvEnyGaCa1Qv8Y2RBUrX!ulItg@ zaPjqjpCYy>f3@S=>f6d{YpObzBIc-Xt6EtqYKr>vvUz#Bl~oJ%mv3z~$aZKOTuK)R z?;{MU^5A-QPOrDNjv#T%18vZGYvU)hu`*?T8>4 zL|z`Ve9fcofCO)*Yo zrA0=gp^nZ`AltOF%CcRuM?090oSJr;nhI4U+gbJ4_rcBWVArWZRtO{tRM$G9= zjOYxcJb*OL;;#rMM|HM3QrJIHH4lf#LcJC00c9RkxW1IEi*>i@1tk7(;XS?ZVi&9K z|8}&38@pKLIUzi~6WR>&+nE$vq2ZF(x>_nejX*YpftZWSg3oue@{~7_X2x}&SZC&T zwwS1=e^C1GvX-*PWWin5^7^NvF*%T_IuQG820BZNZo)q&L2PZ~YP@Dz1m(=TtsCsI z;D`UP5*d&zy~k3w)Sy_V3eXVUdc%@lg+1uLKsSs0|y40P+Q< ze~}*mGMi?Vob-^@qP0WjWsk`4G*>5fp?3UX03dvjuuh>UkjXu*^6ZqX-4jlZRr1-M zRvOzHe5a@NKI7MX!D_v%Yl>ISAnFTF^!adJt2NehZC|TC)+Ab!2xJ3X&zk@uf9hkk zGB5X=m-QdT>M>8wdek~cHl9qVZyvp&8+SMss&d3d9wWD*5a5~ zl^SY)FZVufWnw|S{J1rM^^vbXfzCWAmpoy0VyES`C#;IT(?0(SS^i1u-|;)KpN&=# z2_R_llh#AH8}g4QF=lJzy-!)UKwo?BDXUBJ8tlR#l!Q+$gUsF~Pd#Pb#g580Pg~WR z9VwH6!OS!U=jWkei!p-_usd z0G1`%cXM0<427?Mw7?B|G5-|japG*M%9MV~GghTs+**S3pb-_I5OKs*(D3UU)p3sm zB1)0vLSLxP#rHR2p-nhXI7Aia`A8c^WVxcg499}}O7?qDK0B!|a0R%8XiRMY9|OiT zXhV4LmZ(9M??zKz7+hn!Gal)ci2fDoDqxPBNhy7;XYRQYg}aON#)0S9gQ9Vl415jv zrIyhz9TiZ56^05tf=&hxNsK)WO(d^{EqpIEaW3!dsM+108@n9ck;piJlnxCl2&$ zF344Q^FS&#uZfCk^e_>GOT94gmYZ^trt+pr;?E%2zLR)NJGvb5~NW{<~3r(HKSr!SNCsVh9E(*f`teODw3n%C?GjXR6&p=AW0=j zMhWlxs=NC39R}Zf>uRXyR6Qb!^4#-}!K-6&7c`BB-H^f=VE8{45nr)} z)8**ytlWpcMiz){Iq?PaZLSG^ToCWbq0u1WBYMkNTV3SJE-#uB5p7edtN9^T_vEf- zZ)2~l|B{(|y;mAZ7>|GI+sVBJ;hM>D63kPlL6u1~#A`1>pY{p)!AoZ2@MWXmyO+!+ zM!RPF!s#ns6L=%Y1*W>arsS>S|MK(>Fyd@)^Jp5gD`ty&u9iOR)`N+m`4wt_fHsL`hI(aEQsl-?W~RLBRWq}>pYDSYBYg-+2cEXT zB^Q1j7yQb+pyOXPyWTcgYuE@fKH~f`JemXm?uA5*n#@R?M|p&s919oS@H+p;yf;Oq z^zib}$R+C@Jx*UUP4qc|J<#^f8W0KsDGf}c7t-xu$shmD1USEn0grH$fhN)T5j$QM%- z{W{D0Du@2p?3;8ljn@hBcqU&t5iUhl1lvU5rAvS=!I*7a7)&zT3J?n7FnvHE$aL~u zw9k}zubX9Bl4g_Jg%rVJhr#v7=JUlYBNPf5u8#~G>zVCb(#f=dJH2@$u<~{DZX|1n zQr^+>&fdv9W1(A^4CR0@NNZN1w&g{iG4&a ze8c>2b8qM$X#mV7bwqje!g47IIx)40qLV(=aDe+|*Z-Lf_0m6*?tIogy7c$xrN8KZ zW^?K;Ipp&Jm_S@VD$526J$@V{?u)vB*Q;FT7oWa8&jbqvyI?m2%k8ey0kEwe8D4)t zQxAdZhgA=HXE3+{Dkcyr%BgpKG7xK;6Q^~eEZk2QcIi6Graf24#g-~LFayVr&Hras zYE2a|?O%))8pfy_RXs#H*W#k5;X_?7CLvZM*7QyDhT=U+qGLi;JvIY6SRPBR3Ewb2*{P?xv@%1flLMtz z$CmQZBwu;UtPXqb$8SM6{!}h~%dE!>I3W=+zf8Wcmw))SnSSeP>_WGKPS0`r_hvxN z47|Y#M5Vi%%SWXgfiE49QqT6lLAo=Ghn2plveP||!VS2teBy1hMq|(!Pvqm9Ix`1F zX)#E(+)pujt^p@ORr{S>`nLH%UY;^1ID<3c%%%=%*o(o-dO$16=sTCpE)-MZjK=cl zOlL&jqS`?pVQ@&|9t;rxepgI@#ASy+p2w2~xFoHySww@O^kVVXK%i& zkq`adYI$Ei3}deP5;z_Z2j0QF$gQFB>`txr6gf2C%xV+ds3DE`_`q2$(HkP;zKUaK za)UcUfi+@xu{UYoQBKFO ze<$I=RLz&*fT?#`rMVsmvNOni{W<*XiNnX2=Pt7+H3-^K#tB09B$$s;|? z+`K}t%Zr9p#vJYsVY;CU5Sn5;Q53C-Ef=76cXQTxNx;ECP+%P}6tc2AqP2DOQdM(e zH(sl{WZ;9eg+q&4K0C^Swt#52gCMPaJrFF=cPj7F%ZPG8gz)3-O~hjrra=0 zOlu^^@SnT?taD7n5PITHw?_RW3vO|8eBE`_M%uK16^Z^#|1Y^^h39dJw!Ep z9$PD!=}fucU9&}1{LPC@i3)DII7IT6{tFy=WQE>ld83DH(c8SawVM#d ziJ%yGQN>qqMJcd`2|!wtpqRL{qBCfmf1rs)oDjsgm9dv8#@+7T!bv$%%$Ge_u_ zj|6}MEq8p+tlrjZ`D6g3t~1qJc0%O=@Ez0G#d86`KwaOwXGU~$xa_5CglurybPpLv#82I%E{k2f-^3OqLDJCYQz`_Cc@KMh#1(tYR zOze(0cd*%Bqxj>LD2nSkTQtbP!EnQ6CK^|}$ocv#2J(mb<6O>7q!ZAr4#v z!u7>tEAK{I$%4PDHUp?(QB4=YervCjt%pE?w^P19#H3r-XbYR zF$t5bGLx%yT*76Cd#KRYJ^HbjGvlNu+thV_@=p{BXF!)i*=bpCs9Cnu6U>1|M_9Lo zV`xD>I@Fv6R$#wxzNAt5b#w^8y6=iO>V5NN4YDEtlH}liN_S$ae_;Ncjl3I}bS~04 z>JH`*ZpnZc3`3C8!`ahirD0~%%A6dZO*-9EgVQ)fPh+=XW@TtZ09w~UAe-CG_lKJ|>5;6D0okfSuxm7JbtDbJ|4QR% zKma&VrAxd0n%*drpGtO&@5+hRt?#?s;DLB;<{=i1X=l^`FPX(k`7a} z=~+aI6@N@IOAe|+;*G6LQqv{Pd%eBQ`8mlMP{|d+0{myAG~a3dJ4i&(G^Ve@WCa(C zIsMgzc$l3A&Q7nJI%6q!RYVXSlzm2+6|dW$Mm>l+1tRA0a~wGXLh`QS&bw2iEpg8EMv%NgtWpG=ia|@_`#Tee`Ty`N*uBw=pXRu14j24`}xCR1EyU{D?bg>R=(9 za+EwW7>O`q0%Pg|y}A%6Si^}eh_ozlV22J}Bm6RfOL$`lbZg;A^GV(FK6bF@dL_#n zJ~rFrO=H$2i0{}o>0W|=%E7OQKIS*uGjL5K2u07^5cmM88{GUi!}vyR%|tqo%i?Y{ zqVC^_K-(aQ0E!`Y2VB_g=J zasCL~f6D(GWmYP_m;UJvB}kn2;JYgyZ_!9XpnY$xDv8!D?;MRt4X=aK z0wB4#B~o{gjGh#RyOJncQyv=P3MsQokoT#N~g zd>kF%P#`F|UzX!bK(DqvP%&H2_b6Ge`@}4p_dYlaE}r5bQ4arpUDA$u2U7AXqpp3B z>C747`FqxNdTT+@&%uP!-Q!u!&XVn2Drcz=0;N`koIf?{osfKbtXY*Dm0%yog89~; z$z#o0oj!=i+&Fv2SImL*p8$}$u>>4B{B^e|Jh^KeSXw^aDfg#1iYq+&N3)ViCPI9J= zHE}`MSIWOF$vKm6wTAgbKQ7;%0GY#kvgen~Ne(nLv`|By*|k6({tWKrIQ0fk?($l| z^*=Yu+=zrNDn$-Yc{o4xfnR~$hwlQ-jf$<|AlHx9@C%$``h9MegGT3z&&>$kb`E}l zP@Rx-_)Fd#pH=n^TbB$Uka?hSH_!lG=0ntl*o17^A@e_ z_=Q=k#y*jaP?scoc1d7qH@r$&9NxJ=LUBwbOn1fjPa+I@W4|yv=qk6DbPBYZjQ51O z=#z01%vKMM4RW7f1AuiM-gWEZ8C;*-+_bwRrbk7026y(~0;NE9I=WMinGij_9Sgt& zsD9rBGq=ilBzE#VX~}`%>*e=05b#-dqFJV?ivY#rBVgT|6^{pJRS9-k_{&nxCaw64 z9s%WB6U};B6kHJiQP)g+=vXt+jA%Q^`WP6EF(ppGJ=y*YNYgX?QvejBU+*DJHkf2K zYc&B+Q1H^Oi%##c6p`DX2C=s@w)`ZoJY|1b?^82PE}CREXI@SY??3=JibldZ0rg?b z^83RpBD0?nyq@RI$!2Dok5gf$La)e*@eg)TdR-gtWkpee=X9VsjIDQmL{mgq9yyD{ z5(qHCiL)<5kE!xp@iEWtj_VnM>-XZdt07^rwMh$&?b<$p)9ulPR)iO zjlUHF`jz$$I30P-SUDFf{YytDy`l3rh*sI)I!{3l*OL`ttr16h%0ATae6QqMe{baJ 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