mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(setup): validate Hopper download channel URLs
This commit is contained in:
1 parent
25b69bb803
commit
fef83241b1
3 files changed
+58
-5
No files matched your search
@@ -121,3 +121,35 @@ it("distinguishes malformed UTF-8 from a genuine replacement character and toler
|
||||
parseHopperReleaseJson(new TextEncoder().encode(bom + '{"x":"雪�"}')),
|
||||
).toMatchObject({ ok: true, value: { x: "雪�" } });
|
||||
});
|
||||
|
||||
describe("macOS Hopper public download channel", () => {
|
||||
const macRelease = {
|
||||
filename: "https://www.hopperapp.com:443/downloader/public/Hopper-demo.dmg",
|
||||
file_length: "123",
|
||||
file_hash: "a".repeat(40),
|
||||
};
|
||||
|
||||
it.each([
|
||||
"https://www.hopperapp.com/downloader/public/Hopper-demo.dmg",
|
||||
"https://WWW.HOPPERAPP.COM:443/downloader/public/Hopper-demo.dmg",
|
||||
"https://www.hopperapp.com:443/downloader/public/Hopper%20demo.dmg",
|
||||
])("preserves the admitted source spelling %s", (filename) => {
|
||||
expect(
|
||||
parseMacHopperReleases({ "OS X": { ...macRelease, filename } }),
|
||||
).toEqual({ ok: true, value: { ...macRelease, filename } });
|
||||
});
|
||||
|
||||
it.each([
|
||||
"https://www.hopperapp.com:443/downloader/public/../private/Hopper-demo.dmg",
|
||||
"https://www.hopperapp.com:443/downloader/public/%2e%2e/private/Hopper-demo.dmg",
|
||||
"https://www.hopperapp.com:443/downloader/public/Hopper-demo.dmg?",
|
||||
"https://www.hopperapp.com:443/downloader/public/Hopper-demo.dmg#",
|
||||
"https://user:[email protected]:443/downloader/public/Hopper-demo.dmg",
|
||||
"https://other.example/downloader/public/Hopper-demo.dmg",
|
||||
"http://www.hopperapp.com/downloader/public/Hopper-demo.dmg",
|
||||
])("rejects a URL outside the public channel: %s", (filename) => {
|
||||
expect(
|
||||
parseMacHopperReleases({ "OS X": { ...macRelease, filename } }),
|
||||
).toMatchObject({ ok: false, error: { reason: "release_metadata" } });
|
||||
});
|
||||
});
|
||||
@@ -52,6 +52,14 @@ const selectedPlatformsSchema = z.object({
|
||||
Arch: z.unknown().optional(),
|
||||
});
|
||||
|
||||
/** Interpret channel identity once using the same URL semantics as the downloader. */
|
||||
const isPublicHopperDownload = (url: URL): boolean =>
|
||||
url.origin === "https://www.hopperapp.com" &&
|
||||
url.username === "" &&
|
||||
url.password === "" &&
|
||||
!urlDeclaresQueryOrFragment(url) &&
|
||||
url.pathname.startsWith("/downloader/public/");
|
||||
|
||||
/** Admit the selected macOS row without projecting away vendor release metadata. */
|
||||
export const parseMacHopperReleases = (
|
||||
raw: unknown,
|
||||
@@ -68,7 +76,7 @@ export const parseMacHopperReleases = (
|
||||
),
|
||||
);
|
||||
const retained = release.data as MacHopperRelease;
|
||||
if (!retained.filename.startsWith(HOPPER_DOWNLOAD_PREFIX))
|
||||
if (!isPublicHopperDownload(new URL(retained.filename)))
|
||||
return err(
|
||||
new HopperReleaseError(
|
||||
"release_metadata",
|
||||
@@ -154,10 +162,7 @@ export const parseLinuxHopperRelease = (
|
||||
);
|
||||
const url = new URL(parsed.data.filename);
|
||||
if (
|
||||
url.origin !== "https://www.hopperapp.com" ||
|
||||
url.username !== "" ||
|
||||
url.password !== "" ||
|
||||
urlDeclaresQueryOrFragment(url) ||
|
||||
!isPublicHopperDownload(url) ||
|
||||
!url.pathname.startsWith("/downloader/public/Hopper-") ||
|
||||
!url.pathname.endsWith(`-Linux-demo${suffixes[family]}`)
|
||||
)
|
||||
|
||||
@@ -307,3 +307,19 @@ it.each([false, true])(
|
||||
expect(host.installs).toBe(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("refuses release URLs that normalize outside the public channel before package download", async () => {
|
||||
const host = new FakeMacHopperHost();
|
||||
const feed = await host.download("files-api");
|
||||
const row = JSON.parse(new TextDecoder().decode(feed.bytes));
|
||||
row["OS X"].filename =
|
||||
"https://www.hopperapp.com:443/downloader/public/%2e%2e/private/Hopper-demo.dmg";
|
||||
host.feedBytes = new TextEncoder().encode(JSON.stringify(row));
|
||||
|
||||
expect(await installMacHopper({}, host)).toMatchObject({
|
||||
status: "failed",
|
||||
reason: "release_metadata",
|
||||
});
|
||||
expect(host.packageDownloads).toBe(0);
|
||||
expect(host.mounts).toBe(0);
|
||||
});
|
||||
Reference in new issue
Block a user