Skip to content

Commit e185f37

Browse files
Support partially URL-encoded paths
1 parent c1e5e3f commit e185f37

File tree

2 files changed

+54
-19
lines changed

2 files changed

+54
-19
lines changed

index.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,34 @@ test('getRepositoryInfo', () => {
241241
],
242242
}
243243
`);
244+
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%2F%5Bbar%5D%2Fbaz')).toMatchInlineSnapshot(`
245+
{
246+
"name": "sandbox",
247+
"nameWithOwner": "refined-github/sandbox",
248+
"owner": "refined-github",
249+
"path": "blob/bracket-in-path/foo/[bar]/baz",
250+
"pathParts": [
251+
"blob",
252+
"bracket-in-path",
253+
"foo",
254+
"[bar]",
255+
"baz",
256+
],
257+
}
258+
`);
259+
expect(getRepositoryInfoAdapter('https://github.com/refined-github/sandbox/blob/bracket-in-path/foo%252F%5Bbar%5D%252Fbaz')).toMatchInlineSnapshot(`
260+
{
261+
"name": "sandbox",
262+
"nameWithOwner": "refined-github/sandbox",
263+
"owner": "refined-github",
264+
"path": "blob/bracket-in-path/foo%2F[bar]%2Fbaz",
265+
"pathParts": [
266+
"blob",
267+
"bracket-in-path",
268+
"foo%2F[bar]%2Fbaz",
269+
],
270+
}
271+
`);
244272
}
245273
});
246274

index.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ TEST: addTests('isCompare', [
100100
'isQuickPR',
101101
]);
102102

103-
export const isCompareWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).split('/').slice(3, 5).includes('_compare');
103+
export const isCompareWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).split('/').slice(3, 5).includes('_compare');
104104
TEST: addTests('isCompareWikiPage', [
105105
'https://github.com/brookhong/Surfingkeys/wiki/_compare/8ebb46b1a12d16fc1af442b7df0ca13ca3bb34dc...80e51eeabe69b15a3f23880ecc36f800b71e6c6d',
106106
'https://github.com/brookhong/Surfingkeys/wiki/Color-Themes/_compare/8ebb46b1a12d16fc1af442b7df0ca13ca3bb34dc...80e51eeabe69b15a3f23880ecc36f800b71e6c6d',
@@ -109,7 +109,7 @@ TEST: addTests('isCompareWikiPage', [
109109
/**
110110
* @deprecated Use `isHome` and/or `isFeed` instead
111111
*/
112-
export const isDashboard = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(getCleanPathname(url));
112+
export const isDashboard = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^(orgs\/[^/]+\/)?dashboard(-feed)?(\/|$)/.test(processPathname(url));
113113
TEST: addTests('isDashboard', [
114114
'https://github.com///',
115115
'https://github.com//',
@@ -131,7 +131,7 @@ TEST: addTests('isDashboard', [
131131
'https://github.com/dashboard-feed',
132132
]);
133133

134-
export const isHome = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^dashboard\/?$/.test(getCleanPathname(url));
134+
export const isHome = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^$|^dashboard\/?$/.test(processPathname(url));
135135
TEST: addTests('isHome', [
136136
'https://github.com',
137137
'https://github.com//dashboard',
@@ -150,7 +150,7 @@ TEST: addTests('isHome', [
150150
'https://github.com?search=1', // Gotcha for `isRepoTree`
151151
]);
152152

153-
export const isFeed = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^(feed|orgs\/[^/]+\/dashboard)\/?$/.test(getCleanPathname(url));
153+
export const isFeed = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && /^(feed|orgs\/[^/]+\/dashboard)\/?$/.test(processPathname(url));
154154
TEST: addTests('isFeed', [
155155
'https://github.com/feed',
156156
'https://github.com/orgs/refined-github/dashboard',
@@ -246,12 +246,12 @@ TEST: addTests('isNewRelease', [
246246
'https://github.com/sindresorhus/refined-github/releases/new',
247247
]);
248248

249-
export const isNewWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).endsWith('/_new');
249+
export const isNewWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).endsWith('/_new');
250250
TEST: addTests('isNewWikiPage', [
251251
'https://github.com/tooomm/wikitest/wiki/_new',
252252
]);
253253

254-
export const isNotifications = (url: URL | HTMLAnchorElement | Location = location): boolean => getCleanPathname(url) === 'notifications';
254+
export const isNotifications = (url: URL | HTMLAnchorElement | Location = location): boolean => processPathname(url) === 'notifications';
255255
TEST: addTests('isNotifications', [
256256
'https://github.com/notifications',
257257
]);
@@ -271,7 +271,7 @@ TEST: addTests('isTeamDiscussion', [
271271
'https://github.com/orgs/refined-github/teams/core-team',
272272
]);
273273

274-
export const isOwnUserProfile = (): boolean => getCleanPathname() === getLoggedInUser();
274+
export const isOwnUserProfile = (): boolean => processPathname() === getLoggedInUser();
275275

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

440-
export const isEditingWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && getCleanPathname(url).endsWith('/_edit');
440+
export const isEditingWikiPage = (url: URL | HTMLAnchorElement | Location = location): boolean => isRepoWiki(url) && processPathname(url).endsWith('/_edit');
441441
TEST: addTests('isEditingWikiPage', [
442442
'https://github.com/tooomm/wikitest/wiki/Getting-Started/_edit',
443443
]);
@@ -446,7 +446,7 @@ export const hasWikiPageEditor = (url: URL | HTMLAnchorElement | Location = loca
446446
TEST: addTests('hasWikiPageEditor', combinedTestOnly);
447447

448448
export const isRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => {
449-
const [user, repo, extra] = getCleanPathname(url).split('/');
449+
const [user, repo, extra] = processPathname(url).split('/');
450450
return Boolean(user
451451
&& repo
452452
&& !reservedNames.includes(user)
@@ -776,7 +776,7 @@ const doesLookLikeAProfile = (string: string | undefined): boolean =>
776776

777777
export const isProfile = (url: URL | HTMLAnchorElement | Location = location): boolean =>
778778
!isGist(url)
779-
&& doesLookLikeAProfile(getCleanPathname(url));
779+
&& doesLookLikeAProfile(processPathname(url));
780780

781781
TEST: addTests('isProfile', [
782782
'https://github.com/fregante',
@@ -894,7 +894,7 @@ TEST: addTests('isRepoGitObject', [
894894
/** Covers blob, trees and blame pages */
895895
export const isRepoGitObject = (url: URL | HTMLAnchorElement | Location = location): boolean =>
896896
isRepo(url)
897-
&& [undefined, 'blob', 'tree', 'blame'].includes(getCleanPathname(url).split('/')[2]);
897+
&& [undefined, 'blob', 'tree', 'blame'].includes(processPathname(url).split('/')[2]);
898898

899899
TEST: addTests('hasFiles', combinedTestOnly);
900900
/** Has a list of files */
@@ -952,7 +952,7 @@ export const canUserAdminRepo = (): boolean => {
952952
// eslint-disable-next-line @typescript-eslint/no-deprecated
953953
export const canUserAccessRepoSettings = canUserAdminRepo;
954954

955-
export const isNewRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && (url.pathname === '/new' || /^organizations\/[^/]+\/repositories\/new$/.test(getCleanPathname(url)));
955+
export const isNewRepo = (url: URL | HTMLAnchorElement | Location = location): boolean => !isGist(url) && (url.pathname === '/new' || /^organizations\/[^/]+\/repositories\/new$/.test(processPathname(url)));
956956
TEST: addTests('isNewRepo', [
957957
'https://github.com/new',
958958
'https://github.com/organizations/npmhub/repositories/new',
@@ -967,11 +967,18 @@ TEST: addTests('isNewRepoTemplate', [
967967
/** Get the logged-in user’s username */
968968
const getLoggedInUser = (): string | undefined => $('meta[name="user-login"]')?.getAttribute('content') ?? undefined;
969969

970-
/** Drop all redundant slashes */
971-
const getCleanPathname = (url: URL | HTMLAnchorElement | Location = location): string => url.pathname.replaceAll(/\/\/+/g, '/').replace(/\/$/, '').slice(1);
970+
/** Decode it and drop all redundant slashes */
971+
const processPathname = (url: URL | HTMLAnchorElement | Location = location): string =>
972+
url.pathname
973+
.split('/')
974+
.map(part => decodeURIComponent(part))
975+
.join('/')
976+
.replaceAll(/\/\/+/g, '/')
977+
.replace(/\/$/, '')
978+
.slice(1);
972979

973980
const getCleanGistPathname = (url: URL | HTMLAnchorElement | Location = location): string | undefined => {
974-
const pathname = getCleanPathname(url);
981+
const pathname = processPathname(url);
975982
if (url.hostname.startsWith('gist.')) {
976983
return pathname;
977984
}
@@ -981,7 +988,7 @@ const getCleanGistPathname = (url: URL | HTMLAnchorElement | Location = location
981988
};
982989

983990
const getOrg = (url: URL | HTMLAnchorElement | Location = location): {name: string; path: string} | undefined => {
984-
const [orgs, name, ...path] = getCleanPathname(url).split('/');
991+
const [orgs, name, ...path] = processPathname(url).split('/');
985992
if (orgs === 'orgs' && name) {
986993
return {name, path: path.join('/')};
987994
}
@@ -1027,7 +1034,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
10271034
if (canonical) {
10281035
const canonicalUrl = new URL(canonical.content, location.origin);
10291036
// Sometimes GitHub sets the canonical to an incomplete URL, so it can't be used
1030-
if (getCleanPathname(canonicalUrl).toLowerCase() === getCleanPathname(location).toLowerCase()) {
1037+
if (processPathname(canonicalUrl).toLowerCase() === processPathname(location).toLowerCase()) {
10311038
url = canonicalUrl;
10321039
}
10331040
}
@@ -1041,7 +1048,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
10411048
return;
10421049
}
10431050

1044-
const [owner, name, ...pathParts] = getCleanPathname(url).split('/') as [string, string, string];
1051+
const [owner, name, ...pathParts] = processPathname(url).split('/') as [string, string, string];
10451052
return {
10461053
owner,
10471054
name,
@@ -1054,7 +1061,7 @@ const getRepo = (url?: URL | HTMLAnchorElement | Location | string): RepositoryI
10541061
export const utils = {
10551062
getOrg,
10561063
getLoggedInUser,
1057-
getCleanPathname,
1064+
processPathname,
10581065
getCleanGistPathname,
10591066
getRepositoryInfo: getRepo,
10601067
parseRepoExplorerTitle,

0 commit comments

Comments
 (0)