fix: prevent removing project by mistake due to build or generate paths (#3869)

This commit is contained in:
Pim 2018-09-14 09:06:44 +02:00 committed by Pooya Parsa
parent 40ad691f60
commit 226b90d4ae
5 changed files with 116 additions and 6 deletions

View File

@ -15,7 +15,7 @@ export default class Generator {
// Set variables // Set variables
this.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static) this.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static)
this.srcBuiltPath = path.resolve(this.options.buildDir, 'dist', 'client') this.srcBuiltPath = path.resolve(this.options.buildDir, 'dist', 'client')
this.distPath = path.resolve(this.options.rootDir, this.options.generate.dir) this.distPath = this.options.generate.dir
this.distNuxtPath = path.join( this.distNuxtPath = path.join(
this.distPath, this.distPath,
isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath

View File

@ -3,11 +3,14 @@ import fs from 'fs'
import _ from 'lodash' import _ from 'lodash'
import consola from 'consola' import consola from 'consola'
import { isPureObject, isUrl } from '../common/utils' import { isPureObject, isUrl, guardDir } from '../common/utils'
import modes from './modes' import modes from './modes'
import defaults from './nuxt.config' import defaults from './nuxt.config'
// hasValue utility
const hasValue = v => typeof v === 'string' && v
const Options = {} const Options = {}
export default Options export default Options
@ -40,8 +43,8 @@ Options.from = function (_options) {
options.extensions = [options.extensions] options.extensions = [options.extensions]
} }
const hasValue = v => typeof v === 'string' && v // Resolve rootDir
options.rootDir = hasValue(options.rootDir) ? options.rootDir : process.cwd() options.rootDir = hasValue(options.rootDir) ? path.resolve(options.rootDir) : process.cwd()
// Apply defaults by ${buildDir}/dist/build.config.js // Apply defaults by ${buildDir}/dist/build.config.js
// TODO: Unsafe operation. // TODO: Unsafe operation.
@ -54,12 +57,39 @@ Options.from = function (_options) {
// Apply defaults // Apply defaults
_.defaultsDeep(options, defaults) _.defaultsDeep(options, defaults)
// Resolve dirs // Check srcDir and generate.dir excistence
options.srcDir = hasValue(options.srcDir) const hasSrcDir = hasValue(options.srcDir)
const hasGenerateDir = hasValue(options.generate.dir)
// Resolve srcDir
options.srcDir = hasSrcDir
? path.resolve(options.rootDir, options.srcDir) ? path.resolve(options.rootDir, options.srcDir)
: options.rootDir : options.rootDir
// Resolve buildDir
options.buildDir = path.resolve(options.rootDir, options.buildDir) options.buildDir = path.resolve(options.rootDir, options.buildDir)
// Protect rootDir against buildDir
guardDir(options, 'rootDir', 'buildDir')
if (hasGenerateDir) {
// Resolve generate.dir
options.generate.dir = path.resolve(options.rootDir, options.generate.dir)
// Protect rootDir against buildDir
guardDir(options, 'rootDir', 'generate.dir')
}
if (hasSrcDir) {
// Protect srcDir against buildDir
guardDir(options, 'srcDir', 'buildDir')
if (hasGenerateDir) {
// Protect srcDir against generate.dir
guardDir(options, 'srcDir', 'generate.dir')
}
}
// Populate modulesDir // Populate modulesDir
options.modulesDir = [] options.modulesDir = []
.concat(options.modulesDir) .concat(options.modulesDir)

View File

@ -1,5 +1,6 @@
import path from 'path' import path from 'path'
import _ from 'lodash' import _ from 'lodash'
import consola from 'consola'
export const encodeHtml = function encodeHtml(str) { export const encodeHtml = function encodeHtml(str) {
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;') return str.replace(/</g, '&lt;').replace(/>/g, '&gt;')
@ -326,3 +327,25 @@ export const createRoutes = function createRoutes(files, srcDir, pagesDir) {
}) })
return cleanChildrenRoutes(routes) return cleanChildrenRoutes(routes)
} }
// Guard dir1 from dir2 which can be indiscriminately removed
export const guardDir = function guardDir(options, key1, key2) {
const dir1 = _.get(options, key1, false)
const dir2 = _.get(options, key2, false)
if (
dir1 &&
dir2 &&
(
dir1 === dir2 ||
(
dir1.startsWith(dir2) &&
!path.basename(dir1).startsWith(path.basename(dir2))
)
)
) {
const errorMessage = `options.${key2} cannot be a parent of or same as ${key1}`
consola.fatal(errorMessage)
throw new Error(errorMessage)
}
}

View File

@ -17,4 +17,14 @@ describe('basic fail generate', () => {
expect(e.message).toBe('Not today!') expect(e.message).toBe('Not today!')
}) })
}) })
test('Fail when generate.dir equals rootDir', async () => {
const options = await loadFixture('basic', {
generate: { dir: '../basic' }
})
expect(() => {
new Nuxt(options) /* eslint-disable-line no-new */
}).toThrow(/options.generate.dir cannot be/)
})
}) })

View File

@ -236,6 +236,53 @@ describe('utils', () => {
.toBe(Utils.wp(`loader1!loader2!..${path.sep}baz`)) .toBe(Utils.wp(`loader1!loader2!..${path.sep}baz`))
}) })
}) })
describe('guardDir', () => {
test('Parent dir is guarded', () => {
expect(() => {
Utils.guardDir({
dir1: '/root/parent',
dir2: '/root'
}, 'dir1', 'dir2')
}).toThrow()
})
test('Same dir is guarded', () => {
expect(() => {
Utils.guardDir({
dir1: '/root/parent',
dir2: '/root/parent'
}, 'dir1', 'dir2')
}).toThrow()
})
test('Same level dir is not guarded', () => {
expect(() => {
Utils.guardDir({
dir1: '/root/parent-next',
dir2: '/root/parent'
}, 'dir1', 'dir2')
}).not.toThrow()
})
test('Same level dir is not guarded 2', () => {
expect(() => {
Utils.guardDir({
dir1: '/root/parent',
dir2: '/root/parent-next'
}, 'dir1', 'dir2')
}).not.toThrow()
})
test('Child dir is not guarded', () => {
expect(() => {
Utils.guardDir({
dir1: '/root/parent',
dir2: '/root/parent/child'
}, 'dir1', 'dir2')
}).not.toThrow()
})
})
}) })
test('createRoutes should allow snake case routes', () => { test('createRoutes should allow snake case routes', () => {