diff --git a/packages/docusaurus/src/server/__tests__/brokenLinks.test.ts b/packages/docusaurus/src/server/__tests__/brokenLinks.test.ts index 52da59184408..e132545f4a2d 100644 --- a/packages/docusaurus/src/server/__tests__/brokenLinks.test.ts +++ b/packages/docusaurus/src/server/__tests__/brokenLinks.test.ts @@ -205,4 +205,54 @@ describe('brokenLinks', () => { }); expect(result).toEqual(allCollectedLinksFiltered); }); + + describe('Encoded link', () => { + test('getAllBrokenLinks', async () => { + const routes: RouteConfig[] = [ + { + path: '/docs', + component: '', + routes: [ + {path: '/docs/some doc', component: ''}, + {path: '/docs/some other doc', component: ''}, + {path: '/docs/weird%20file%20name', component: ''}, + ], + }, + { + path: '*', + component: '', + }, + ]; + + const allCollectedLinks = { + '/docs/some doc': [ + // good - valid file with spaces in name + './some%20other%20doc', + // good - valid file with percent-20 in its name + './weird%20file%20name', + // bad - non-existant file with spaces in name + './some%20other%20non-existant%20doc', + // evil - trying to use ../../ but '/' won't get decoded + './break%2F..%2F..%2Fout', + ], + }; + + const expectedBrokenLinks = { + '/docs/some doc': [ + { + link: './some%20other%20non-existant%20doc', + resolvedLink: '/docs/some%20other%20non-existant%20doc', + }, + { + link: './break%2F..%2F..%2Fout', + resolvedLink: '/docs/break%2F..%2F..%2Fout', + }, + ], + }; + + expect(getAllBrokenLinks({allCollectedLinks, routes})).toEqual( + expectedBrokenLinks, + ); + }); + }); }); diff --git a/packages/docusaurus/src/server/brokenLinks.ts b/packages/docusaurus/src/server/brokenLinks.ts index a4e0d5c4c48a..699871d935ea 100644 --- a/packages/docusaurus/src/server/brokenLinks.ts +++ b/packages/docusaurus/src/server/brokenLinks.ts @@ -48,7 +48,9 @@ function getPageBrokenLinks({ } function isBrokenLink(link: string) { - const matchedRoutes = matchRoutes(toReactRouterRoutes(routes), link); + const matchedRoutes = [link, decodeURI(link)] + .map((l) => matchRoutes(toReactRouterRoutes(routes), l)) + .reduce((prev, cur) => prev.concat(cur)); return matchedRoutes.length === 0; }