123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- 'use strict'
- const fs = require('../fs')
- const path = require('path')
- const u = require('universalify').fromPromise
- 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 }
- }
- async function checkPaths (src, dest, funcName, opts) {
- const { srcStat, destStat } = await getStats(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 }
- }
- 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.
- async function checkParentPaths (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 = await fs.stat(destParent, { bigint: true })
- } catch (err) {
- if (err.code === 'ENOENT') return
- throw err
- }
- if (areIdentical(srcStat, destStat)) {
- throw new Error(errMsg(src, dest, funcName))
- }
- return checkParentPaths(src, srcStat, destParent, funcName)
- }
- 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.every((cur, i) => destArr[i] === cur)
- }
- function errMsg (src, dest, funcName) {
- return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.`
- }
- module.exports = {
- // checkPaths
- checkPaths: u(checkPaths),
- checkPathsSync,
- // checkParent
- checkParentPaths: u(checkParentPaths),
- checkParentPathsSync,
- // Misc
- isSrcSubdir,
- areIdentical
- }
|