diff --git a/src/config/platforms.js b/src/config/platforms.js index 3371717..8334199 100644 --- a/src/config/platforms.js +++ b/src/config/platforms.js @@ -72,6 +72,7 @@ export const PLATFORMS = { 'ip-hyperbolic': 'https://api.hyperbolic.xyz', // Container Registries + 'cr-docker': 'https://registry-1.docker.io', 'cr-quay': 'https://quay.io', 'cr-gcr': 'https://gcr.io', 'cr-mcr': 'https://mcr.microsoft.com', diff --git a/src/index.js b/src/index.js index bb321e6..064931d 100644 --- a/src/index.js +++ b/src/index.js @@ -595,7 +595,14 @@ async function handleRequest(request, env, ctx) { const repoPath = pathParts.slice(4).join('/'); // Skip /v2/cr/[registry] const repoParts = repoPath.split('/'); if (repoParts.length >= 1) { - const repoName = repoParts.slice(0, -2).join('/'); // Remove /manifests/tag or /blobs/sha + let repoName = repoParts.slice(0, -2).join('/'); // Remove /manifests/tag or /blobs/sha + + // Special handling for Docker Hub: official images need 'library/' prefix + // Docker Hub stores official images like nginx, redis, etc. as library/nginx, library/redis + if (platform === 'cr-docker' && repoName && !repoName.includes('/')) { + repoName = `library/${repoName}`; + } + if (repoName) { scope = `repository:${repoName}:pull`; } diff --git a/test/container-registry.test.js b/test/container-registry.test.js index 01d5acd..fcd8ea7 100644 --- a/test/container-registry.test.js +++ b/test/container-registry.test.js @@ -207,6 +207,7 @@ describe('Container Registry Support', () => { describe('Container Registry Platform Support', () => { const containerRegistries = [ + { name: 'Docker Hub', prefix: 'cr/docker', expectedStatus: [200, 301, 302, 401, 404] }, { name: 'Quay.io', prefix: 'cr/quay', expectedStatus: [200, 301, 302, 401, 404] }, { name: 'Google Container Registry', @@ -235,4 +236,60 @@ describe('Container Registry Support', () => { }); }); }); + + describe('Docker Hub Specific Tests', () => { + it('should handle Docker Hub official images (single-name images)', async () => { + // Official images like nginx, redis are stored as library/nginx in Docker Hub + const testUrl = 'https://example.com/cr/docker/v2/nginx/manifests/latest'; + const response = await SELF.fetch(testUrl, { + headers: { + Accept: 'application/vnd.docker.distribution.manifest.v2+json' + } + }); + + // Should attempt to proxy to Docker Hub + expect(response.status).not.toBe(400); + }); + + it('should handle Docker Hub user images (namespace/image format)', async () => { + // User images already have namespace prefix + const testUrl = 'https://example.com/cr/docker/v2/nginxinc/nginx-unprivileged/manifests/latest'; + const response = await SELF.fetch(testUrl, { + headers: { + Accept: 'application/vnd.docker.distribution.manifest.v2+json' + } + }); + + // Should attempt to proxy to Docker Hub + expect(response.status).not.toBe(400); + }); + + it('should allow GET for Docker Hub manifest requests', async () => { + const response = await SELF.fetch( + 'https://example.com/cr/docker/v2/nginx/manifests/latest', + { + method: 'GET', + headers: { + Accept: 'application/vnd.docker.distribution.manifest.v2+json' + } + } + ); + + expect(response.status).not.toBe(405); + }); + + it('should allow HEAD for Docker Hub manifest requests', async () => { + const response = await SELF.fetch( + 'https://example.com/cr/docker/v2/nginx/manifests/latest', + { + method: 'HEAD', + headers: { + Accept: 'application/vnd.docker.distribution.manifest.v2+json' + } + } + ); + + expect(response.status).not.toBe(405); + }); + }); });