feat: release 2.7.0-rc2
This commit is contained in:
+75
-9
@@ -10,6 +10,13 @@ const ACCEPT = [
|
||||
"application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"application/vnd.docker.distribution.manifest.v2+json",
|
||||
].join(", ");
|
||||
const MAX_MANIFEST_BYTES = 4 * 1024 * 1024;
|
||||
const MANIFEST_MEDIA_TYPES = new Set([
|
||||
"application/vnd.oci.image.index.v1+json",
|
||||
"application/vnd.oci.image.manifest.v1+json",
|
||||
"application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
"application/vnd.docker.distribution.manifest.v2+json",
|
||||
]);
|
||||
|
||||
export type RegistryCredentials = { username: string; password: string };
|
||||
export type RegistryFetch = (
|
||||
@@ -103,10 +110,67 @@ function bearerParameters(
|
||||
}
|
||||
|
||||
async function responseError(response: Response): Promise<Error> {
|
||||
const detail = (await response.text()).slice(0, 512).trim();
|
||||
return new Error(
|
||||
`Registry request failed (${response.status})${detail ? `: ${detail}` : ""}`,
|
||||
);
|
||||
await response.body?.cancel().catch(() => {});
|
||||
return new Error(`Registry request failed (${response.status})`);
|
||||
}
|
||||
|
||||
async function readManifestBody(response: Response): Promise<Uint8Array> {
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) throw new Error("Registry returned an invalid manifest");
|
||||
const chunks: Uint8Array[] = [];
|
||||
let length = 0;
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
length += value.byteLength;
|
||||
if (length > MAX_MANIFEST_BYTES) {
|
||||
await reader.cancel();
|
||||
throw new Error("Registry manifest exceeds the size limit");
|
||||
}
|
||||
chunks.push(value);
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
const body = new Uint8Array(length);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
body.set(chunk, offset);
|
||||
offset += chunk.byteLength;
|
||||
}
|
||||
let manifest: unknown;
|
||||
try {
|
||||
manifest = JSON.parse(
|
||||
new TextDecoder("utf-8", { fatal: true }).decode(body),
|
||||
);
|
||||
} catch {
|
||||
throw new Error("Registry returned an invalid manifest");
|
||||
}
|
||||
if (
|
||||
!manifest ||
|
||||
typeof manifest !== "object" ||
|
||||
Array.isArray(manifest) ||
|
||||
(manifest as { schemaVersion?: unknown }).schemaVersion !== 2
|
||||
)
|
||||
throw new Error("Registry returned an invalid manifest");
|
||||
const record = manifest as Record<string, unknown>;
|
||||
const mediaType = record.mediaType;
|
||||
if (typeof mediaType !== "string" || !MANIFEST_MEDIA_TYPES.has(mediaType))
|
||||
throw new Error("Registry returned an invalid manifest");
|
||||
const isIndex =
|
||||
mediaType.endsWith("image.index.v1+json") ||
|
||||
mediaType.endsWith("manifest.list.v2+json");
|
||||
if (
|
||||
isIndex
|
||||
? !Array.isArray(record.manifests)
|
||||
: !record.config ||
|
||||
typeof record.config !== "object" ||
|
||||
Array.isArray(record.config) ||
|
||||
!Array.isArray(record.layers)
|
||||
)
|
||||
throw new Error("Registry returned an invalid manifest");
|
||||
return body;
|
||||
}
|
||||
|
||||
export async function resolveRegistryDigest(
|
||||
@@ -191,17 +255,19 @@ export async function resolveRegistryDigest(
|
||||
}
|
||||
if (!response.ok) throw await responseError(response);
|
||||
|
||||
const body = new Uint8Array(await response.arrayBuffer());
|
||||
const body = await readManifestBody(response);
|
||||
const advertised = response.headers
|
||||
.get("docker-content-digest")
|
||||
?.trim()
|
||||
?.toLowerCase();
|
||||
let digest: Sha256Digest;
|
||||
const digest =
|
||||
`sha256:${createHash("sha256").update(body).digest("hex")}` as Sha256Digest;
|
||||
if (advertised !== undefined) {
|
||||
assertSha256Digest(advertised);
|
||||
digest = advertised;
|
||||
} else {
|
||||
digest = `sha256:${createHash("sha256").update(body).digest("hex")}`;
|
||||
if (advertised !== digest)
|
||||
throw new Error(
|
||||
"Registry manifest digest does not match Docker-Content-Digest",
|
||||
);
|
||||
}
|
||||
if (cacheTtlMs && cacheMaxEntries) {
|
||||
digestCache.delete(cacheKey);
|
||||
|
||||
Reference in New Issue
Block a user