From 20baee245470cba59d6c26d7ebd082d6691f33b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Ga=C5=A1pari=C4=87?= <56818232+codedoga@users.noreply.github.com> Date: Sun, 2 Nov 2025 12:09:37 +0100 Subject: [PATCH 1/2] Refactor response headers in router.ts Refactor response `Link Header` to be compatible with RFC-5988 paragraph 5. --- src/router.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/router.ts b/src/router.ts index aa000ca..8c4f0e2 100644 --- a/src/router.ts +++ b/src/router.ts @@ -34,17 +34,23 @@ v2Router.get("/_catalog", async (req, env: Env) => { if ("response" in response) { return response.response; } - const url = new URL(req.url); + const headers: Record = { + "Content-Type": "application/json", + }; + + if (response.cursor) { + const cleanCursor = response.cursor.split("/manifests/")[0]; + const linkValue = `<${url.protocol}//${url.hostname}${url.pathname}?n=${n ?? 1000}&last=${cleanCursor}>; rel="next"`; + headers["Link"] = linkValue; + } + return new Response( JSON.stringify({ repositories: response.repositories, }), { - headers: { - "Link": `${url.protocol}//${url.hostname}${url.pathname}?n=${n ?? 1000}&last=${response.cursor ?? ""}; rel=next`, - "Content-Type": "application/json", - }, + headers, }, ); }); From b8a829ae25f425ef3a1ef5a81d1d141830650c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Ga=C5=A1pari=C4=87?= <56818232+codedoga@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:58:56 +0100 Subject: [PATCH 2/2] Refactor response headers in router.ts 2 --- src/router.ts | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/src/router.ts b/src/router.ts index 8c4f0e2..dc6d425 100644 --- a/src/router.ts +++ b/src/router.ts @@ -34,23 +34,17 @@ v2Router.get("/_catalog", async (req, env: Env) => { if ("response" in response) { return response.response; } - const url = new URL(req.url); - const headers: Record = { - "Content-Type": "application/json", - }; - - if (response.cursor) { - const cleanCursor = response.cursor.split("/manifests/")[0]; - const linkValue = `<${url.protocol}//${url.hostname}${url.pathname}?n=${n ?? 1000}&last=${cleanCursor}>; rel="next"`; - headers["Link"] = linkValue; - } + const url = new URL(req.url); return new Response( JSON.stringify({ repositories: response.repositories, }), { - headers, + headers: { + "Link": `${url.protocol}//${url.hostname}${url.pathname}?n=${n ?? 1000}&last=${response.cursor ?? ""}; rel=next`, + "Content-Type": "application/json", + }, }, ); }); @@ -93,22 +87,23 @@ v2Router.delete("/:name+/manifests/:reference", async (req, env: Env) => { const url = new URL(req.url); if (tags.truncated) { - url.searchParams.set("last", tags.truncated ? tags.cursor : ""); + url.searchParams.set("last", tags.cursor); + const headers: Record = { + "Content-Type": "application/json", + }; + const linkValue = `<${url.toString()}>; rel="next"`; + headers["Link"] = linkValue; return new Response(JSON.stringify(ManifestTagsListTooBigError), { status: 400, - headers: { - "Link": `${url.toString()}; rel=next`, - "Content-Type": "application/json", - }, + headers, }); } - // Last but not least, delete the digest manifest await env.REGISTRY.delete(`${name}/manifests/${reference}`); return new Response("", { status: 202, headers: { - "Content-Length": "None", + "Content-Length": "0", }, }); }); @@ -582,12 +577,13 @@ v2Router.get("/:name+/tags/list", async (req, env: Env) => { const url = new URL(req.url); url.searchParams.set("n", `${n}`); url.searchParams.set("last", keys.length ? keys[keys.length - 1] : ""); - const responseHeaders: { "Content-Type": string; "Link"?: string } = { + const responseHeaders: Record = { "Content-Type": "application/json", }; // Only supply a next link if the previous result is truncated if (tags.truncated) { - responseHeaders.Link = `${url.toString()}; rel=next`; + const linkValue = `<${url.toString()}>; rel="next"`; + responseHeaders["Link"] = linkValue; } return new Response( JSON.stringify({