You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					173 lines
				
				5.6 KiB
			
		
		
			
		
	
	
					173 lines
				
				5.6 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const fs = require('graceful-fs')
							 | 
						||
| 
								 | 
							
								const path = require('path')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const NODE_VERSION_MAJOR_WITH_BIGINT = 10
							 | 
						||
| 
								 | 
							
								const NODE_VERSION_MINOR_WITH_BIGINT = 5
							 | 
						||
| 
								 | 
							
								const NODE_VERSION_PATCH_WITH_BIGINT = 0
							 | 
						||
| 
								 | 
							
								const nodeVersion = process.versions.node.split('.')
							 | 
						||
| 
								 | 
							
								const nodeVersionMajor = Number.parseInt(nodeVersion[0], 10)
							 | 
						||
| 
								 | 
							
								const nodeVersionMinor = Number.parseInt(nodeVersion[1], 10)
							 | 
						||
| 
								 | 
							
								const nodeVersionPatch = Number.parseInt(nodeVersion[2], 10)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function nodeSupportsBigInt () {
							 | 
						||
| 
								 | 
							
								  if (nodeVersionMajor > NODE_VERSION_MAJOR_WITH_BIGINT) {
							 | 
						||
| 
								 | 
							
								    return true
							 | 
						||
| 
								 | 
							
								  } else if (nodeVersionMajor === NODE_VERSION_MAJOR_WITH_BIGINT) {
							 | 
						||
| 
								 | 
							
								    if (nodeVersionMinor > NODE_VERSION_MINOR_WITH_BIGINT) {
							 | 
						||
| 
								 | 
							
								      return true
							 | 
						||
| 
								 | 
							
								    } else if (nodeVersionMinor === NODE_VERSION_MINOR_WITH_BIGINT) {
							 | 
						||
| 
								 | 
							
								      if (nodeVersionPatch >= NODE_VERSION_PATCH_WITH_BIGINT) {
							 | 
						||
| 
								 | 
							
								        return true
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return false
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getStats (src, dest, cb) {
							 | 
						||
| 
								 | 
							
								  if (nodeSupportsBigInt()) {
							 | 
						||
| 
								 | 
							
								    fs.stat(src, { bigint: true }, (err, srcStat) => {
							 | 
						||
| 
								 | 
							
								      if (err) return cb(err)
							 | 
						||
| 
								 | 
							
								      fs.stat(dest, { bigint: true }, (err, destStat) => {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          if (err.code === 'ENOENT') return cb(null, { srcStat, destStat: null })
							 | 
						||
| 
								 | 
							
								          return cb(err)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return cb(null, { srcStat, destStat })
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    fs.stat(src, (err, srcStat) => {
							 | 
						||
| 
								 | 
							
								      if (err) return cb(err)
							 | 
						||
| 
								 | 
							
								      fs.stat(dest, (err, destStat) => {
							 | 
						||
| 
								 | 
							
								        if (err) {
							 | 
						||
| 
								 | 
							
								          if (err.code === 'ENOENT') return cb(null, { srcStat, destStat: null })
							 | 
						||
| 
								 | 
							
								          return cb(err)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        return cb(null, { srcStat, destStat })
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function getStatsSync (src, dest) {
							 | 
						||
| 
								 | 
							
								  let srcStat, destStat
							 | 
						||
| 
								 | 
							
								  if (nodeSupportsBigInt()) {
							 | 
						||
| 
								 | 
							
								    srcStat = fs.statSync(src, { bigint: true })
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    srcStat = fs.statSync(src)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    if (nodeSupportsBigInt()) {
							 | 
						||
| 
								 | 
							
								      destStat = fs.statSync(dest, { bigint: true })
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      destStat = fs.statSync(dest)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } catch (err) {
							 | 
						||
| 
								 | 
							
								    if (err.code === 'ENOENT') return { srcStat, destStat: null }
							 | 
						||
| 
								 | 
							
								    throw err
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return { srcStat, destStat }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function checkPaths (src, dest, funcName, cb) {
							 | 
						||
| 
								 | 
							
								  getStats(src, dest, (err, stats) => {
							 | 
						||
| 
								 | 
							
								    if (err) return cb(err)
							 | 
						||
| 
								 | 
							
								    const { srcStat, destStat } = stats
							 | 
						||
| 
								 | 
							
								    if (destStat && destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
							 | 
						||
| 
								 | 
							
								      return cb(new Error('Source and destination must not be the same.'))
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
							 | 
						||
| 
								 | 
							
								      return cb(new Error(errMsg(src, dest, funcName)))
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return cb(null, { srcStat, destStat })
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function checkPathsSync (src, dest, funcName) {
							 | 
						||
| 
								 | 
							
								  const { srcStat, destStat } = getStatsSync(src, dest)
							 | 
						||
| 
								 | 
							
								  if (destStat && destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Source and destination must not be the same.')
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
							 | 
						||
| 
								 | 
							
								    throw new Error(errMsg(src, dest, funcName))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return { srcStat, destStat }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// recursively check if dest parent is a subdirectory of src.
							 | 
						||
| 
								 | 
							
								// It works for all file types including symlinks since it
							 | 
						||
| 
								 | 
							
								// checks the src and dest inodes. It starts from the deepest
							 | 
						||
| 
								 | 
							
								// parent and stops once it reaches the src parent or the root path.
							 | 
						||
| 
								 | 
							
								function checkParentPaths (src, srcStat, dest, funcName, cb) {
							 | 
						||
| 
								 | 
							
								  const srcParent = path.resolve(path.dirname(src))
							 | 
						||
| 
								 | 
							
								  const destParent = path.resolve(path.dirname(dest))
							 | 
						||
| 
								 | 
							
								  if (destParent === srcParent || destParent === path.parse(destParent).root) return cb()
							 | 
						||
| 
								 | 
							
								  if (nodeSupportsBigInt()) {
							 | 
						||
| 
								 | 
							
								    fs.stat(destParent, { bigint: true }, (err, destStat) => {
							 | 
						||
| 
								 | 
							
								      if (err) {
							 | 
						||
| 
								 | 
							
								        if (err.code === 'ENOENT') return cb()
							 | 
						||
| 
								 | 
							
								        return cb(err)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
							 | 
						||
| 
								 | 
							
								        return cb(new Error(errMsg(src, dest, funcName)))
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return checkParentPaths(src, srcStat, destParent, funcName, cb)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    fs.stat(destParent, (err, destStat) => {
							 | 
						||
| 
								 | 
							
								      if (err) {
							 | 
						||
| 
								 | 
							
								        if (err.code === 'ENOENT') return cb()
							 | 
						||
| 
								 | 
							
								        return cb(err)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
							 | 
						||
| 
								 | 
							
								        return cb(new Error(errMsg(src, dest, funcName)))
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return checkParentPaths(src, srcStat, destParent, funcName, cb)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function checkParentPathsSync (src, srcStat, dest, funcName) {
							 | 
						||
| 
								 | 
							
								  const srcParent = path.resolve(path.dirname(src))
							 | 
						||
| 
								 | 
							
								  const destParent = path.resolve(path.dirname(dest))
							 | 
						||
| 
								 | 
							
								  if (destParent === srcParent || destParent === path.parse(destParent).root) return
							 | 
						||
| 
								 | 
							
								  let destStat
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    if (nodeSupportsBigInt()) {
							 | 
						||
| 
								 | 
							
								      destStat = fs.statSync(destParent, { bigint: true })
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      destStat = fs.statSync(destParent)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } catch (err) {
							 | 
						||
| 
								 | 
							
								    if (err.code === 'ENOENT') return
							 | 
						||
| 
								 | 
							
								    throw err
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
							 | 
						||
| 
								 | 
							
								    throw new Error(errMsg(src, dest, funcName))
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return checkParentPathsSync(src, srcStat, destParent, funcName)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// return true if dest is a subdir of src, otherwise false.
							 | 
						||
| 
								 | 
							
								// It only checks the path strings.
							 | 
						||
| 
								 | 
							
								function isSrcSubdir (src, dest) {
							 | 
						||
| 
								 | 
							
								  const srcArr = path.resolve(src).split(path.sep).filter(i => i)
							 | 
						||
| 
								 | 
							
								  const destArr = path.resolve(dest).split(path.sep).filter(i => i)
							 | 
						||
| 
								 | 
							
								  return srcArr.reduce((acc, cur, i) => acc && destArr[i] === cur, true)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function errMsg (src, dest, funcName) {
							 | 
						||
| 
								 | 
							
								  return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.`
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = {
							 | 
						||
| 
								 | 
							
								  checkPaths,
							 | 
						||
| 
								 | 
							
								  checkPathsSync,
							 | 
						||
| 
								 | 
							
								  checkParentPaths,
							 | 
						||
| 
								 | 
							
								  checkParentPathsSync,
							 | 
						||
| 
								 | 
							
								  isSrcSubdir
							 | 
						||
| 
								 | 
							
								}
							 |