diff --git a/build.zig.zon b/build.zig.zon index 7034bf3..6d2d5c6 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -14,8 +14,8 @@ .hash = "httpz-0.0.0-PNVzrBtMBwAPcQx3mNEgat3Xbsynw-eIC9SmOX5M9XtP", }, .zfin = .{ - .url = "git+https://git.lerch.org/lobo/zfin#0d3dfc6a559c5957b3ddd556a3ad02aa700c7e40", - .hash = "zfin-0.0.0-J-B21uSMGwB3Pc_aOIfSQew8PLMgdCv0kDC9PDOaFC0t", + .url = "git+https://git.lerch.org/lobo/zfin#f9c7fa99e4d30ac613c45572bbe1c45cd3767b3d", + .hash = "zfin-0.0.0-J-B21u7_IgD0BHz6KGXpZ4sWj7aIOHwcTKCOByLAUYKJ", }, }, } diff --git a/src/main.zig b/src/main.zig index 07677b2..318315a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -291,8 +291,23 @@ fn handleSrfFile(app: *App, req: *httpz.Request, res: *httpz.Response, filename: return; }; + // Body integrity header: sha256 of the bytes we're about to send. + // Clients can use this to detect mid-stream truncation that Zig's + // std.http.Client.fetch silently accepts on the Content-Length path + // (a premature EOF from the transport bubbles up as EndOfStream and + // is swallowed as a normal end-of-body). Shaped as a standard + // `ETag` value so future conditional-request work gets it for free. + var hash: [std.crypto.hash.sha2.Sha256.digest_length]u8 = undefined; + std.crypto.hash.sha2.Sha256.hash(content, &hash, .{}); + var etag_buf: [std.crypto.hash.sha2.Sha256.digest_length * 2 + "\"sha256:\"".len]u8 = undefined; + const etag = try std.fmt.bufPrint(&etag_buf, "\"sha256:{x}\"", .{&hash}); + // httpz.Response.header borrows the value — duplicate into the + // per-request arena so the slice outlives `etag_buf`. + const etag_owned = try arena.dupe(u8, etag); + res.content_type = httpz.ContentType.BINARY; res.header("content-type", "application/x-srf"); + res.header("etag", etag_owned); res.body = content; }