From f02fefb5b79804ab3abcce3888e3fd9f035c0766 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 25 Dec 2021 15:24:21 +0800 Subject: [PATCH] fix(utils): properly escape Windows paths (#6190) * fix(utils): properly escape Windows paths * Use in more places * Escape path in test * Fix snapshot * Better comment * Fix tests --- .../transformImage/__tests__/index.test.ts | 4 ++-- .../transformLinks/__tests__/index.test.ts | 4 ++-- packages/docusaurus-utils/src/escapePath.ts | 6 ++--- packages/docusaurus-utils/src/posixPath.ts | 14 ++++++----- packages/docusaurus-utils/src/webpackUtils.ts | 8 +++---- .../__snapshots__/routes.test.ts.snap | 24 +++++++++---------- packages/docusaurus/src/server/index.ts | 9 ++++--- packages/docusaurus/src/server/routes.ts | 6 ++--- 8 files changed, 38 insertions(+), 37 deletions(-) diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts index fec6f2980b..f7acabd6a5 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts @@ -11,7 +11,6 @@ import mdx from 'remark-mdx'; import vfile from 'to-vfile'; import plugin from '../index'; import headings from '../../headings/index'; -import {posixPath} from '@docusaurus/utils'; const processFixture = async (name, options) => { const filePath = path.join(__dirname, `__fixtures__/${name}.md`); @@ -24,7 +23,8 @@ const processFixture = async (name, options) => { return result .toString() - .replace(new RegExp(posixPath(process.cwd()), 'g'), '[CWD]'); + .replace(/\\\\/g, '/') + .replace(new RegExp(process.cwd().replace(/\\/g, '/'), 'g'), '[CWD]'); }; const staticDirs = [ diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts index 6129510b41..d48a014d19 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts @@ -11,7 +11,6 @@ import mdx from 'remark-mdx'; import vfile from 'to-vfile'; import plugin from '..'; import transformImage from '../../transformImage'; -import {posixPath} from '@docusaurus/utils'; const processFixture = async (name: string, options?) => { const filePath = path.join(__dirname, `__fixtures__/${name}.md`); @@ -33,7 +32,8 @@ const processFixture = async (name: string, options?) => { return result .toString() - .replace(new RegExp(posixPath(process.cwd()), 'g'), '[CWD]'); + .replace(/\\\\/g, '/') + .replace(new RegExp(process.cwd().replace(/\\/g, '/'), 'g'), '[CWD]'); }; describe('transformAsset plugin', () => { diff --git a/packages/docusaurus-utils/src/escapePath.ts b/packages/docusaurus-utils/src/escapePath.ts index d53a52a7b3..b0ded61ccb 100644 --- a/packages/docusaurus-utils/src/escapePath.ts +++ b/packages/docusaurus-utils/src/escapePath.ts @@ -11,9 +11,9 @@ * For example, this would fail due to unescaped \: `` * But this would work: `` * - * Workaround for issue in posixPath, maybe we won't need it anymore soon? - * https://github.com/facebook/docusaurus/issues/4730#issuecomment-833530370 - * https://github.com/sindresorhus/slash/pull/16#issuecomment-833528479 + * posixPath can't be used in all cases, because forward slashes are only valid + * Windows paths when they don't contain non-ascii characters, and posixPath + * doesn't escape those that fail to be converted. */ export function escapePath(str: string): string { const escaped = JSON.stringify(str); diff --git a/packages/docusaurus-utils/src/posixPath.ts b/packages/docusaurus-utils/src/posixPath.ts index 792356376e..bf3bf05c86 100644 --- a/packages/docusaurus-utils/src/posixPath.ts +++ b/packages/docusaurus-utils/src/posixPath.ts @@ -7,18 +7,20 @@ /** * Convert Windows backslash paths to posix style paths. - * E.g: endi\\lie -> endi/lie + * E.g: endi\lie -> endi/lie * - * Looks like this code was originally copied from https://github.com/sindresorhus/slash/blob/main/index.js + * Returns original path if the posix counterpart is not valid Windows path. + * This makes the legacy code that uses posixPath safe; but also makes it less + * useful when you actually want a path with forward slashes (e.g. for URL) * + * Adopted from https://github.com/sindresorhus/slash/blob/main/index.js */ export function posixPath(str: string): string { const isExtendedLengthPath = /^\\\\\?\\/.test(str); - // TODO not sure why we need this - // See https://github.com/sindresorhus/slash/pull/16#issuecomment-833528479 - // See https://github.com/facebook/docusaurus/issues/4730#issuecomment-833530370 - const hasNonAscii = /[^\u0000-\u0080]+/.test(str); // eslint-disable-line + // Forward slashes are only valid Windows paths when they don't contain non-ascii characters. + // eslint-disable-next-line no-control-regex + const hasNonAscii = /[^\u0000-\u0080]+/.test(str); if (isExtendedLengthPath || hasNonAscii) { return str; diff --git a/packages/docusaurus-utils/src/webpackUtils.ts b/packages/docusaurus-utils/src/webpackUtils.ts index 858b817588..87b1bb8f00 100644 --- a/packages/docusaurus-utils/src/webpackUtils.ts +++ b/packages/docusaurus-utils/src/webpackUtils.ts @@ -7,7 +7,7 @@ import type {RuleSetRule} from 'webpack'; import path from 'path'; -import {posixPath} from './posixPath'; +import {escapePath} from './escapePath'; import { WEBPACK_URL_LOADER_LIMIT, OUTPUT_STATIC_ASSETS_DIR_NAME, @@ -61,12 +61,12 @@ export function getFileLoaderUtils(): FileLoaderUtils { // Maybe with the ideal image plugin, all md images should be "ideal"? // This is used to force url-loader+file-loader on markdown images // https://webpack.js.org/concepts/loaders/#inline - inlineMarkdownImageFileLoader: `!${posixPath( + inlineMarkdownImageFileLoader: `!${escapePath( require.resolve('url-loader'), )}?limit=${urlLoaderLimit}&name=${fileLoaderFileName( 'images', - )}&fallback=${posixPath(require.resolve('file-loader'))}!`, - inlineMarkdownLinkFileLoader: `!${posixPath( + )}&fallback=${escapePath(require.resolve('file-loader'))}!`, + inlineMarkdownLinkFileLoader: `!${escapePath( require.resolve('file-loader'), )}?name=${fileLoaderFileName('files')}!`, }; diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/routes.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/routes.test.ts.snap index 03623c65b4..fa6b77131d 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/routes.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/routes.test.ts.snap @@ -4,19 +4,19 @@ exports[`loadRoutes flat route config 1`] = ` Object { "registry": Object { "component---theme-blog-list-pagea-6-a-7ba": Object { - "loader": "() => import(/* webpackChunkName: 'component---theme-blog-list-pagea-6-a-7ba' */ \\"@theme/BlogListPage\\")", + "loader": "() => import(/* webpackChunkName: 'component---theme-blog-list-pagea-6-a-7ba' */ '@theme/BlogListPage')", "modulePath": "@theme/BlogListPage", }, "content---blog-0-b-4-09e": Object { - "loader": "() => import(/* webpackChunkName: 'content---blog-0-b-4-09e' */ \\"blog/2018-12-14-Happy-First-Birthday-Slash.md?truncated=true\\")", + "loader": "() => import(/* webpackChunkName: 'content---blog-0-b-4-09e' */ 'blog/2018-12-14-Happy-First-Birthday-Slash.md?truncated=true')", "modulePath": "blog/2018-12-14-Happy-First-Birthday-Slash.md?truncated=true", }, "content---blog-7-b-8-fd9": Object { - "loader": "() => import(/* webpackChunkName: 'content---blog-7-b-8-fd9' */ \\"blog/2018-12-14-Happy-First-Birthday-Slash.md\\")", + "loader": "() => import(/* webpackChunkName: 'content---blog-7-b-8-fd9' */ 'blog/2018-12-14-Happy-First-Birthday-Slash.md')", "modulePath": "blog/2018-12-14-Happy-First-Birthday-Slash.md", }, "metadata---blog-0-b-6-74c": Object { - "loader": "() => import(/* webpackChunkName: 'metadata---blog-0-b-6-74c' */ \\"blog-2018-12-14-happy-first-birthday-slash-d2c.json\\")", + "loader": "() => import(/* webpackChunkName: 'metadata---blog-0-b-6-74c' */ 'blog-2018-12-14-happy-first-birthday-slash-d2c.json')", "modulePath": "blog-2018-12-14-happy-first-birthday-slash-d2c.json", }, }, @@ -62,31 +62,31 @@ exports[`loadRoutes nested route config 1`] = ` Object { "registry": Object { "component---theme-doc-item-178-a40": Object { - "loader": "() => import(/* webpackChunkName: 'component---theme-doc-item-178-a40' */ \\"@theme/DocItem\\")", + "loader": "() => import(/* webpackChunkName: 'component---theme-doc-item-178-a40' */ '@theme/DocItem')", "modulePath": "@theme/DocItem", }, "component---theme-doc-page-1-be-9be": Object { - "loader": "() => import(/* webpackChunkName: 'component---theme-doc-page-1-be-9be' */ \\"@theme/DocPage\\")", + "loader": "() => import(/* webpackChunkName: 'component---theme-doc-page-1-be-9be' */ '@theme/DocPage')", "modulePath": "@theme/DocPage", }, "content---docs-foo-baz-8-ce-61e": Object { - "loader": "() => import(/* webpackChunkName: 'content---docs-foo-baz-8-ce-61e' */ \\"docs/foo/baz.md\\")", + "loader": "() => import(/* webpackChunkName: 'content---docs-foo-baz-8-ce-61e' */ 'docs/foo/baz.md')", "modulePath": "docs/foo/baz.md", }, "content---docs-helloaff-811": Object { - "loader": "() => import(/* webpackChunkName: 'content---docs-helloaff-811' */ \\"docs/hello.md\\")", + "loader": "() => import(/* webpackChunkName: 'content---docs-helloaff-811' */ 'docs/hello.md')", "modulePath": "docs/hello.md", }, "docsMetadata---docs-routef-34-881": Object { - "loader": "() => import(/* webpackChunkName: 'docsMetadata---docs-routef-34-881' */ \\"docs-b5f.json\\")", + "loader": "() => import(/* webpackChunkName: 'docsMetadata---docs-routef-34-881' */ 'docs-b5f.json')", "modulePath": "docs-b5f.json", }, "metadata---docs-foo-baz-2-cf-fa7": Object { - "loader": "() => import(/* webpackChunkName: 'metadata---docs-foo-baz-2-cf-fa7' */ \\"docs-foo-baz-dd9.json\\")", + "loader": "() => import(/* webpackChunkName: 'metadata---docs-foo-baz-2-cf-fa7' */ 'docs-foo-baz-dd9.json')", "modulePath": "docs-foo-baz-dd9.json", }, "metadata---docs-hello-956-741": Object { - "loader": "() => import(/* webpackChunkName: 'metadata---docs-hello-956-741' */ \\"docs-hello-da2.json\\")", + "loader": "() => import(/* webpackChunkName: 'metadata---docs-hello-956-741' */ 'docs-hello-da2.json')", "modulePath": "docs-hello-da2.json", }, }, @@ -146,7 +146,7 @@ exports[`loadRoutes route config with empty (but valid) path string 1`] = ` Object { "registry": Object { "component---hello-world-jse-0-f-b6c": Object { - "loader": "() => import(/* webpackChunkName: 'component---hello-world-jse-0-f-b6c' */ \\"hello/world.js\\")", + "loader": "() => import(/* webpackChunkName: 'component---hello-world-jse-0-f-b6c' */ 'hello/world.js')", "modulePath": "hello/world.js", }, }, diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index c8bca3ddd8..1c18f9a4b0 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -7,6 +7,7 @@ import { generate, + escapePath, DEFAULT_BUILD_DIR_NAME, DEFAULT_CONFIG_FILE_NAME, GENERATED_FILES_DIR_NAME, @@ -323,8 +324,7 @@ export async function load( `export default [\n${clientModules // import() is async so we use require() because client modules can have // CSS and the order matters for loading CSS. - // We need to JSON.stringify so that if its on windows, backslash are escaped. - .map((module) => ` require(${JSON.stringify(module)}),`) + .map((module) => ` require('${escapePath(module)}'),`) .join('\n')}\n];\n`, ); @@ -343,10 +343,9 @@ ${Object.keys(registry) .sort() .map( (key) => - // We need to JSON.stringify so that if its on windows, backslash are escaped. - ` '${key}': [${registry[key].loader}, ${JSON.stringify( + ` '${key}': [${registry[key].loader}, '${escapePath( registry[key].modulePath, - )}, require.resolveWeak(${JSON.stringify(registry[key].modulePath)})],`, + )}', require.resolveWeak('${escapePath(registry[key].modulePath)}')],`, ) .join('\n')}};\n`, ); diff --git a/packages/docusaurus/src/server/routes.ts b/packages/docusaurus/src/server/routes.ts index 91740d8df1..ac6a505e14 100644 --- a/packages/docusaurus/src/server/routes.ts +++ b/packages/docusaurus/src/server/routes.ts @@ -10,6 +10,7 @@ import { normalizeUrl, removeSuffix, simpleHash, + escapePath, } from '@docusaurus/utils'; import {has, isPlainObject, isString} from 'lodash'; import {stringify} from 'querystring'; @@ -232,10 +233,9 @@ function genRouteChunkNames( if (isModule(value)) { const modulePath = getModulePath(value); const chunkName = genChunkName(modulePath, prefix, name); - // We need to JSON.stringify so that if its on windows, backslashes are escaped. - const loader = `() => import(/* webpackChunkName: '${chunkName}' */ ${JSON.stringify( + const loader = `() => import(/* webpackChunkName: '${chunkName}' */ '${escapePath( modulePath, - )})`; + )}')`; registry[chunkName] = { loader,