Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/proxy/routes/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand All @@ -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.
*/
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/proxy/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
7 changes: 7 additions & 0 deletions test/extractRawBody.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
15 changes: 15 additions & 0 deletions test/testProxyRoute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
19 changes: 19 additions & 0 deletions test/testRouteFilter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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://',
Expand Down
Loading