diff --git a/CHANGELOG-2.x.md b/CHANGELOG-2.x.md index 4bf3aa575e..82034f6ea2 100644 --- a/CHANGELOG-2.x.md +++ b/CHANGELOG-2.x.md @@ -2,7 +2,8 @@ ## Unreleased -- Shorter chunk naming for pages. Instead of absolute path, we use relative path from site directory +- Fix broken markdown linking replacement for mdx files +- Fix potential security vulnerability because we're exposing the directory structure of the host machine. Instead of absolute path, we use relative path from site directory. Resulting in shorter webpack chunk naming and smaller bundle size. - Use contenthash instead of chunkhash for better long term caching - Allow user to customize generated heading from MDX. Swizzle `@theme/Heading` diff --git a/packages/docusaurus-plugin-content-blog/src/index.js b/packages/docusaurus-plugin-content-blog/src/index.js index 2e450bcfea..7d8b867ecc 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.js +++ b/packages/docusaurus-plugin-content-blog/src/index.js @@ -49,7 +49,7 @@ module.exports = function(context, opts) { // Fetches blog contents and returns metadata for the necessary routes. async loadContent() { const {postsPerPage, include, routeBasePath} = options; - const {siteConfig} = context; + const {siteConfig, siteDir} = context; const blogDir = contentPath; if (!fs.existsSync(blogDir)) { @@ -65,8 +65,9 @@ module.exports = function(context, opts) { await Promise.all( blogFiles.map(async relativeSource => { + // Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it. const source = path.join(blogDir, relativeSource); - + const aliasedSource = `@site/${path.relative(siteDir, source)}`; const blogFileName = path.basename(relativeSource); // Extract, YYYY, MM, DD from the file name. const filePathDateArr = blogFileName.split('-'); @@ -87,7 +88,7 @@ module.exports = function(context, opts) { routeBasePath, frontMatter.id || fileToUrl(blogFileName), ]), - source, + source: aliasedSource, description: frontMatter.description || excerpt, date, tags: frontMatter.tags, diff --git a/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/index.test.js b/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/index.test.js index 0578c86cbb..cb434acd25 100644 --- a/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/index.test.js +++ b/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/index.test.js @@ -17,6 +17,7 @@ describe('loadDocs', () => { url: 'https://docusaurus.io', }; const sidebarPath = path.join(siteDir, 'sidebars.json'); + const pluginPath = 'docs'; const plugin = pluginContentDocs( { siteDir, @@ -28,7 +29,6 @@ describe('loadDocs', () => { }, ); const {docs: docsMetadata} = await plugin.loadContent(); - const docsDir = plugin.contentPath; expect(docsMetadata.hello).toEqual({ category: 'Guides', @@ -37,7 +37,7 @@ describe('loadDocs', () => { previous: 'foo/baz', previous_title: 'baz', sidebar: 'docs', - source: path.join(docsDir, 'hello.md'), + source: path.join('@site', pluginPath, 'hello.md'), title: 'Hello, World !', description: `Hi, Endilie here :)`, }); @@ -49,7 +49,7 @@ describe('loadDocs', () => { next_title: 'baz', permalink: '/docs/foo/bar', sidebar: 'docs', - source: path.join(docsDir, 'foo', 'bar.md'), + source: path.join('@site', pluginPath, 'foo', 'bar.md'), title: 'Bar', description: 'This is custom description', }); diff --git a/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/metadata.test.js b/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/metadata.test.js index 9de643d84c..117fe3a9d7 100644 --- a/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/metadata.test.js +++ b/packages/docusaurus-plugin-content-docs-legacy/src/__tests__/metadata.test.js @@ -15,36 +15,28 @@ describe('processMetadata', () => { baseUrl: '/', url: 'https://docusaurus.io', }; - const docsDir = path.resolve(siteDir, 'docs'); + const pluginPath = 'docs'; + const docsDir = path.resolve(siteDir, pluginPath); test('normal docs', async () => { const sourceA = path.join('foo', 'bar.md'); const sourceB = path.join('hello.md'); - const dataA = await processMetadata( - sourceA, - docsDir, - {}, - siteConfig, - 'docs', - ); - const dataB = await processMetadata( - sourceB, - docsDir, - {}, - siteConfig, - 'docs', - ); + + const [dataA, dataB] = await Promise.all([ + processMetadata(sourceA, docsDir, {}, siteConfig, pluginPath, siteDir), + processMetadata(sourceB, docsDir, {}, siteConfig, pluginPath, siteDir), + ]); expect(dataA).toEqual({ id: 'foo/bar', permalink: '/docs/foo/bar', - source: path.join(docsDir, sourceA), + source: path.join('@site', pluginPath, sourceA), title: 'Bar', description: 'This is custom description', }); expect(dataB).toEqual({ id: 'hello', permalink: '/docs/hello', - source: path.join(docsDir, sourceB), + source: path.join('@site', pluginPath, sourceB), title: 'Hello, World !', description: `Hi, Endilie here :)`, }); @@ -52,11 +44,18 @@ describe('processMetadata', () => { test('docs with custom permalink', async () => { const source = path.join('permalink.md'); - const data = await processMetadata(source, docsDir, {}, siteConfig, 'docs'); + const data = await processMetadata( + source, + docsDir, + {}, + siteConfig, + pluginPath, + siteDir, + ); expect(data).toEqual({ id: 'permalink', permalink: '/docs/endiliey/permalink', - source: path.join(docsDir, source), + source: path.join('@site', pluginPath, source), title: 'Permalink', description: 'This has a different permalink', }); diff --git a/packages/docusaurus-plugin-content-docs-legacy/src/index.js b/packages/docusaurus-plugin-content-docs-legacy/src/index.js index 96cdea49b0..7298e9df4f 100644 --- a/packages/docusaurus-plugin-content-docs-legacy/src/index.js +++ b/packages/docusaurus-plugin-content-docs-legacy/src/index.js @@ -46,7 +46,7 @@ module.exports = function(context, opts) { // Fetches blog contents and returns metadata for the contents. async loadContent() { const {include, routeBasePath, sidebarPath} = options; - const {siteConfig} = context; + const {siteConfig, siteDir} = context; const docsDir = contentPath; if (!fs.existsSync(docsDir)) { @@ -73,6 +73,7 @@ module.exports = function(context, opts) { order, siteConfig, routeBasePath, + siteDir, ); docs[metadata.id] = metadata; }), @@ -177,6 +178,7 @@ module.exports = function(context, opts) { loader: path.resolve(__dirname, './markdown/index.js'), options: { siteConfig: context.siteConfig, + siteDir: context.siteDir, docsDir: globalContents.docsDir, sourceToPermalink: globalContents.sourceToPermalink, }, diff --git a/packages/docusaurus-plugin-content-docs-legacy/src/markdown/index.js b/packages/docusaurus-plugin-content-docs-legacy/src/markdown/index.js index 7f2d88c256..33e6920843 100644 --- a/packages/docusaurus-plugin-content-docs-legacy/src/markdown/index.js +++ b/packages/docusaurus-plugin-content-docs-legacy/src/markdown/index.js @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +const path = require('path'); const {getOptions} = require('loader-utils'); const {resolve} = require('url'); @@ -13,7 +14,7 @@ module.exports = async function(fileString) { const options = Object.assign({}, getOptions(this), { filepath: this.resourcePath, }); - const {docsDir, sourceToPermalink} = options; + const {docsDir, siteDir, sourceToPermalink} = options; // Determine the source dir. e.g: /docs, /website/versioned_docs/version-1.0.0 let sourceDir; @@ -37,15 +38,17 @@ module.exports = async function(fileString) { // Replace inline-style links or reference-style links e.g: // This is [Document 1](doc1.md) -> we replace this doc1.md with correct link // [doc1]: doc1.md -> we replace this doc1.md with correct link - const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.md)/g; + const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https)([^'")\]\s>]+\.mdx?)/g; let mdMatch = mdRegex.exec(modifiedLine); while (mdMatch !== null) { // Replace it to correct html link. const mdLink = mdMatch[1]; const targetSource = `${sourceDir}/${mdLink}`; + const aliasedSource = source => + `@site/${path.relative(siteDir, source)}`; const permalink = - sourceToPermalink[resolve(thisSource, mdLink)] || - sourceToPermalink[targetSource]; + sourceToPermalink[aliasedSource(resolve(thisSource, mdLink))] || + sourceToPermalink[aliasedSource(targetSource)]; if (permalink) { modifiedLine = modifiedLine.replace(mdLink, permalink); } diff --git a/packages/docusaurus-plugin-content-docs-legacy/src/metadata.js b/packages/docusaurus-plugin-content-docs-legacy/src/metadata.js index cfc6c046f2..4949bad9fe 100644 --- a/packages/docusaurus-plugin-content-docs-legacy/src/metadata.js +++ b/packages/docusaurus-plugin-content-docs-legacy/src/metadata.js @@ -11,12 +11,14 @@ const {parse, normalizeUrl} = require('@docusaurus/utils'); module.exports = async function processMetadata( source, - refDir, + docsDir, order, siteConfig, docsBasePath, + siteDir, ) { - const filepath = path.resolve(refDir, source); + const filepath = path.join(docsDir, source); + const fileString = await fs.readFile(filepath, 'utf-8'); const {frontMatter: metadata = {}, excerpt} = parse(fileString); @@ -24,6 +26,7 @@ module.exports = async function processMetadata( if (!metadata.id) { metadata.id = path.basename(source, path.extname(source)); } + if (metadata.id.includes('/')) { throw new Error('Document id cannot include "/".'); } @@ -45,9 +48,9 @@ module.exports = async function processMetadata( } } - // The docs absolute file source. - // e.g: `/end/docs/hello.md` or `/end/website/versioned_docs/version-1.0.0/hello.md` - metadata.source = path.join(refDir, source); + // Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it. + const aliasedPath = `@site/${path.relative(siteDir, filepath)}`; + metadata.source = aliasedPath; // Build the permalink. const {baseUrl} = siteConfig; diff --git a/packages/docusaurus-plugin-content-pages/src/index.js b/packages/docusaurus-plugin-content-pages/src/index.js index 7982a4b436..1c47cd4d16 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.js +++ b/packages/docusaurus-plugin-content-pages/src/index.js @@ -47,10 +47,8 @@ module.exports = function(context, opts) { return pagesFiles.map(relativeSource => { const source = path.join(pagesDir, relativeSource); - const aliasedSource = path.join( - '@site', - path.relative(siteDir, source), - ); + // Cannot use path.join() as it resolves '../' and removes the '@site'. Let webpack loader resolve it. + const aliasedSource = `@site/${path.relative(siteDir, source)}`; const pathName = encodePath(fileToPath(relativeSource)); // Default Language. return { diff --git a/website/docs/installation.md b/website/docs/installation.md index e0bcba7a26..cea3249522 100644 --- a/website/docs/installation.md +++ b/website/docs/installation.md @@ -61,7 +61,7 @@ my-website ### Project structure rundown - `/blog/` - Contains the blog markdown files. You can delete the directory if you do not want/need a blog. More details can be found in the [blog guide](blog.md). -- `/docs/` - Contains the markdown files for the docs. Customize the order of the docs sidebar in `sidebars.js`. More details can be found in the [docs guide](markdown-features). +- `/docs/` - Contains the markdown files for the docs. Customize the order of the docs sidebar in `sidebars.js`. More details can be found in the [docs guide](markdown-features.mdx). - `/src/` - Non-documentation files like pages or custom React components. You don't have to strictly put your non-documentation files in here but putting them under a centralized directory makes it easier to specify in case you need to do some sort of linting/processing - `/src/pages` - Any files within this directory will be converted into a website page. More details can be found in the [pages guide](creating-pages.md). - `/static/` - Static directory. Any contents inside here will be copied into the root of the final `build` directory.