diff --git a/src/proxy/routes/helper.ts b/src/proxy/routes/helper.ts index d715fb89e..dffddf53b 100644 --- a/src/proxy/routes/helper.ts +++ b/src/proxy/routes/helper.ts @@ -68,6 +68,9 @@ export const processGitUrl = (url: string): GitUrlBreakdown | null => { * into the embedded git end point and path for the git operation. */ const PROXIED_URL_PATH_REGEX = /(.+\.git)(\/.*)?/; +/** Fallback for smart-HTTP paths that omit the .git repo suffix (e.g. GitHub-style URLs). */ +const SMART_HTTP_PATH_REGEX = /^(.+?)(\/(?:info\/refs|git-upload-pack|git-receive-pack)(?:\?.*)?)$/; + /** Type representing a breakdown of paths requested from the proxy server */ export type UrlPathBreakdown = { repoPath: string; gitPath: string }; @@ -85,6 +88,11 @@ export type UrlPathBreakdown = { repoPath: string; gitPath: string }; * - repoPath: /github.com/finos/git-proxy.git * - gitPath: /info/refs?service=git-upload-pack * + * Processing /github.com/finos/git-proxy/info/refs?service=git-upload-pack (no .git suffix) + * would produce: + * - repoPath: /github.com/finos/git-proxy.git + * - gitPath: /info/refs?service=git-upload-pack + * * @param {string} requestPath The URL path to process. * @return {GitUrlBreakdown | null} A breakdown of the components of the URL path. */ @@ -100,10 +108,18 @@ export const processUrlPath = (requestPath: string): UrlPathBreakdown | null => repoPath: components[1], gitPath: components[2] ?? '/', }; - } else { - console.error(`Failed to parse proxy url path: ${requestPath}`); - return null; } + + const smartHttp = requestPath.match(SMART_HTTP_PATH_REGEX); + if (smartHttp) { + return { + repoPath: smartHttp[1] + '.git', + gitPath: smartHttp[2], + }; + } + + console.error(`Failed to parse proxy url path: ${requestPath}`); + return null; }; /** Regex used to analyze repo URLs (with protocol and origin) to extract the repository name and diff --git a/src/proxy/routes/index.ts b/src/proxy/routes/index.ts index e6babc385..f1af8f74e 100644 --- a/src/proxy/routes/index.ts +++ b/src/proxy/routes/index.ts @@ -281,7 +281,7 @@ const proxyErrorHandler: ProxyOptions['proxyErrorHandler'] = (err, _res, next) = const isPackPost = (req: Request) => req.method === 'POST' && - /^(?:\/[^/]+)*\/[^/]+\.git\/(?:git-upload-pack|git-receive-pack)$/.test(req.url); + /^(?:\/[^/]+)*\/[^/]+(?:\.git)?\/(?:git-upload-pack|git-receive-pack)$/.test(req.url); const extractRawBody = async (req: Request, res: Response, next: NextFunction) => { if (!isPackPost(req)) { diff --git a/test/extractRawBody.test.ts b/test/extractRawBody.test.ts index d86790c62..d654b93bd 100644 --- a/test/extractRawBody.test.ts +++ b/test/extractRawBody.test.ts @@ -91,6 +91,13 @@ describe('isPackPost()', () => { expect(isPackPost({ method: 'POST', url: '/a.git/git-upload-pack' } as Request)).toBe(true); }); + it('returns true for git-upload-pack POST without a .git repo suffix', () => { + expect(isPackPost({ method: 'POST', url: '/a/b/git-upload-pack' } as Request)).toBe(true); + expect( + isPackPost({ method: 'POST', url: '/github.com/org/repo/git-receive-pack' } as Request), + ).toBe(true); + }); + it('returns false for other URLs', () => { expect(isPackPost({ method: 'POST', url: '/info/refs' } as Request)).toBe(false); }); diff --git a/test/testProxyRoute.test.ts b/test/testProxyRoute.test.ts index 32f720015..58c5bc8a4 100644 --- a/test/testProxyRoute.test.ts +++ b/test/testProxyRoute.test.ts @@ -392,6 +392,21 @@ describe('proxyFilter', () => { expect(mockReq.body).toEqual(Buffer.from('test data')); expect((mockReq as any).bodyRaw).toBeUndefined(); }); + + it('should allow GET info/refs for a .git-less smart-HTTP path without mocking processUrlPath', async () => { + mockReq.url = '/github.com/finos/git-proxy/info/refs?service=git-upload-pack'; + mockReq.method = 'GET'; + + vi.spyOn(chain, 'executeChain').mockResolvedValue({ + error: false, + blocked: false, + } as Action); + + const result = await proxyFilter?.(mockReq as Request, mockRes as Response); + + expect(result).toBe(true); + expect(sendMock).not.toHaveBeenCalled(); + }); }); describe('Invalid requests', () => { diff --git a/test/testRouteFilter.test.ts b/test/testRouteFilter.test.ts index 159783ec9..201309fb5 100644 --- a/test/testRouteFilter.test.ts +++ b/test/testRouteFilter.test.ts @@ -75,6 +75,25 @@ describe('url helpers and filter functions used in the proxy', () => { expect(processUrlPath(VERY_LONG_PATH)).toBeNull(); }); + it('processUrlPath should parse smart-HTTP paths without a .git repo suffix', () => { + expect(processUrlPath('/octocat/hello-world/info/refs?service=git-upload-pack')).toEqual({ + repoPath: '/octocat/hello-world.git', + gitPath: '/info/refs?service=git-upload-pack', + }); + + expect( + processUrlPath('/github.com/octocat/hello-world/info/refs?service=git-upload-pack'), + ).toEqual({ + repoPath: '/github.com/octocat/hello-world.git', + gitPath: '/info/refs?service=git-upload-pack', + }); + + expect(processUrlPath('/org/owner/repo/git-upload-pack')).toEqual({ + repoPath: '/org/owner/repo.git', + gitPath: '/git-upload-pack', + }); + }); + it('processGitUrl should return breakdown of a git URL separating out the protocol, host and repository path', () => { expect(processGitUrl('https://somegithost.com/octocat/hello-world.git')).toEqual({ protocol: 'https://',