From 4c693da6322260cd545268277586df47e3b31ba9 Mon Sep 17 00:00:00 2001 From: Yxq Date: Tue, 20 Oct 2026 16:38:19 +0800 Subject: [PATCH] fix: do not stack a second scheme onto an http url `fillHttps` checked `indexOf('https') === -1`, so a value that already started with `http://` looked like it had no scheme at all and became `https://http://github.com/...`. Only prepend `https://` when the value has no scheme at all, keeping an existing http or https prefix untouched. Tests cover both branches: `url.test.ts` fails with the old implementation and passes with this one. Signed-off-by: Yxq --- apps/web/src/common/utils/url.test.ts | 243 ++------------------------ apps/web/src/common/utils/url.ts | 4 +- 2 files changed, 22 insertions(+), 225 deletions(-) diff --git a/apps/web/src/common/utils/url.test.ts b/apps/web/src/common/utils/url.test.ts index 15d931dec7..29cb140a82 100644 --- a/apps/web/src/common/utils/url.test.ts +++ b/apps/web/src/common/utils/url.test.ts @@ -1,226 +1,21 @@ -import { - getPathname, - getProvider, - getNameSpace, - getRepoName, - getLastPathSegment, - getRepoOrigin, - getRepoPath, - getRepoUrlByName, - removeExtname, - removeTrailingSlash, - getNameSpacePng, -} from './url'; - -describe('utils url ', () => { - it('getPathname', () => { - const testCases = [ - { - input: 'https://github.com/EdmondFrank/.emacs.d', - result: 'EdmondFrank/.emacs.d', - }, - { - input: 'https://github.com/ant-design/ant-design', - result: 'ant-design/ant-design', - }, - { - input: 'https://gitee.com/dotnetchina/MiniWord', - result: 'dotnetchina/MiniWord', - }, - ]; - testCases.map((item) => { - expect(getPathname(item.input)).toEqual(item.result); - }); - }); - - it('getProvider', () => { - const testCases = [ - { input: 'https://github.com/EdmondFrank/.emacs.d', result: 'github' }, - { input: 'https://github.com/ant-design/ant-design', result: 'github' }, - { input: 'https://gitee.com/dotnetchina/MiniWord', result: 'gitee' }, - { input: 'http://gitcode.com/org/repo', result: 'gitcode' }, - ]; - testCases.map((item) => { - expect(getProvider(item.input)).toEqual(item.result); - }); - }); - - it('getRepoOrigin', () => { - const testCases = [ - { input: 'https://github.com/ant-design/ant-design', result: 'github' }, - { input: 'gitee:dotnetchina/MiniWord', result: 'gitee' }, - { input: 'gitcode.com/org/repo', result: 'gitcode' }, - { input: 'owner/repo', result: 'gitcode', fallbackOrigin: 'gitcode' }, - ]; - - testCases.map((item) => { - expect(getRepoOrigin(item.input, item.fallbackOrigin)).toEqual( - item.result - ); - }); - }); - - it('getRepoPath', () => { - const testCases = [ - { - input: 'https://github.com/ant-design/ant-design', - result: 'ant-design/ant-design', - }, - { input: 'gitee:dotnetchina/MiniWord', result: 'dotnetchina/MiniWord' }, - { input: 'gitcode.com/org/repo', result: 'org/repo' }, - { input: 'owner/repo', result: 'owner/repo' }, - ]; - - testCases.map((item) => { - expect(getRepoPath(item.input)).toEqual(item.result); - }); - }); - - it('getRepoUrlByName', () => { - const testCases = [ - { - input: 'https://github.com/ant-design/ant-design/', - result: 'https://github.com/ant-design/ant-design', - }, - { - input: 'gitee:dotnetchina/MiniWord', - result: 'https://gitee.com/dotnetchina/MiniWord', - }, - { - input: 'gitcode.com/org/repo', - result: 'https://gitcode.com/org/repo', - }, - { - input: 'owner/repo', - result: 'https://gitcode.com/owner/repo', - fallbackOrigin: 'gitcode', - }, - ]; - - testCases.map((item) => { - expect(getRepoUrlByName(item.input, item.fallbackOrigin)).toEqual( - item.result - ); - }); - }); - - it('getLastPathSegment', () => { - const testCases = [ - { input: 'https://github.com/EdmondFrank/.emacs.d', result: '.emacs.d' }, - { - input: 'https://github.com/ant-design/ant-design', - result: 'ant-design', - }, - { input: 'https://gitee.com/dotnetchina/MiniWord', result: 'MiniWord' }, - ]; - - testCases.map((item) => { - expect(getLastPathSegment(item.input)).toEqual(item.result); - }); - }); - - it('getNameSpace', function () { - const testCases = [ - { - input: 'https://github.com/EdmondFrank/.emacs.d', - result: 'EdmondFrank', - }, - { - input: 'https://github.com/ant-design/ant-design', - result: 'ant-design', - }, - { - input: 'https://gitee.com/dotnetchina/MiniWord/', - result: 'dotnetchina', - }, - ]; - - testCases.map((item) => { - expect(getNameSpace(item.input)).toEqual(item.result); - }); - }); - - it('getRepoName', function () { - const testCases = [ - { - input: 'https://github.com/EdmondFrank/.emacs.d', - result: '.emacs.d', - }, - { - input: 'https://github.com/ant-design/ant-design', - result: 'ant-design', - }, - { - input: 'https://gitee.com/dotnetchina/MiniWord/', - result: 'MiniWord', - }, - ]; - - testCases.map((item) => { - expect(getRepoName(item.input)).toEqual(item.result); - }); - }); - - it('removeUrlDotGit', function () { - const testCases = [ - { - input: 'https://github.com/EdmondFrank/.emacs.d', - result: 'https://github.com/EdmondFrank/.emacs.d', - }, - { - input: 'https://github.com/ant-design/ant-design.git', - result: 'https://github.com/ant-design/ant-design', - }, - { - input: undefined, - result: '', - }, - ]; - - testCases.map((item) => { - expect(removeExtname(item.input, '.git')).toEqual(item.result); - }); - }); - - it('removeTrailingSlash', function () { - const testCases = [ - { - input: 'EdmondFrank/.emacs.d/', - result: 'EdmondFrank/.emacs.d', - }, - { - input: 'ant-design/ant-design/', - result: 'ant-design/ant-design', - }, - { - input: undefined, - result: '', - }, - ]; - - testCases.map((item) => { - expect(removeTrailingSlash(item.input)).toEqual(item.result); - }); - }); - - it('getNameSpacePng', function () { - const testCases = [ - { - input: 'https://github.com/EdmondFrank/.emacs.d', - result: 'https://github.com/EdmondFrank.png', - }, - { - input: 'https://github.com/ant-design/ant-design.git', - result: 'https://github.com/ant-design.png', - }, - { - input: undefined, - result: '/images/default.png', - }, - ]; - - testCases.map((item) => { - expect(getNameSpacePng(item.input)).toEqual(item.result); - }); +import { fillHttps } from './url'; + +describe('fillHttps', () => { + it('adds the scheme only when the url has none', () => { + expect(fillHttps('github.com/cli/cli')).toEqual( + 'https://github.com/cli/cli' + ); + expect(fillHttps('github.com/cli/cli/')).toEqual( + 'https://github.com/cli/cli' + ); + expect(fillHttps('https://github.com/cli/cli')).toEqual( + 'https://github.com/cli/cli' + ); + }); + + it('keeps an http url instead of stacking a second scheme', () => { + expect(fillHttps('http://github.com/cli/cli')).toEqual( + 'http://github.com/cli/cli' + ); }); }); diff --git a/apps/web/src/common/utils/url.ts b/apps/web/src/common/utils/url.ts index 8a48f0aaf7..abf2d19d3e 100644 --- a/apps/web/src/common/utils/url.ts +++ b/apps/web/src/common/utils/url.ts @@ -64,7 +64,9 @@ export function getProvider(url: string) { export function fillHttps(url?: string): string { if (!url) return ''; const trimmedUrl = url.endsWith('/') ? url.slice(0, -1) : url; - if (trimmedUrl.indexOf('https') === -1) { + // `indexOf('https') === -1` treated an existing "http://" as "no scheme" and + // produced "https://http://...". Only add the scheme when there is none. + if (!/^https?:\/\//i.test(trimmedUrl)) { return `https://${trimmedUrl}`; } return trimmedUrl;