Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion landing/app/open/fragment.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, test } from "vitest";
import { readFragment } from "./fragment";

const UUID = "3f2504e0-4f89-11d3-9a0c-0305e82c3301";
Expand Down Expand Up @@ -119,3 +119,36 @@ describe("the navigate routes", () => {
expect(readFragment("#billing")).toEqual({ url: "voltius://billing", code: null });
});
});

test("an invite fragment forwards a well-formed handle and rejects anything else", () => {
expect(readFragment("#invite?h=%40kevin-p")?.url).toBe("voltius://invite?h=%40kevin-p");
expect(readFragment("#invite?h=kevin-p")?.url).toBe("voltius://invite?h=kevin-p");
expect(readFragment("#invite?h=ab")).toBeNull();
expect(readFragment("#invite?h=-kevin")).toBeNull();
expect(readFragment("#invite")).toBeNull();
});

test("a plugin-install fragment forwards a valid id and rejects a path-escaping one", () => {
expect(readFragment("#plugin-install?id=docker&src=voltius")?.url).toBe(
"voltius://plugin-install?id=docker&src=voltius",
);
expect(readFragment("#plugin-install?id=docker")?.url).toBe("voltius://plugin-install?id=docker");
expect(readFragment("#plugin-install?id=../evil")).toBeNull();
expect(readFragment("#plugin-install?id=__meta__")).toBeNull();
expect(readFragment("#plugin-install?id=Docker")).toBeNull();
expect(readFragment("#plugin-install")).toBeNull();
});

test("a snippet-install fragment forwards a non-empty id and rejects an over-long one", () => {
expect(readFragment("#snippet-install?id=docker-cleanup")?.url).toBe(
"voltius://snippet-install?id=docker-cleanup",
);
expect(readFragment("#snippet-install?id=")).toBeNull();
expect(readFragment("#snippet-install?id=" + "a".repeat(101))).toBeNull();
});

test("no new route produces a paste-able code", () => {
expect(readFragment("#invite?h=kevin-p")?.code).toBeNull();
expect(readFragment("#plugin-install?id=docker")?.code).toBeNull();
expect(readFragment("#snippet-install?id=docker-cleanup")?.code).toBeNull();
});
32 changes: 32 additions & 0 deletions landing/app/open/fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ const SETTINGS_SECTIONS = new Set([
// Mirrors `MAX_ENTRY_ID` in voltius (src/services/deepLinkUrl.ts).
const MAX_ENTRY_ID = 200;

// Mirrors `HANDLE_RE` in voltius (src/services/deepLinkUrl.ts), itself the
// server's `validate_custom_handle` rule.
const HANDLE_RE = /^[a-z0-9][a-z0-9_-]{1,28}[a-z0-9]$/;

// Mirrors `isValidPluginId` in voltius (src/plugins/pluginId.ts). The leading
// alphanumeric is what keeps `__meta__` and `..` unclaimable, so it is not
// cosmetic — a plugin id becomes a directory name on the client.
const PLUGIN_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
const PLUGIN_ID_MAX_LENGTH = 64;

// Mirrors `MAX_SOURCE_ID` and `MAX_CATALOG_ID` in voltius (src/services/deepLinkUrl.ts).
const MAX_SOURCE_ID = 100;
const MAX_CATALOG_ID = 100;

const ROUTE_VALIDATORS: Record<string, (params: URLSearchParams) => boolean> = {
join: (params) => isSessionId(params.get("s") ?? "") && !!params.get("t"),
verified: (params) => isSessionId(params.get("u") ?? ""),
Expand All @@ -30,6 +44,24 @@ const ROUTE_VALIDATORS: Record<string, (params: URLSearchParams) => boolean> = {
notification: (params) => (params.get("n") ?? "").length <= MAX_ENTRY_ID,
settings: (params) => SETTINGS_SECTIONS.has(params.get("section") ?? ""),
billing: () => true,
invite: (params) => HANDLE_RE.test((params.get("h") ?? "").replace(/^@/, "").toLowerCase()),
// The source is a catalogue *id* the client resolves against its own configured
// sources, never a URL — so only its length is checked here.
"plugin-install": (params) => {
const id = params.get("id") ?? "";
return (
id.length > 0 &&
id.length <= PLUGIN_ID_MAX_LENGTH &&
PLUGIN_ID_RE.test(id) &&
(params.get("src") ?? "").length <= MAX_SOURCE_ID
);
},
// The catalogue id is opaque — the client fetches the catalogue at confirm time —
// so only its presence and length are checked.
"snippet-install": (params) => {
const id = params.get("id") ?? "";
return id.length > 0 && id.length <= MAX_CATALOG_ID;
},
};

export type Target = {
Expand Down
Loading