Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/winbindex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,14 @@ export async function streamFromSymbolServer(

const digest = hash.digest("hex");
if (digest !== expectedSha1.toLowerCase()) {
if (!wrote) {
// Nothing reached the client (e.g. an empty 200 body): the response
// is still pristine, so fall back to MinIO rather than fail it.
console.warn(
`Winbindex: SHA-1 mismatch for ${name} before any byte was sent (expected ${expectedSha1.toLowerCase()}, got ${digest}), falling back`,
);
return "not_available";
}
console.warn(
`Winbindex: SHA-1 mismatch for ${name} (expected ${expectedSha1.toLowerCase()}, got ${digest}), destroying response`,
);
Expand Down
30 changes: 30 additions & 0 deletions tests/winbindex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ const streamOf = (data: Uint8Array): ReadableStream<Uint8Array> =>
},
});

/** A 200-body stream that closes without ever yielding a chunk. */
const emptyStream = (): ReadableStream<Uint8Array> =>
new ReadableStream({
start(controller) {
controller.close();
},
});

/** A stream that yields `data` then errors, i.e. fails *after* the first byte. */
const streamThenError = (data: Uint8Array): ReadableStream<Uint8Array> => {
let sent = false;
Expand Down Expand Up @@ -526,6 +534,28 @@ describe("tryServeFromWinbindex", () => {
expect(console.warn).toHaveBeenCalled();
});

it("falls back to MinIO on a SHA-1 mismatch when no byte was sent (empty 200 body)", async () => {
const requestedHash = sha1Hex(bytes("a real, non-empty PE file"));
fetchMock.mockImplementation(async (input: unknown) =>
String(input).includes(".json.gz")
? jsonIndexResponse(entryIndex(requestedHash))
: symbolResponse(emptyStream()),
);

const res = makeMockRes();
const outcome = await tryServeFromWinbindex(
CONFIG,
requestedHash,
"kernel32.dll",
res,
);

expect(outcome).toBe("not_available");
expect(res.destroyed).toBe(false);
expect(res.body).toHaveLength(0);
expect(res.headers).toEqual({});
});

it.each(["a?b.dll", "x#y.dll", "mal ware.dll", "%2e%2e.dll", "café.dll"])(
"returns not_available without fetching for an unsafe filename %s",
async (unsafe) => {
Expand Down
Loading