stat.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. 'use strict'
  2. const fs = require('graceful-fs')
  3. const path = require('path')
  4. const NODE_VERSION_MAJOR_WITH_BIGINT = 10
  5. const NODE_VERSION_MINOR_WITH_BIGINT = 5
  6. const NODE_VERSION_PATCH_WITH_BIGINT = 0
  7. const nodeVersion = process.versions.node.split('.')
  8. const nodeVersionMajor = Number.parseInt(nodeVersion[0], 10)
  9. const nodeVersionMinor = Number.parseInt(nodeVersion[1], 10)
  10. const nodeVersionPatch = Number.parseInt(nodeVersion[2], 10)
  11. function nodeSupportsBigInt () {
  12. if (nodeVersionMajor > NODE_VERSION_MAJOR_WITH_BIGINT) {
  13. return true
  14. } else if (nodeVersionMajor === NODE_VERSION_MAJOR_WITH_BIGINT) {
  15. if (nodeVersionMinor > NODE_VERSION_MINOR_WITH_BIGINT) {
  16. return true
  17. } else if (nodeVersionMinor === NODE_VERSION_MINOR_WITH_BIGINT) {
  18. if (nodeVersionPatch >= NODE_VERSION_PATCH_WITH_BIGINT) {
  19. return true
  20. }
  21. }
  22. }
  23. return false
  24. }
  25. function getStats (src, dest, cb) {
  26. if (nodeSupportsBigInt()) {
  27. fs.stat(src, { bigint: true }, (err, srcStat) => {
  28. if (err) return cb(err)
  29. fs.stat(dest, { bigint: true }, (err, destStat) => {
  30. if (err) {
  31. if (err.code === 'ENOENT') return cb(null, { srcStat, destStat: null })
  32. return cb(err)
  33. }
  34. return cb(null, { srcStat, destStat })
  35. })
  36. })
  37. } else {
  38. fs.stat(src, (err, srcStat) => {
  39. if (err) return cb(err)
  40. fs.stat(dest, (err, destStat) => {
  41. if (err) {
  42. if (err.code === 'ENOENT') return cb(null, { srcStat, destStat: null })
  43. return cb(err)
  44. }
  45. return cb(null, { srcStat, destStat })
  46. })
  47. })
  48. }
  49. }
  50. function getStatsSync (src, dest) {
  51. let srcStat, destStat
  52. if (nodeSupportsBigInt()) {
  53. srcStat = fs.statSync(src, { bigint: true })
  54. } else {
  55. srcStat = fs.statSync(src)
  56. }
  57. try {
  58. if (nodeSupportsBigInt()) {
  59. destStat = fs.statSync(dest, { bigint: true })
  60. } else {
  61. destStat = fs.statSync(dest)
  62. }
  63. } catch (err) {
  64. if (err.code === 'ENOENT') return { srcStat, destStat: null }
  65. throw err
  66. }
  67. return { srcStat, destStat }
  68. }
  69. function checkPaths (src, dest, funcName, cb) {
  70. getStats(src, dest, (err, stats) => {
  71. if (err) return cb(err)
  72. const { srcStat, destStat } = stats
  73. if (destStat && destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
  74. return cb(new Error('Source and destination must not be the same.'))
  75. }
  76. if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
  77. return cb(new Error(errMsg(src, dest, funcName)))
  78. }
  79. return cb(null, { srcStat, destStat })
  80. })
  81. }
  82. function checkPathsSync (src, dest, funcName) {
  83. const { srcStat, destStat } = getStatsSync(src, dest)
  84. if (destStat && destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
  85. throw new Error('Source and destination must not be the same.')
  86. }
  87. if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
  88. throw new Error(errMsg(src, dest, funcName))
  89. }
  90. return { srcStat, destStat }
  91. }
  92. // recursively check if dest parent is a subdirectory of src.
  93. // It works for all file types including symlinks since it
  94. // checks the src and dest inodes. It starts from the deepest
  95. // parent and stops once it reaches the src parent or the root path.
  96. function checkParentPaths (src, srcStat, dest, funcName, cb) {
  97. const srcParent = path.resolve(path.dirname(src))
  98. const destParent = path.resolve(path.dirname(dest))
  99. if (destParent === srcParent || destParent === path.parse(destParent).root) return cb()
  100. if (nodeSupportsBigInt()) {
  101. fs.stat(destParent, { bigint: true }, (err, destStat) => {
  102. if (err) {
  103. if (err.code === 'ENOENT') return cb()
  104. return cb(err)
  105. }
  106. if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
  107. return cb(new Error(errMsg(src, dest, funcName)))
  108. }
  109. return checkParentPaths(src, srcStat, destParent, funcName, cb)
  110. })
  111. } else {
  112. fs.stat(destParent, (err, destStat) => {
  113. if (err) {
  114. if (err.code === 'ENOENT') return cb()
  115. return cb(err)
  116. }
  117. if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
  118. return cb(new Error(errMsg(src, dest, funcName)))
  119. }
  120. return checkParentPaths(src, srcStat, destParent, funcName, cb)
  121. })
  122. }
  123. }
  124. function checkParentPathsSync (src, srcStat, dest, funcName) {
  125. const srcParent = path.resolve(path.dirname(src))
  126. const destParent = path.resolve(path.dirname(dest))
  127. if (destParent === srcParent || destParent === path.parse(destParent).root) return
  128. let destStat
  129. try {
  130. if (nodeSupportsBigInt()) {
  131. destStat = fs.statSync(destParent, { bigint: true })
  132. } else {
  133. destStat = fs.statSync(destParent)
  134. }
  135. } catch (err) {
  136. if (err.code === 'ENOENT') return
  137. throw err
  138. }
  139. if (destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev) {
  140. throw new Error(errMsg(src, dest, funcName))
  141. }
  142. return checkParentPathsSync(src, srcStat, destParent, funcName)
  143. }
  144. // return true if dest is a subdir of src, otherwise false.
  145. // It only checks the path strings.
  146. function isSrcSubdir (src, dest) {
  147. const srcArr = path.resolve(src).split(path.sep).filter(i => i)
  148. const destArr = path.resolve(dest).split(path.sep).filter(i => i)
  149. return srcArr.reduce((acc, cur, i) => acc && destArr[i] === cur, true)
  150. }
  151. function errMsg (src, dest, funcName) {
  152. return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.`
  153. }
  154. module.exports = {
  155. checkPaths,
  156. checkPathsSync,
  157. checkParentPaths,
  158. checkParentPathsSync,
  159. isSrcSubdir
  160. }