- commit
- 799f014549985817ef4434f4c9bf3ca20b072d10
- parent
- df3cd487ef72e9bf8ce0fabe1b0c7770ef806560
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 3 | ++- |
| M | src/ | 6 | +++--- |
| M | test/ | 1 | + |
| M | test/ | 2 | ++ |
@@ -37,7 +37,8 @@ selects `/blog/index.html`. A missing path selects the site's `/404.html` when
3737 present and otherwise returns 404. A missing manifest, missing file, hash3838 mismatch, unavailable blob, or failed remote fetch returns a plain 404.3939 Responses forward the stored content type and length, use the blob hash as the ETag,40-and verify the blob before serving it.40+and verify the blob before serving it. `Cache-Control: no-transform` keeps41+intermediaries from changing the verified representation or its validators.4142 4243 The relay's Rules > Reads setting applies to sites. Open reads make a site4344 public; signed-in and members-only reads require the same admission as the@@ -192,9 +192,9 @@ export async function serveSite(relay: Relay, req: Request, label: string): Prom
192192 if (!featureOn(relay.settings.policy, "sites") || blobBlocked(relay, sha) || manifest(relay, site)?.id !== e.id || relay.settings.mayRead(who.pubkeys)) return siteError(req, 404, "Not found");193193 const headers = new Headers({194194 etag: `"${sha}"`,195- // Revalidate every request so policy changes and expiry cannot be196- // bypassed by a fresh cache entry, including on snapshot URLs.197- "cache-control": relay.settings.policy.reads === "open" ? "public, no-cache, must-revalidate" : "private, no-store",195+ // Revalidation respects policy changes and expiry, including snapshots.196+ // Edge transforms must preserve the bytes named by the signed manifest.197+ "cache-control": relay.settings.policy.reads === "open" ? "public, no-cache, must-revalidate, no-transform" : "private, no-store, no-transform",198198 "x-content-type-options": "nosniff", "referrer-policy": "same-origin",199199 });200200 if (obj && blob) {@@ -100,6 +100,7 @@ describe("NIP-5A static websites", () => {
100100 expect(response.headers.get("content-type")).toContain("text/html");101101 expect(response.headers.get("etag")).toBe(`"${indexHash}"`);102102 expect(response.headers.get("cache-control")).toContain("public");103+ expect(response.headers.get("cache-control")).toContain("no-transform");103104 const head = await siteFetch(host, "/index.html", { method: "HEAD" });104105 expect(head.status).toBe(200);105106 expect(head.headers.get("content-length")).toBe(String(new TextEncoder().encode(index).length));@@ -100,6 +100,7 @@ describe("sites", () => {
100100 expect(res.headers.get("content-type")).toBe("text/plain");101101 expect(res.headers.get("content-length")).toBe("10");102102 expect(res.headers.get("etag")).toBe('"' + file.sha + '"');103+ expect(res.headers.get("cache-control")).toContain("no-transform");103104 const head = await SELF.fetch(url + "/", { method: "HEAD" });104105 expect(head.status).toBe(200); expect(await head.text()).toBe("");105106 expect((await SELF.fetch(url + "/", { headers: { "if-none-match": res.headers.get("etag")! } })).status).toBe(304);@@ -117,6 +118,7 @@ describe("sites", () => {
117118 expect((await SELF.fetch(url, { headers: { authorization: await nip98(generateSecretKey(), url) } })).status).toBe(403);118119 const member = await SELF.fetch(url, { headers: { authorization: await nip98(sk, url) } });119120 expect(member.status).toBe(200); expect(member.headers.get("cache-control")).toContain("no-store");121+ expect(member.headers.get("cache-control")).toContain("no-transform");120122 await rpc(host, sk, "setpolicy", { reads: "open", features: { sites: false } });121123 expect((await SELF.fetch(url)).status).toBe(404); expect((await info(host)).nsites).toBeUndefined();122124 await rpc(host, sk, "setpolicy", { features: { sites: true } });