按规划继续工作

结论:补齐 OPFS G-code 程序文件名的自定义路径与非法遍历/嵌套路径拒绝验证,覆盖 Node OPFS 与 Chromium OPFS 路径,并通过 host/WASM/browser 聚合验证。
This commit is contained in:
2026-06-08 09:26:36 +08:00
parent 78f54bb64c
commit 70e2597295
5 changed files with 51 additions and 8 deletions

View File

@@ -101,7 +101,8 @@ tool table, parameter, G-code, preview-cache, and session-snapshot storage
targets. It also validates the host-side session snapshot JSON envelope and targets. It also validates the host-side session snapshot JSON envelope and
round-trip store, including unsupported format/version, session-id mismatch, round-trip store, including unsupported format/version, session-id mismatch,
non-object metadata/payload rejection, custom snapshot filenames, and invalid non-object metadata/payload rejection, custom snapshot filenames, and invalid
snapshot filename rejection, plus pure-text machine file and G-code stores snapshot filename rejection, plus pure-text machine file and G-code stores,
including G-code program filename rejection for traversal or nested paths,
without defining CNC machine-state or file-format semantics. It now also validates the without defining CNC machine-state or file-format semantics. It now also validates the
OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the OPFS-to-WASM parameter-file bridge with a mock interpreter SDK to ensure the
host boundary copies text into and out of the WASM filesystem without defining host boundary copies text into and out of the WASM filesystem without defining
@@ -115,7 +116,8 @@ The browser INI/OPFS smoke script serves `wasm-port/` over localhost and runs
Chromium headless against a test page that imports the JS SDK, loads the INI Chromium headless against a test page that imports the JS SDK, loads the INI
WASM module, queries vendored LinuxCNC INI parsing through the SDK, and WASM module, queries vendored LinuxCNC INI parsing through the SDK, and
performs an OPFS text-file, generic session snapshot, custom snapshot filename, performs an OPFS text-file, generic session snapshot, custom snapshot filename,
invalid snapshot filename/envelope, machine file, and G-code text round trip. invalid snapshot filename/envelope, machine file, G-code text round trip, and
G-code filename path-model rejection.
The browser interpreter smoke script serves `wasm-port/` over localhost and The browser interpreter smoke script serves `wasm-port/` over localhost and
runs Chromium headless against a test page that loads the interpreter-core runs Chromium headless against a test page that loads the interpreter-core
@@ -186,8 +188,8 @@ The validation fails if:
| --- | --- | | --- | --- |
| `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. | | `tests/wasm/node/verify_ini_wasm.sh` | Validates the browser-facing INI WASM module can be built from vendored LinuxCNC `inifile.cc`, loaded through the JS SDK in Node, and queried through the exported C ABI. |
| `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. | | `tests/wasm/node/verify_interp_wasm.sh` | Validates the initial interpreter-core WASM module can be built from vendored LinuxCNC interpreter source, loaded through the interpreter JS SDK, run the first fixture group through `Interp::execute()` and selected file fixtures through `Interp::open()`/`read()`/`execute()`, match the native canonical event plus required state readback fixtures, and run parameter-file restore/save through vendored LinuxCNC `Interp::restore_parameters()` and `Interp::save_parameters()`. |
| `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. | | `tests/opfs/node/verify_file_service.sh` | Validates the host-owned OPFS text-file adapter, path model, session snapshot store including custom filenames and envelope/path rejection paths, machine file store, G-code text store including filename rejection paths, OPFS-to-WASM parameter/tool-table bridges, and grouped machine-session loading without moving file persistence, parameter semantics, or tool-table semantics into the WASM core. |
| `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, OPFS text-file round trip, generic session snapshot round trip plus custom filename and envelope/path rejection paths, machine file text round trip, G-code text round trip, and the INI panel UI's machine-session load, G-code run, and canonical-event display paths in a real browser runtime. | | `tests/browser/verify_ini_panel_browser.sh` | Validates the INI SDK, INI/interpreter WASM module loading, OPFS text-file round trip, generic session snapshot round trip plus custom filename and envelope/path rejection paths, machine file text round trip, G-code text round trip plus filename rejection paths, and the INI panel UI's machine-session load, G-code run, and canonical-event display paths in a real browser runtime. |
| `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including OPFS-backed parameter-file restore/save and tool-table load/save through vendored LinuxCNC source. | | `tests/browser/verify_interp_browser.sh` | Validates the interpreter-core WASM module loads through the interpreter JS SDK in a real browser runtime and runs selected positive and negative canonical fixtures through vendored LinuxCNC `Interp::execute()` plus `Interp::open()`/`read()`/`execute()` via the exported C ABI, including OPFS-backed parameter-file restore/save and tool-table load/save through vendored LinuxCNC source. |
| `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. | | `tests/host/verify_host_smokes.sh` | Runs the current host-side Node, WASM interpreter-core, OPFS, and browser smoke validation with shared WASM builds. |

View File

@@ -71,8 +71,9 @@ semantic rewrites:
file and G-code storage, parameter/tool-table bridge copying into the file and G-code storage, parameter/tool-table bridge copying into the
interpreter SDK filesystem, grouped machine-session loading, explicit interpreter SDK filesystem, grouped machine-session loading, explicit
session file-name overrides, invalid INI-derived machine file names, generic session file-name overrides, invalid INI-derived machine file names, generic
session snapshot custom filenames plus envelope/path rejection paths, and a session snapshot custom filenames plus envelope/path rejection paths, G-code
Chromium localhost round trip for those persistence paths. program filename rejection paths, and a Chromium localhost round trip for
those persistence paths.
- Host-side smoke validation is aggregated by - Host-side smoke validation is aggregated by
`tests/host/verify_host_smokes.sh` so Node, WASM, OPFS, and browser checks `tests/host/verify_host_smokes.sh` so Node, WASM, OPFS, and browser checks
run from one command. run from one command.

View File

@@ -87,7 +87,8 @@ Current validation is intentionally mechanical:
filenames under the session directory, and validates its format, version, filenames under the session directory, and validates its format, version,
session id, metadata, payload shape, and filename boundary. session id, metadata, payload shape, and filename boundary.
`runtime/opfs/machine-file-store.js` adds pure-text storage for INI, tool `runtime/opfs/machine-file-store.js` adds pure-text storage for INI, tool
table, parameter file, and G-code program content. table, parameter file, and G-code program content, with G-code program
filenames constrained by `runtime/opfs/path-model.js`.
`runtime/opfs/linuxcnc-parameter-bridge.js` copies OPFS-backed parameter `runtime/opfs/linuxcnc-parameter-bridge.js` copies OPFS-backed parameter
files into the interpreter SDK filesystem and writes back the LinuxCNC-saved files into the interpreter SDK filesystem and writes back the LinuxCNC-saved
parameter file plus backup. `runtime/opfs/linuxcnc-tool-table-bridge.js` parameter file plus backup. `runtime/opfs/linuxcnc-tool-table-bridge.js`

View File

@@ -9,7 +9,11 @@
<script type="module"> <script type="module">
import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js"; import { createLinuxCncIniSdk } from "../../runtime/sdk/src/index.js";
import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js"; import { loadTextFile, saveTextFile } from "../../runtime/opfs/file-service.js";
import { machineIniPath, sessionSnapshotPath } from "../../runtime/opfs/path-model.js"; import {
gcodeProgramPath,
machineIniPath,
sessionSnapshotPath,
} from "../../runtime/opfs/path-model.js";
import { import {
loadSessionSnapshot, loadSessionSnapshot,
saveSessionSnapshot, saveSessionSnapshot,
@@ -201,8 +205,25 @@ JOINTS = 3
assertEqual(machineFiles.ini, iniText, "machine ini text"); assertEqual(machineFiles.ini, iniText, "machine ini text");
assertEqual(machineFiles.toolTable, "T0 P0 ; no tool\n", "tool table text"); assertEqual(machineFiles.toolTable, "T0 P0 ; no tool\n", "tool table text");
assertEqual(machineFiles.parameters, "5161 0.0\n", "parameter text"); assertEqual(machineFiles.parameters, "5161 0.0\n", "parameter text");
assertEqual(
gcodeProgramPath("browser-custom.ngc"),
"linuxcnc/gcode/browser-custom.ngc",
"custom G-code path",
);
await saveGcodeProgram("browser-smoke.ngc", "G0 X0 Y0\nM2\n"); await saveGcodeProgram("browser-smoke.ngc", "G0 X0 Y0\nM2\n");
assertEqual(await loadGcodeProgram("browser-smoke.ngc"), "G0 X0 Y0\nM2\n", "gcode text"); assertEqual(await loadGcodeProgram("browser-smoke.ngc"), "G0 X0 Y0\nM2\n", "gcode text");
await saveGcodeProgram("browser-custom.ngc", "G1 X1 F10\nM2\n");
assertEqual(await loadGcodeProgram("browser-custom.ngc"), "G1 X1 F10\nM2\n", "custom gcode text");
await assertRejects(
"browser G-code invalid filename save",
() => saveGcodeProgram("nested/browser.ngc", ""),
/Invalid G-code filename/,
);
await assertRejects(
"browser G-code invalid filename load",
() => loadGcodeProgram("../escape.ngc"),
/Invalid G-code filename/,
);
const uiDocument = await loadUiFrame(); const uiDocument = await loadUiFrame();
await waitFor( await waitFor(

View File

@@ -102,6 +102,7 @@ assert.equal(machineIniPath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/machine.i
assert.equal(toolTablePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/tool.tbl"); assert.equal(toolTablePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/tool.tbl");
assert.equal(parameterFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/linuxcnc.var"); assert.equal(parameterFilePath("xyzab-tdr"), "linuxcnc/machines/xyzab-tdr/linuxcnc.var");
assert.equal(gcodeProgramPath("fixture.ngc"), "linuxcnc/gcode/fixture.ngc"); assert.equal(gcodeProgramPath("fixture.ngc"), "linuxcnc/gcode/fixture.ngc");
assert.equal(gcodeProgramPath("custom-fixture.ngc"), "linuxcnc/gcode/custom-fixture.ngc");
assert.equal(previewCachePath("fixture"), "linuxcnc/preview-cache/fixture/preview.json"); assert.equal(previewCachePath("fixture"), "linuxcnc/preview-cache/fixture/preview.json");
assert.equal(sessionSnapshotPath("session-1"), "linuxcnc/sessions/session-1/snapshot.json"); assert.equal(sessionSnapshotPath("session-1"), "linuxcnc/sessions/session-1/snapshot.json");
assert.equal( assert.equal(
@@ -204,6 +205,11 @@ assert.equal(
"linuxcnc/gcode/fixture.ngc", "linuxcnc/gcode/fixture.ngc",
); );
assert.equal(await loadGcodeProgram("fixture.ngc", { storage }), "G0 X0 Y0\nM2\n"); assert.equal(await loadGcodeProgram("fixture.ngc", { storage }), "G0 X0 Y0\nM2\n");
assert.equal(
await saveGcodeProgram("custom-fixture.ngc", "G1 X1 F10\nM2\n", { storage }),
"linuxcnc/gcode/custom-fixture.ngc",
);
assert.equal(await loadGcodeProgram("custom-fixture.ngc", { storage }), "G1 X1 F10\nM2\n");
const bridgeFiles = new Map(); const bridgeFiles = new Map();
const bridgeInterp = { const bridgeInterp = {
@@ -594,6 +600,18 @@ assert.throws(
() => sessionSnapshotPath("session-1", "nested/snapshot.json"), () => sessionSnapshotPath("session-1", "nested/snapshot.json"),
/Invalid session snapshot filename/, /Invalid session snapshot filename/,
); );
assert.throws(
() => gcodeProgramPath("../escape.ngc"),
/Invalid G-code filename/,
);
await assert.rejects(
() => saveGcodeProgram("nested/fixture.ngc", "", { storage }),
/Invalid G-code filename/,
);
await assert.rejects(
() => loadGcodeProgram("../escape.ngc", { storage }),
/Invalid G-code filename/,
);
await assert.rejects( await assert.rejects(
() => saveSessionSnapshot("session-1", {}, { () => saveSessionSnapshot("session-1", {}, {
storage, storage,