123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- 'use strict'
- const fs = require('../fs')
- const path = require('path')
- const util = require('util')
- const atLeastNode = require('at-least-node')
- const nodeSupportsBigInt = atLeastNode('10.5.0')
- const stat = (file) => nodeSupportsBigInt ? fs.stat(file, { bigint: true }) : fs.stat(file)
- const statSync = (file) => nodeSupportsBigInt ? fs.statSync(file, { bigint: true }) : fs.statSync(file)
- function getStats (src, dest) {
- return Promise.all([
- stat(src),
- stat(dest).catch(err => {
- if (err.code === 'ENOENT') return null
- throw err
- })
- ]).then(([srcStat, destStat]) => ({ srcStat, destStat }))
- }
- function getStatsSync (src, dest) {
- let destStat
- const srcStat = statSync(src)
- try {
- destStat = statSync(dest)
- } catch (err) {
- if (err.code === 'ENOENT') return { srcStat, destStat: null }
- throw err
- }
- return { srcStat, destStat }
- }
- function checkPaths (src, dest, funcName, cb) {
- util.callbackify(getStats)(src, dest, (err, stats) => {
- if (err) return cb(err)
- const { srcStat, destStat } = stats
- if (destStat && areIdentical(srcStat, destStat)) {
- 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 && areIdentical(srcStat, destStat)) {
- 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()
- const callback = (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)
- }
- if (nodeSupportsBigInt) fs.stat(destParent, { bigint: true }, callback)
- else fs.stat(destParent, callback)
- }
- 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 = statSync(destParent)
- } 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) {
- if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
- if (nodeSupportsBigInt || destStat.ino < Number.MAX_SAFE_INTEGER) {
- // definitive answer
- return true
- }
- // Use additional heuristics if we can't use 'bigint'.
- // Different 'ino' could be represented the same if they are >= Number.MAX_SAFE_INTEGER
- // See issue 657
- if (destStat.size === srcStat.size &&
- destStat.mode === srcStat.mode &&
- destStat.nlink === srcStat.nlink &&
- destStat.atimeMs === srcStat.atimeMs &&
- destStat.mtimeMs === srcStat.mtimeMs &&
- destStat.ctimeMs === srcStat.ctimeMs &&
- destStat.birthtimeMs === srcStat.birthtimeMs) {
- // heuristic answer
- return true
- }
- }
- return false
- }
- // 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
- }
|