返回 DeepSeek-Reasonix
protocol.test.ts
根目录 / desktop / electron / src / main / protocol.test.ts
1 import assert from "node:assert/strict";
2 import { mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from "node:fs";
3 import { tmpdir } from "node:os";
4 import { join } from "node:path";
5 import { after, before, test } from "node:test";
6 import { isForwardedPath, registerAppProtocol, resolveDistRoot, routeAppRequest } from "./protocol.js";
7
8 let dist = "";
9 const isFile = (path: string) => {
10 try {
11 return statSync(path).isFile();
12 } catch {
13 return false;
14 }
15 };
16 const route = (url: string) => routeAppRequest(url, dist, isFile);
17
18 test("normal app documents do not pre-enable a profiling engine", async () => {
19 let handler!: (request: Request) => Promise<Response> | Response;
20 registerAppProtocol({
21 protocol: { handle: (_scheme, callback) => { handler = callback; } },
22 fetch: async () => new Response("forwarded"), distRoot: dist, resources: () => null,
23 log: { info() {}, warn() {}, error() {} },
24 });
25 const response = await handler(new Request("reasonix://app/"));
26 assert.equal(response.headers.get("Document-Policy"), null);
27 await response.text();
28 });
29
30 before(() => {
31 dist = mkdtempSync(join(tmpdir(), "reasonix-dist-"));
32 mkdirSync(join(dist, "assets"), { recursive: true });
33 writeFileSync(join(dist, "index.html"), "<!doctype html>");
34 writeFileSync(join(dist, "assets", "app.js"), "export {};");
35 writeFileSync(join(dist, "assets", "styles.css"), "body{}");
36 writeFileSync(join(dist, "assets", "font.woff2"), "");
37 });
38
39 after(() => rmSync(dist, { recursive: true, force: true }));
40
41 test("the root and existing files under dist are served with their MIME types", () => {
42 assert.deepEqual(route("reasonix://app/"), { kind: "file", path: join(dist, "index.html"), mime: "text/html; charset=utf-8" });
43 assert.deepEqual(route("reasonix://app"), { kind: "file", path: join(dist, "index.html"), mime: "text/html; charset=utf-8" });
44 assert.deepEqual(route("reasonix://app/index.html?boot=1"), { kind: "file", path: join(dist, "index.html"), mime: "text/html; charset=utf-8" });
45 assert.deepEqual(route("reasonix://app/assets/app.js"), { kind: "file", path: join(dist, "assets", "app.js"), mime: "text/javascript; charset=utf-8" });
46 assert.equal(route("reasonix://app/assets/styles.css").kind, "file");
47 assert.equal((route("reasonix://app/assets/font.woff2") as { mime: string }).mime, "font/woff2");
48 });
49
50 test("index.html is not a fallback for other paths", () => {
51 assert.equal(route("reasonix://app/settings").kind, "notFound");
52 assert.equal(route("reasonix://app/assets/").kind, "notFound");
53 assert.equal(route("reasonix://app/assets/missing.js").kind, "notFound");
54 });
55
56 test("traversal, absolute escapes and odd hosts are rejected", () => {
57 // The WHATWG parser collapses literal and %2e-encoded dot segments before
58 // routing, exactly as Chromium does for a standard scheme, so these can only
59 // land inside dist; the encoded-slash and backslash forms must still fail.
60 assert.equal(route("reasonix://app/../index.html").kind, "file");
61 assert.equal(route("reasonix://app/%2e%2e/index.html").kind, "file");
62 assert.equal(route("reasonix://app/assets/../../etc/passwd").kind, "notFound");
63 assert.equal(route("reasonix://app/assets/%2e%2e/%2e%2e/etc/passwd").kind, "notFound");
64 for (const url of [
65 "reasonix://app//etc/passwd",
66 "reasonix://app/assets/..%2Fapp.js",
67 "reasonix://app/assets%5Capp.js",
68 "reasonix://app/%00",
69 "reasonix://evil/index.html",
70 "http://app/index.html",
71 "not a url",
72 ]) {
73 assert.equal(route(url).kind, "notFound", url);
74 }
75 });
76
77 test("only the resource prefixes forward to the loopback origin, query included", () => {
78 assert.deepEqual(route("reasonix://app/__reasonix_workspace_media/tok/a.png"), { kind: "forward", target: "/__reasonix_workspace_media/tok/a.png" });
79 assert.deepEqual(route("reasonix://app/__reasonix_theme_asset/x.jpg?v=2"), { kind: "forward", target: "/__reasonix_theme_asset/x.jpg?v=2" });
80 assert.deepEqual(route("reasonix://app/__reasonix_remote_markdown_image?url=https%3A%2F%2Fx"), {
81 kind: "forward",
82 target: "/__reasonix_remote_markdown_image?url=https%3A%2F%2Fx",
83 });
84 assert.equal(isForwardedPath("/__reasonix_remote_markdown_imagex"), false);
85 assert.equal(isForwardedPath("/__reasonix_workspace_media"), false);
86 assert.equal(route("reasonix://app/__shell/quit").kind, "notFound");
87 });
88
89 test("the dist root honours the override, then packaging layout", () => {
90 assert.equal(resolveDistRoot({ env: { REASONIX_FRONTEND_DIST: "/tmp/dist" }, appPath: "/app/electron", resourcesPath: "/res", packaged: true }), "/tmp/dist");
91 assert.equal(resolveDistRoot({ env: {}, appPath: "/app/electron", resourcesPath: "/res", packaged: false }), "/app/frontend/dist");
92 assert.equal(resolveDistRoot({ env: {}, appPath: "/app/electron", resourcesPath: "/res", packaged: true }), "/res/app");
93 });
94
94 lines TYPESCRIPT