Skip to content

Commit 69e49d0

Browse files
Support partially URL-encoded paths
1 parent 5125a08 commit 69e49d0

File tree

2 files changed

+54
-17
lines changed

2 files changed

+54
-17
lines changed

index.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,36 @@ test('getRepositoryInfo', () => {
234234
],
235235
}
236236
`);
237+
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%2F%5Bbar%5D%2Fbaz')).toMatchInlineSnapshot(`
238+
{
239+
"name": "sandbox",
240+
"nameWithOwner": "refined-github/sandbox",
241+
"owner": "refined-github",
242+
"path": "blob/bracket-in-path/foo/[bar]/baz",
243+
"pathParts": [
244+
"blob",
245+
"bracket-in-path",
246+
"foo",
247+
"[bar]",
248+
"baz",
249+
],
250+
}
251+
`);
252+
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%252F%5Bbar%5D%252Fbaz')).toMatchInlineSnapshot(`
253+
{
254+
"name": "sandbox",
255+
"nameWithOwner": "refined-github/sandbox",
256+
"owner": "refined-github",
257+
"path": "blob/bracket-in-path/foo%2F[bar]%2Fbaz",
258+
"pathParts": [
259+
"blob",
260+
"bracket-in-path",
261+
"foo%2F[bar]%2Fbaz",
262+
],
263+
}
264+
`);
237265
}
266+
// foo%252F%5Bbar%5D%252Fbaz
238267
});
239268

240269
test('parseRepoExplorerTitle', () => {

index.ts

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ TEST: addTests('isCompare', [
5555
'isQuickPR',
5656
]);
5757

58-
export const isCompareWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).split('/').slice(3, 5).includes('_compare');
58+
export const isCompareWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).split('/').slice(3, 5).includes('_compare');
5959
TEST: addTests('isCompareWikiPage', [
6060
'https://github.com/brookhong/Surfingkeys/wiki/_compare/8ebb46b1a12d16fc1af442b7df0ca13ca3bb34dc...80e51eeabe69b15a3f23880ecc36f800b71e6c6d',
6161
'https://github.com/brookhong/Surfingkeys/wiki/Color-Themes/_compare/8ebb46b1a12d16fc1af442b7df0ca13ca3bb34dc...80e51eeabe69b15a3f23880ecc36f800b71e6c6d',
6262
]);
6363

64-
export const isDashboard = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(getCleanPathname(url));
64+
export const isDashboard = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(processPathname(url));
6565
TEST: addTests('isDashboard', [
6666
'https://github.com///',
6767
'https://github.com//',
@@ -174,12 +174,12 @@ TEST: addTests('isNewRelease', [
174174
'https://github.com/sindresorhus/refined-github/releases/new',
175175
]);
176176

177-
export const isNewWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).endsWith('/_new');
177+
export const isNewWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).endsWith('/_new');
178178
TEST: addTests('isNewWikiPage', [
179179
'https://github.com/tooomm/wikitest/wiki/_new',
180180
]);
181181

182-
export const isNotifications = (url: URL | HTMLAnchorElement | Location = location): boolean => getCleanPathname(url) === 'notifications';
182+
export const isNotifications = (url: URL | HTMLAnchorElement | Location = location): boolean => processPathname(url) === 'notifications';
183183
TEST: addTests('isNotifications', [
184184
'https://github.com/notifications',
185185
]);
@@ -199,7 +199,7 @@ TEST: addTests('isTeamDiscussion', [
199199
'https://github.com/orgs/refined-github/teams/core-team',
200200
]);
201201

202-
export const isOwnUserProfile = (): boolean => getCleanPathname() === getLoggedInUser();
202+
export const isOwnUserProfile = (): boolean => processPathname() === getLoggedInUser();
203203

204204
// If there's a Report Abuse link, we're not part of the org
205205
export const isOwnOrganizationProfile = (): boolean => isOrganizationProfile() && !exists('[href*="contact/report-abuse?report="]');
@@ -365,7 +365,7 @@ TEST: addTests('isEditingRelease', [
365365
export const hasReleaseEditor = (url: URL | HTMLAnchorElement | Location = location): boolean => isEditingRelease(url) || isNewRelease(url);
366366
TEST: addTests('hasReleaseEditor', combinedTestOnly);
367367

368-
export const isEditingWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).endsWith('/_edit');
368+
export const isEditingWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).endsWith('/_edit');
369369
TEST: addTests('isEditingWikiPage', [
370370
'https://github.com/tooomm/wikitest/wiki/Getting-Started/_edit',
371371
]);
@@ -374,7 +374,7 @@ export const hasWikiPageEditor = (url: URL | HTMLAnchorElement | Location = loca
374374
TEST: addTests('hasWikiPageEditor', combinedTestOnly);
375375

376376
export const isRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => {
377-
const [user, repo, extra] = getCleanPathname(url).split('/');
377+
const [user, repo, extra] = processPathname(url).split('/');
378378
return Boolean(user
379379
&& repo
380380
&& !reservedNames.includes(user)
@@ -704,7 +704,7 @@ const doesLookLikeAProfile = (string: string | undefined): boolean =>
704704

705705
export const isProfile = (url: URL | HTMLAnchorElement | Location = location): boolean =>
706706
!isGist(url)
707-
&& doesLookLikeAProfile(getCleanPathname(url));
707+
&& doesLookLikeAProfile(processPathname(url));
708708

709709
TEST: addTests('isProfile', [
710710
'https://github.com/fregante',
@@ -822,7 +822,7 @@ TEST: addTests('isRepoGitObject', [
822822
/** Covers blob, trees and blame pages */
823823
export const isRepoGitObject = (url: URL | HTMLAnchorElement | Location = location): boolean =>
824824
isRepo(url)
825-
&& [undefined, 'blob', 'tree', 'blame'].includes(getCleanPathname(url).split('/')[2]);
825+
&& [undefined, 'blob', 'tree', 'blame'].includes(processPathname(url).split('/')[2]);
826826

827827
TEST: addTests('hasFiles', combinedTestOnly);
828828
/** Has a list of files */
@@ -874,7 +874,7 @@ export const canUserAdminRepo = (): boolean => {
874874
].join(',')}) a[href="/${repo.nameWithOwner}/settings"]`));
875875
};
876876

877-
export const isNewRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && (url.pathname === '/new' || /^organizations\/[^/]+\/repositories\/new$/.test(getCleanPathname(url)));
877+
export const isNewRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && (url.pathname === '/new' || /^organizations\/[^/]+\/repositories\/new$/.test(processPathname(url)));
878878
TEST: addTests('isNewRepo', [
879879
'https://github.com/new',
880880
'https://github.com/organizations/npmhub/repositories/new',
@@ -889,11 +889,19 @@ TEST: addTests('isNewRepoTemplate', [
889889
/** Get the logged-in user’s username */
890890
const getLoggedInUser = (): string | undefined => $('meta[name="user-login"]')?.getAttribute('content') ?? undefined;
891891

892-
/** Drop all redundant slashes */
893-
const getCleanPathname = (url: URL | HTMLAnchorElement | Location = location): string => url.pathname.replaceAll(/\/\/+/g, '/').replace(/\/$/, '').slice(1);
892+
/** Decode it and drop all redundant slashes */
893+
const processPathname = (url: URL | HTMLAnchorElement | Location = location): string =>
894+
url.pathname
895+
.split("/")
896+
.map((part) => decodeURIComponent(part))
897+
.join('/')
898+
.replaceAll(/\/\/+/g, "/")
899+
.replace(/\/$/, "")
900+
.slice(1);
901+
894902

895903
const getCleanGistPathname = (url: URL | HTMLAnchorElement | Location = location): string | undefined => {
896-
const pathname = getCleanPathname(url);
904+
const pathname = processPathname(url);
897905
if (url.hostname.startsWith('gist.')) {
898906
return pathname;
899907
}
@@ -903,7 +911,7 @@ const getCleanGistPathname = (url: URL | HTMLAnchorElement | Location = location
903911
};
904912

905913
const getOrg = (url: URL | HTMLAnchorElement | Location = location): {name: string; path: string} | undefined => {
906-
const [orgs, name, ...path] = getCleanPathname(url).split('/');
914+
const [orgs, name, ...path] = processPathname(url).split('/');
907915
if (orgs === 'orgs' && name) {
908916
return {name, path: path.join('/')};
909917
}
@@ -949,7 +957,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
949957
if (canonical) {
950958
const canonicalUrl = new URL(canonical.content, location.origin);
951959
// Sometimes GitHub sets the canonical to an incomplete URL, so it can't be used
952-
if (getCleanPathname(canonicalUrl).toLowerCase() === getCleanPathname(location).toLowerCase()) {
960+
if (processPathname(canonicalUrl).toLowerCase() === processPathname(location).toLowerCase()) {
953961
url = canonicalUrl;
954962
}
955963
}
@@ -963,7 +971,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
963971
return;
964972
}
965973

966-
const [owner, name, ...pathParts] = getCleanPathname(url).split('/') as [string, string, string];
974+
const [owner, name, ...pathParts] = processPathname(decodePathname(url)).split('/') as [string, string, string];
967975
return {
968976
owner,
969977
name,
@@ -976,7 +984,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
976984
export const utils = {
977985
getOrg,
978986
getLoggedInUser,
979-
getCleanPathname,
987+
processPathname,
980988
getCleanGistPathname,
981989
getRepositoryInfo: getRepo,
982990
parseRepoExplorerTitle,

0 commit comments

Comments
 (0)