Skip to content

Commit bffbecf

Browse files
committed
todo
1 parent 3e532ba commit bffbecf

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

src/registry/r2.ts

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from "../chunk";
1212
import { InternalError, ManifestError, RangeError, ServerError } from "../errors";
1313
import { SHA256_PREFIX_LEN, getSHA256, hexToDigest } from "../user";
14-
import { readableToBlob, readerToBlob, wrap } from "../utils";
14+
import { errorString, readableToBlob, readerToBlob, wrap } from "../utils";
1515
import { BlobUnknownError, ManifestUnknownError } from "../v2-errors";
1616
import {
1717
CheckLayerResponse,
@@ -29,6 +29,7 @@ import {
2929
} from "./registry";
3030
import { GarbageCollectionMode, GarbageCollector } from "./garbage-collector";
3131
import { ManifestSchema, manifestSchema } from "../manifest";
32+
import { DigestInvalid, RegistryResponseJSON } from "../v2-responses";
3233

3334
export type Chunk =
3435
| {
@@ -691,12 +692,27 @@ export class R2Registry implements Registry {
691692
// TODO: Handle one last buffer here
692693
await upload.complete(state.parts);
693694
const obj = await this.env.REGISTRY.get(uuid);
694-
const put = this.env.REGISTRY.put(`${namespace}/blobs/${expectedSha}`, obj!.body, {
695-
sha256: (expectedSha as string).slice(SHA256_PREFIX_LEN),
696-
});
695+
if (!obj) throw new Error("internal error, upload not found just after completing");
696+
if (obj.size >= 2 * 1000 * 1000) {
697+
const digestStream = new crypto.DigestStream("SHA-256");
698+
await obj.body.pipeTo(digestStream);
699+
const digestHex = await digestStream.digest;
700+
const digest = hexToDigest(digestHex);
701+
if (digest !== expectedSha) {
702+
return { response: new RegistryResponseJSON(JSON.stringify(DigestInvalid(expectedSha, digest))) };
703+
}
704+
} else {
705+
const put = this.env.REGISTRY.put(`${namespace}/blobs/${expectedSha}`, obj!.body, {
706+
sha256: (expectedSha as string).slice(SHA256_PREFIX_LEN),
707+
});
697708

698-
await put;
699-
await this.env.REGISTRY.delete(uuid);
709+
const [, err] = await wrap<unknown, Error>(put);
710+
if (err !== null) {
711+
return { response: handleR2PutError(expectedSha, err) };
712+
}
713+
714+
await this.env.REGISTRY.delete(uuid);
715+
}
700716
}
701717

702718
await this.env.REGISTRY.delete(getRegistryUploadsPath(state));
@@ -752,3 +768,28 @@ export class R2Registry implements Registry {
752768
return result;
753769
}
754770
}
771+
772+
function handleR2PutError(expectedSha: string, err: unknown): Response {
773+
if (err instanceof Error) {
774+
// this is very annoying, parsing the error message manually to know the error and retrieve the calculated hash...
775+
if (
776+
err instanceof Error &&
777+
err.message.includes("The SHA-256 checksum you specified did not match what we received")
778+
) {
779+
const actualHashWasMessage = "Actual SHA-256 was: ";
780+
const digestIndex = err.message.indexOf(actualHashWasMessage);
781+
const errorCodeIndex = err.message.indexOf(" (10037)");
782+
let hash = "";
783+
if (digestIndex !== -1 && errorCodeIndex !== -1 && digestIndex < errorCodeIndex) {
784+
hash = "sha256:" + err.message.slice(actualHashWasMessage.length + digestIndex, errorCodeIndex);
785+
}
786+
787+
return new RegistryResponseJSON(JSON.stringify(DigestInvalid(hash, expectedSha)), {
788+
status: 400,
789+
});
790+
}
791+
}
792+
793+
console.error(`There has been an internal error pushing ${expectedSha}: ${errorString(err)}`);
794+
return new RegistryResponseJSON(JSON.stringify({ error: "INTERNAL_ERROR" }));
795+
}

src/v2-responses.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,40 @@ export const ManifestTagsListTooBigError = {
77
},
88
],
99
};
10+
11+
export class RegistryResponse extends Response {
12+
constructor(body?: BodyInit | null, init?: ResponseInit) {
13+
super(body, {
14+
...init,
15+
headers: {
16+
...init?.headers,
17+
"Docker-Distribution-Api-Version": "registry/2.0",
18+
},
19+
});
20+
}
21+
}
22+
23+
export class RegistryResponseJSON extends RegistryResponse {
24+
constructor(body?: BodyInit | null, init?: ResponseInit) {
25+
super(body, {
26+
...init,
27+
headers: {
28+
...init?.headers,
29+
"Content-Type": "application/json",
30+
},
31+
});
32+
}
33+
}
34+
35+
export const DigestInvalid = (expected: string, got: string) => ({
36+
errors: [
37+
{
38+
code: "DIGEST_INVALID",
39+
message: "digests don't match",
40+
detail: {
41+
Expected: expected,
42+
Got: got,
43+
},
44+
},
45+
],
46+
});

0 commit comments

Comments
 (0)