| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 | 'use strict'const fs = require('../fs')const path = require('path')const util = require('util')function getStats (src, dest, opts) {  const statFunc = opts.dereference    ? (file) => fs.stat(file, { bigint: true })    : (file) => fs.lstat(file, { bigint: true })  return Promise.all([    statFunc(src),    statFunc(dest).catch(err => {      if (err.code === 'ENOENT') return null      throw err    })  ]).then(([srcStat, destStat]) => ({ srcStat, destStat }))}function getStatsSync (src, dest, opts) {  let destStat  const statFunc = opts.dereference    ? (file) => fs.statSync(file, { bigint: true })    : (file) => fs.lstatSync(file, { bigint: true })  const srcStat = statFunc(src)  try {    destStat = statFunc(dest)  } catch (err) {    if (err.code === 'ENOENT') return { srcStat, destStat: null }    throw err  }  return { srcStat, destStat }}function checkPaths (src, dest, funcName, opts, cb) {  util.callbackify(getStats)(src, dest, opts, (err, stats) => {    if (err) return cb(err)    const { srcStat, destStat } = stats    if (destStat) {      if (areIdentical(srcStat, destStat)) {        const srcBaseName = path.basename(src)        const destBaseName = path.basename(dest)        if (funcName === 'move' &&          srcBaseName !== destBaseName &&          srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {          return cb(null, { srcStat, destStat, isChangingCase: true })        }        return cb(new Error('Source and destination must not be the same.'))      }      if (srcStat.isDirectory() && !destStat.isDirectory()) {        return cb(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`))      }      if (!srcStat.isDirectory() && destStat.isDirectory()) {        return cb(new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`))      }    }    if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {      return cb(new Error(errMsg(src, dest, funcName)))    }    return cb(null, { srcStat, destStat })  })}function checkPathsSync (src, dest, funcName, opts) {  const { srcStat, destStat } = getStatsSync(src, dest, opts)  if (destStat) {    if (areIdentical(srcStat, destStat)) {      const srcBaseName = path.basename(src)      const destBaseName = path.basename(dest)      if (funcName === 'move' &&        srcBaseName !== destBaseName &&        srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {        return { srcStat, destStat, isChangingCase: true }      }      throw new Error('Source and destination must not be the same.')    }    if (srcStat.isDirectory() && !destStat.isDirectory()) {      throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)    }    if (!srcStat.isDirectory() && destStat.isDirectory()) {      throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`)    }  }  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()  fs.stat(destParent, { bigint: true }, (err, destStat) => {    if (err) {      if (err.code === 'ENOENT') return cb()      return cb(err)    }    if (areIdentical(srcStat, destStat)) {      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 {    destStat = fs.statSync(destParent, { bigint: true })  } catch (err) {    if (err.code === 'ENOENT') return    throw err  }  if (areIdentical(srcStat, destStat)) {    throw new Error(errMsg(src, dest, funcName))  }  return checkParentPathsSync(src, srcStat, destParent, funcName)}function areIdentical (srcStat, destStat) {  return destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev}// 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,  areIdentical}
 |