fix: derive file ETags from current source content

This commit is contained in:
tdurieux
2026-09-06 09:17:47 +02:00
parent c14a548cb9
commit 3e275311f1
3 changed files with 11 additions and 6 deletions
+3 -3
View File
@@ -1,17 +1,17 @@
import { createHash } from "crypto"; import { createHash } from "crypto";
// Build an ETag that fingerprints the upstream content (?v=<sha>), the file // Build an ETag that fingerprints the current upstream content (commit and blob SHA), the file
// path, and the anonymization config the user has saved. Without the config // path, and the anonymization config the user has saved. Without the config
// part the browser kept serving bytes anonymized under an older term list // part the browser kept serving bytes anonymized under an older term list
// (#439). The path is folded in so two different files inside the same repo // (#439). The path is folded in so two different files inside the same repo
// can never collide. // can never collide.
export function fileETag( export function fileETag(
versionParam: string | undefined, sourceFingerprint: string | undefined,
filePath: string, filePath: string,
options: unknown options: unknown
): string { ): string {
const h = createHash("sha1"); const h = createHash("sha1");
h.update(versionParam || ""); h.update(sourceFingerprint || "");
h.update("|"); h.update("|");
h.update(filePath || ""); h.update(filePath || "");
h.update("|"); h.update("|");
+1 -1
View File
@@ -132,7 +132,7 @@ router.get(
); );
} }
const etag = fileETag( const etag = fileETag(
req.query.v as string | undefined, `${repo.model.source.commit || ""}:${await f.sha() || ""}`,
anonymizedPath, anonymizedPath,
repo.model.options repo.model.options
); );
+7 -2
View File
@@ -142,10 +142,15 @@ describe("production regressions", function () {
await handler(req, response()); await handler(req, response());
expect(failure.message).to.equal("file_not_supported"); expect(failure.message).to.equal("file_not_supported");
}); });
it("sandboxes renamed HTML documents", async function () { it("sandboxes renamed HTML and invalidates stale client versions", async function () {
const { handler, req } = fileRoute("page.html"); const { handler, req, repo } = fileRoute("page.html");
const first = response(); await handler(req, first); const first = response(); await handler(req, first);
expect(first.headers["Content-Security-Policy"]).to.include("sandbox"); expect(first.headers["Content-Security-Policy"]).to.include("sandbox");
repo.model.source.commit = "commit-2";
req.headers["if-none-match"] = first.headers.ETag;
const second = response(); await handler(req, second);
expect(second.headers.ETag).not.to.equal(first.headers.ETag);
expect(second.statusCode).not.to.equal(304);
}); });
it("omits an upstream length when later text is rewritten", async function () { it("omits an upstream length when later text is rewritten", async function () {
const File = require("../src/core/AnonymizedFile").default; const File = require("../src/core/AnonymizedFile").default;