Skip to content
60 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { contentDisposition, hardenRegistryHeaders, NOTHING_RUNS, opensAsDocument } from "./content-safety.ts";
5
6test("publisher documents a browser would open are downloads", () => {
7 for (const type of [
8 "application/xml",
9 "text/xml; charset=utf-8",
10 "application/xhtml+xml",
11 "text/html",
12 "image/svg+xml",
13 "application/vnd.example+xml",
14 "text/javascript",
15 "",
16 null,
17 ]) {
18 assert.equal(opensAsDocument(type), true, String(type));
19 }
20});
21
22test("data types stay inline", () => {
23 for (const type of [
24 "application/json",
25 "text/plain; charset=utf-8",
26 "application/vnd.oci.image.manifest.v1+json",
27 "application/vnd.npm.install-v1+json",
28 "application/octet-stream",
29 "application/java-archive",
30 "application/gzip",
31 "image/png",
32 ]) {
33 assert.equal(opensAsDocument(type), false, type);
34 }
35});
36
37test("every registry answer runs nothing and is never sniffed", () => {
38 const pom = new Headers({ "content-type": "application/xml" });
39 hardenRegistryHeaders(pom);
40 assert.equal(pom.get("x-content-type-options"), "nosniff");
41 assert.equal(pom.get("content-security-policy"), NOTHING_RUNS);
42 assert.equal(pom.get("content-disposition"), "attachment");
43
44 const json = new Headers({ "content-type": "application/json" });
45 hardenRegistryHeaders(json);
46 assert.equal(json.get("content-security-policy"), NOTHING_RUNS);
47 assert.equal(json.get("content-disposition"), null);
48
49 const named = new Headers({ "content-type": "text/html", "content-disposition": 'attachment; filename="a.html"' });
50 hardenRegistryHeaders(named);
51 assert.equal(named.get("content-disposition"), 'attachment; filename="a.html"');
52});
53
54test("download names keep quotes and control characters out of the header", () => {
55 assert.equal(contentDisposition("web-main.zip"), `attachment; filename="web-main.zip"; filename*=UTF-8''web-main.zip`);
56 const sly = contentDisposition('web-a"b\r\nSet-Cookie: x.zip');
57 assert.ok(!/[\r\n]/.test(sly));
58 assert.match(sly, /^attachment; filename="web-a_b__Set-Cookie: x.zip"; filename\*=UTF-8''web-a%22b__Set-Cookie%3A%20x.zip$/);
59 assert.match(contentDisposition("café.zip"), /filename="caf_.zip"; filename\*=UTF-8''caf%C3%A9.zip$/);
60});