123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- '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
- }
|