asar.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. 'use strict';
  2. (function () {
  3. const asar = process._linkedBinding('atom_common_asar')
  4. const v8Util = process._linkedBinding('atom_common_v8_util')
  5. const { Buffer } = require('buffer')
  6. const Module = require('module')
  7. const path = require('path')
  8. const util = require('util')
  9. const envNoAsar = process.env.ELECTRON_NO_ASAR &&
  10. process.type !== 'browser' &&
  11. process.type !== 'renderer'
  12. const isAsarDisabled = () => process.noAsar || envNoAsar
  13. const internalBinding = process.internalBinding
  14. delete process.internalBinding
  15. /**
  16. * @param {!Function} functionToCall
  17. * @param {!Array|undefined} args
  18. */
  19. const nextTick = (functionToCall, args = []) => {
  20. process.nextTick(() => functionToCall(...args))
  21. }
  22. // Cache asar archive objects.
  23. const cachedArchives = new Map()
  24. const getOrCreateArchive = archivePath => {
  25. const isCached = cachedArchives.has(archivePath)
  26. if (isCached) {
  27. return cachedArchives.get(archivePath)
  28. }
  29. const newArchive = asar.createArchive(archivePath)
  30. if (!newArchive) return null
  31. cachedArchives.set(archivePath, newArchive)
  32. return newArchive
  33. }
  34. // Separate asar package's path from full path.
  35. const splitPath = archivePathOrBuffer => {
  36. // Shortcut for disabled asar.
  37. if (isAsarDisabled()) return { isAsar: false }
  38. // Check for a bad argument type.
  39. let archivePath = archivePathOrBuffer
  40. if (Buffer.isBuffer(archivePathOrBuffer)) {
  41. archivePath = archivePathOrBuffer.toString()
  42. }
  43. if (typeof archivePath !== 'string') return { isAsar: false }
  44. return asar.splitPath(path.normalize(archivePath))
  45. }
  46. // Convert asar archive's Stats object to fs's Stats object.
  47. let nextInode = 0
  48. const uid = process.getuid != null ? process.getuid() : 0
  49. const gid = process.getgid != null ? process.getgid() : 0
  50. const fakeTime = new Date()
  51. const msec = (date) => (date || fakeTime).getTime()
  52. const asarStatsToFsStats = function (stats) {
  53. const { Stats, constants } = require('fs')
  54. let mode = constants.S_IROTH ^ constants.S_IRGRP ^ constants.S_IRUSR ^ constants.S_IWUSR
  55. if (stats.isFile) {
  56. mode ^= constants.S_IFREG
  57. } else if (stats.isDirectory) {
  58. mode ^= constants.S_IFDIR
  59. } else if (stats.isLink) {
  60. mode ^= constants.S_IFLNK
  61. }
  62. return new Stats(
  63. 1, // dev
  64. mode, // mode
  65. 1, // nlink
  66. uid,
  67. gid,
  68. 0, // rdev
  69. undefined, // blksize
  70. ++nextInode, // ino
  71. stats.size,
  72. undefined, // blocks,
  73. msec(stats.atime), // atim_msec
  74. msec(stats.mtime), // mtim_msec
  75. msec(stats.ctime), // ctim_msec
  76. msec(stats.birthtime) // birthtim_msec
  77. )
  78. }
  79. const AsarError = {
  80. NOT_FOUND: 'NOT_FOUND',
  81. NOT_DIR: 'NOT_DIR',
  82. NO_ACCESS: 'NO_ACCESS',
  83. INVALID_ARCHIVE: 'INVALID_ARCHIVE'
  84. }
  85. const createError = (errorType, { asarPath, filePath } = {}) => {
  86. let error
  87. switch (errorType) {
  88. case AsarError.NOT_FOUND:
  89. error = new Error(`ENOENT, ${filePath} not found in ${asarPath}`)
  90. error.code = 'ENOENT'
  91. error.errno = -2
  92. break
  93. case AsarError.NOT_DIR:
  94. error = new Error('ENOTDIR, not a directory')
  95. error.code = 'ENOTDIR'
  96. error.errno = -20
  97. break
  98. case AsarError.NO_ACCESS:
  99. error = new Error(`EACCES: permission denied, access '${filePath}'`)
  100. error.code = 'EACCES'
  101. error.errno = -13
  102. break
  103. case AsarError.INVALID_ARCHIVE:
  104. error = new Error(`Invalid package ${asarPath}`)
  105. break
  106. default:
  107. throw new Error(`Invalid error type "${errorType}" passed to createError.`)
  108. }
  109. return error
  110. }
  111. const overrideAPISync = function (module, name, pathArgumentIndex, fromAsync) {
  112. if (pathArgumentIndex == null) pathArgumentIndex = 0
  113. const old = module[name]
  114. const func = function () {
  115. const pathArgument = arguments[pathArgumentIndex]
  116. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  117. if (!isAsar) return old.apply(this, arguments)
  118. const archive = getOrCreateArchive(asarPath)
  119. if (!archive) throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  120. const newPath = archive.copyFileOut(filePath)
  121. if (!newPath) throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  122. arguments[pathArgumentIndex] = newPath
  123. return old.apply(this, arguments)
  124. }
  125. if (fromAsync) {
  126. return func
  127. }
  128. module[name] = func
  129. }
  130. const overrideAPI = function (module, name, pathArgumentIndex) {
  131. if (pathArgumentIndex == null) pathArgumentIndex = 0
  132. const old = module[name]
  133. module[name] = function () {
  134. const pathArgument = arguments[pathArgumentIndex]
  135. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  136. if (!isAsar) return old.apply(this, arguments)
  137. const callback = arguments[arguments.length - 1]
  138. if (typeof callback !== 'function') {
  139. return overrideAPISync(module, name, pathArgumentIndex, true).apply(this, arguments)
  140. }
  141. const archive = getOrCreateArchive(asarPath)
  142. if (!archive) {
  143. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  144. nextTick(callback, [error])
  145. return
  146. }
  147. const newPath = archive.copyFileOut(filePath)
  148. if (!newPath) {
  149. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  150. nextTick(callback, [error])
  151. return
  152. }
  153. arguments[pathArgumentIndex] = newPath
  154. return old.apply(this, arguments)
  155. }
  156. if (old[util.promisify.custom]) {
  157. module[name][util.promisify.custom] = makePromiseFunction(old[util.promisify.custom], pathArgumentIndex)
  158. }
  159. if (module.promises && module.promises[name]) {
  160. module.promises[name] = makePromiseFunction(module.promises[name], pathArgumentIndex)
  161. }
  162. }
  163. const makePromiseFunction = function (orig, pathArgumentIndex) {
  164. return function (...args) {
  165. const pathArgument = args[pathArgumentIndex]
  166. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  167. if (!isAsar) return orig.apply(this, args)
  168. const archive = getOrCreateArchive(asarPath)
  169. if (!archive) {
  170. return Promise.reject(createError(AsarError.INVALID_ARCHIVE, { asarPath }))
  171. }
  172. const newPath = archive.copyFileOut(filePath)
  173. if (!newPath) {
  174. return Promise.reject(createError(AsarError.NOT_FOUND, { asarPath, filePath }))
  175. }
  176. args[pathArgumentIndex] = newPath
  177. return orig.apply(this, args)
  178. }
  179. }
  180. // Override fs APIs.
  181. exports.wrapFsWithAsar = fs => {
  182. const logFDs = {}
  183. const logASARAccess = (asarPath, filePath, offset) => {
  184. if (!process.env.ELECTRON_LOG_ASAR_READS) return
  185. if (!logFDs[asarPath]) {
  186. const path = require('path')
  187. const logFilename = `${path.basename(asarPath, '.asar')}-access-log.txt`
  188. const logPath = path.join(require('os').tmpdir(), logFilename)
  189. logFDs[asarPath] = fs.openSync(logPath, 'a')
  190. }
  191. fs.writeSync(logFDs[asarPath], `${offset}: ${filePath}\n`)
  192. }
  193. const { lstatSync } = fs
  194. fs.lstatSync = (pathArgument, options) => {
  195. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  196. if (!isAsar) return lstatSync(pathArgument, options)
  197. const archive = getOrCreateArchive(asarPath)
  198. if (!archive) throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  199. const stats = archive.stat(filePath)
  200. if (!stats) throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  201. return asarStatsToFsStats(stats)
  202. }
  203. const { lstat } = fs
  204. fs.lstat = function (pathArgument, options, callback) {
  205. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  206. if (typeof options === 'function') {
  207. callback = options
  208. options = {}
  209. }
  210. if (!isAsar) return lstat(pathArgument, options, callback)
  211. const archive = getOrCreateArchive(asarPath)
  212. if (!archive) {
  213. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  214. nextTick(callback, [error])
  215. return
  216. }
  217. const stats = archive.stat(filePath)
  218. if (!stats) {
  219. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  220. nextTick(callback, [error])
  221. return
  222. }
  223. const fsStats = asarStatsToFsStats(stats)
  224. nextTick(callback, [null, fsStats])
  225. }
  226. fs.promises.lstat = util.promisify(fs.lstat)
  227. const { statSync } = fs
  228. fs.statSync = (pathArgument, options) => {
  229. const { isAsar } = splitPath(pathArgument)
  230. if (!isAsar) return statSync(pathArgument, options)
  231. // Do not distinguish links for now.
  232. return fs.lstatSync(pathArgument, options)
  233. }
  234. const { stat } = fs
  235. fs.stat = (pathArgument, options, callback) => {
  236. const { isAsar } = splitPath(pathArgument)
  237. if (typeof options === 'function') {
  238. callback = options
  239. options = {}
  240. }
  241. if (!isAsar) return stat(pathArgument, options, callback)
  242. // Do not distinguish links for now.
  243. process.nextTick(() => fs.lstat(pathArgument, options, callback))
  244. }
  245. fs.promises.stat = util.promisify(fs.stat)
  246. const wrapRealpathSync = function (realpathSync) {
  247. return function (pathArgument, options) {
  248. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  249. if (!isAsar) return realpathSync.apply(this, arguments)
  250. const archive = getOrCreateArchive(asarPath)
  251. if (!archive) {
  252. throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  253. }
  254. const fileRealPath = archive.realpath(filePath)
  255. if (fileRealPath === false) {
  256. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  257. }
  258. return path.join(realpathSync(asarPath, options), fileRealPath)
  259. }
  260. }
  261. const { realpathSync } = fs
  262. fs.realpathSync = wrapRealpathSync(realpathSync)
  263. fs.realpathSync.native = wrapRealpathSync(realpathSync.native)
  264. const wrapRealpath = function (realpath) {
  265. return function (pathArgument, options, callback) {
  266. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  267. if (!isAsar) return realpath.apply(this, arguments)
  268. if (arguments.length < 3) {
  269. callback = options
  270. options = {}
  271. }
  272. const archive = getOrCreateArchive(asarPath)
  273. if (!archive) {
  274. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  275. nextTick(callback, [error])
  276. return
  277. }
  278. const fileRealPath = archive.realpath(filePath)
  279. if (fileRealPath === false) {
  280. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  281. nextTick(callback, [error])
  282. return
  283. }
  284. realpath(asarPath, options, (error, archiveRealPath) => {
  285. if (error === null) {
  286. const fullPath = path.join(archiveRealPath, fileRealPath)
  287. callback(null, fullPath)
  288. } else {
  289. callback(error)
  290. }
  291. })
  292. }
  293. }
  294. const { realpath } = fs
  295. fs.realpath = wrapRealpath(realpath)
  296. fs.realpath.native = wrapRealpath(realpath.native)
  297. fs.promises.realpath = util.promisify(fs.realpath.native)
  298. const { exists } = fs
  299. fs.exists = (pathArgument, callback) => {
  300. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  301. if (!isAsar) return exists(pathArgument, callback)
  302. const archive = getOrCreateArchive(asarPath)
  303. if (!archive) {
  304. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  305. nextTick(callback, [error])
  306. return
  307. }
  308. const pathExists = (archive.stat(filePath) !== false)
  309. nextTick(callback, [pathExists])
  310. }
  311. fs.exists[util.promisify.custom] = pathArgument => {
  312. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  313. if (!isAsar) return exists[util.promisify.custom](pathArgument)
  314. const archive = getOrCreateArchive(asarPath)
  315. if (!archive) {
  316. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  317. return Promise.reject(error)
  318. }
  319. return Promise.resolve(archive.stat(filePath) !== false)
  320. }
  321. const { existsSync } = fs
  322. fs.existsSync = pathArgument => {
  323. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  324. if (!isAsar) return existsSync(pathArgument)
  325. const archive = getOrCreateArchive(asarPath)
  326. if (!archive) return false
  327. return archive.stat(filePath) !== false
  328. }
  329. const { access } = fs
  330. fs.access = function (pathArgument, mode, callback) {
  331. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  332. if (!isAsar) return access.apply(this, arguments)
  333. if (typeof mode === 'function') {
  334. callback = mode
  335. mode = fs.constants.F_OK
  336. }
  337. const archive = getOrCreateArchive(asarPath)
  338. if (!archive) {
  339. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  340. nextTick(callback, [error])
  341. return
  342. }
  343. const info = archive.getFileInfo(filePath)
  344. if (!info) {
  345. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  346. nextTick(callback, [error])
  347. return
  348. }
  349. if (info.unpacked) {
  350. const realPath = archive.copyFileOut(filePath)
  351. return fs.access(realPath, mode, callback)
  352. }
  353. const stats = archive.stat(filePath)
  354. if (!stats) {
  355. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  356. nextTick(callback, [error])
  357. return
  358. }
  359. if (mode & fs.constants.W_OK) {
  360. const error = createError(AsarError.NO_ACCESS, { asarPath, filePath })
  361. nextTick(callback, [error])
  362. return
  363. }
  364. nextTick(callback)
  365. }
  366. fs.promises.access = util.promisify(fs.access)
  367. const { accessSync } = fs
  368. fs.accessSync = function (pathArgument, mode) {
  369. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  370. if (!isAsar) return accessSync.apply(this, arguments)
  371. if (mode == null) mode = fs.constants.F_OK
  372. const archive = getOrCreateArchive(asarPath)
  373. if (!archive) {
  374. throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  375. }
  376. const info = archive.getFileInfo(filePath)
  377. if (!info) {
  378. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  379. }
  380. if (info.unpacked) {
  381. const realPath = archive.copyFileOut(filePath)
  382. return fs.accessSync(realPath, mode)
  383. }
  384. const stats = archive.stat(filePath)
  385. if (!stats) {
  386. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  387. }
  388. if (mode & fs.constants.W_OK) {
  389. throw createError(AsarError.NO_ACCESS, { asarPath, filePath })
  390. }
  391. }
  392. const { readFile } = fs
  393. fs.readFile = function (pathArgument, options, callback) {
  394. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  395. if (!isAsar) return readFile.apply(this, arguments)
  396. if (typeof options === 'function') {
  397. callback = options
  398. options = { encoding: null }
  399. } else if (typeof options === 'string') {
  400. options = { encoding: options }
  401. } else if (options === null || options === undefined) {
  402. options = { encoding: null }
  403. } else if (typeof options !== 'object') {
  404. throw new TypeError('Bad arguments')
  405. }
  406. const { encoding } = options
  407. const archive = getOrCreateArchive(asarPath)
  408. if (!archive) {
  409. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  410. nextTick(callback, [error])
  411. return
  412. }
  413. const info = archive.getFileInfo(filePath)
  414. if (!info) {
  415. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  416. nextTick(callback, [error])
  417. return
  418. }
  419. if (info.size === 0) {
  420. nextTick(callback, [null, encoding ? '' : Buffer.alloc(0)])
  421. return
  422. }
  423. if (info.unpacked) {
  424. const realPath = archive.copyFileOut(filePath)
  425. return fs.readFile(realPath, options, callback)
  426. }
  427. const buffer = Buffer.alloc(info.size)
  428. const fd = archive.getFd()
  429. if (!(fd >= 0)) {
  430. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  431. nextTick(callback, [error])
  432. return
  433. }
  434. logASARAccess(asarPath, filePath, info.offset)
  435. fs.read(fd, buffer, 0, info.size, info.offset, error => {
  436. callback(error, encoding ? buffer.toString(encoding) : buffer)
  437. })
  438. }
  439. fs.promises.readFile = util.promisify(fs.readFile)
  440. const { readFileSync } = fs
  441. fs.readFileSync = function (pathArgument, options) {
  442. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  443. if (!isAsar) return readFileSync.apply(this, arguments)
  444. const archive = getOrCreateArchive(asarPath)
  445. if (!archive) throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  446. const info = archive.getFileInfo(filePath)
  447. if (!info) throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  448. if (info.size === 0) return (options) ? '' : Buffer.alloc(0)
  449. if (info.unpacked) {
  450. const realPath = archive.copyFileOut(filePath)
  451. return fs.readFileSync(realPath, options)
  452. }
  453. if (!options) {
  454. options = { encoding: null }
  455. } else if (typeof options === 'string') {
  456. options = { encoding: options }
  457. } else if (typeof options !== 'object') {
  458. throw new TypeError('Bad arguments')
  459. }
  460. const { encoding } = options
  461. const buffer = Buffer.alloc(info.size)
  462. const fd = archive.getFd()
  463. if (!(fd >= 0)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  464. logASARAccess(asarPath, filePath, info.offset)
  465. fs.readSync(fd, buffer, 0, info.size, info.offset)
  466. return (encoding) ? buffer.toString(encoding) : buffer
  467. }
  468. const { readdir } = fs
  469. fs.readdir = function (pathArgument, options, callback) {
  470. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  471. if (typeof options === 'function') {
  472. callback = options
  473. options = {}
  474. }
  475. if (!isAsar) return readdir.apply(this, arguments)
  476. const archive = getOrCreateArchive(asarPath)
  477. if (!archive) {
  478. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  479. nextTick(callback, [error])
  480. return
  481. }
  482. const files = archive.readdir(filePath)
  483. if (!files) {
  484. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  485. nextTick(callback, [error])
  486. return
  487. }
  488. nextTick(callback, [null, files])
  489. }
  490. fs.promises.readdir = util.promisify(fs.readdir)
  491. const { readdirSync } = fs
  492. fs.readdirSync = function (pathArgument, options) {
  493. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  494. if (!isAsar) return readdirSync.apply(this, arguments)
  495. const archive = getOrCreateArchive(asarPath)
  496. if (!archive) {
  497. throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  498. }
  499. const files = archive.readdir(filePath)
  500. if (!files) {
  501. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  502. }
  503. return files
  504. }
  505. const { internalModuleReadJSON } = internalBinding('fs')
  506. internalBinding('fs').internalModuleReadJSON = pathArgument => {
  507. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  508. if (!isAsar) return internalModuleReadJSON(pathArgument)
  509. const archive = getOrCreateArchive(asarPath)
  510. if (!archive) return
  511. const info = archive.getFileInfo(filePath)
  512. if (!info) return
  513. if (info.size === 0) return ''
  514. if (info.unpacked) {
  515. const realPath = archive.copyFileOut(filePath)
  516. return fs.readFileSync(realPath, { encoding: 'utf8' })
  517. }
  518. const buffer = Buffer.alloc(info.size)
  519. const fd = archive.getFd()
  520. if (!(fd >= 0)) return
  521. logASARAccess(asarPath, filePath, info.offset)
  522. fs.readSync(fd, buffer, 0, info.size, info.offset)
  523. return buffer.toString('utf8')
  524. }
  525. const { internalModuleStat } = internalBinding('fs')
  526. internalBinding('fs').internalModuleStat = pathArgument => {
  527. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  528. if (!isAsar) return internalModuleStat(pathArgument)
  529. // -ENOENT
  530. const archive = getOrCreateArchive(asarPath)
  531. if (!archive) return -34
  532. // -ENOENT
  533. const stats = archive.stat(filePath)
  534. if (!stats) return -34
  535. return (stats.isDirectory) ? 1 : 0
  536. }
  537. // Calling mkdir for directory inside asar archive should throw ENOTDIR
  538. // error, but on Windows it throws ENOENT.
  539. if (process.platform === 'win32') {
  540. const { mkdir } = fs
  541. fs.mkdir = (pathArgument, options, callback) => {
  542. if (typeof options === 'function') {
  543. callback = options
  544. options = {}
  545. }
  546. const { isAsar, filePath } = splitPath(pathArgument)
  547. if (isAsar && filePath.length > 0) {
  548. const error = createError(AsarError.NOT_DIR)
  549. nextTick(callback, [error])
  550. return
  551. }
  552. mkdir(pathArgument, options, callback)
  553. }
  554. fs.promises.mkdir = util.promisify(fs.mkdir)
  555. const { mkdirSync } = fs
  556. fs.mkdirSync = function (pathArgument, options) {
  557. const { isAsar, filePath } = splitPath(pathArgument)
  558. if (isAsar && filePath.length) throw createError(AsarError.NOT_DIR)
  559. return mkdirSync(pathArgument, options)
  560. }
  561. }
  562. function invokeWithNoAsar (func) {
  563. return function () {
  564. const processNoAsarOriginalValue = process.noAsar
  565. process.noAsar = true
  566. try {
  567. return func.apply(this, arguments)
  568. } finally {
  569. process.noAsar = processNoAsarOriginalValue
  570. }
  571. }
  572. }
  573. // Strictly implementing the flags of fs.copyFile is hard, just do a simple
  574. // implementation for now. Doing 2 copies won't spend much time more as OS
  575. // has filesystem caching.
  576. overrideAPI(fs, 'copyFile')
  577. overrideAPISync(fs, 'copyFileSync')
  578. overrideAPI(fs, 'open')
  579. overrideAPISync(process, 'dlopen', 1)
  580. overrideAPISync(Module._extensions, '.node', 1)
  581. overrideAPISync(fs, 'openSync')
  582. // Lazily override the child_process APIs only when child_process is fetched the first time
  583. const originalModuleLoad = Module._load
  584. Module._load = (request, ...args) => {
  585. const loadResult = originalModuleLoad(request, ...args)
  586. if (request === 'child_process') {
  587. if (!v8Util.getHiddenValue(loadResult, 'asar-ready')) {
  588. v8Util.setHiddenValue(loadResult, 'asar-ready', true)
  589. // Just to make it obvious what we are dealing with here
  590. const childProcess = loadResult
  591. // Executing a command string containing a path to an asar
  592. // archive confuses `childProcess.execFile`, which is internally
  593. // called by `childProcess.{exec,execSync}`, causing
  594. // Electron to consider the full command as a single path
  595. // to an archive.
  596. const { exec, execSync } = childProcess
  597. childProcess.exec = invokeWithNoAsar(exec)
  598. childProcess.exec[util.promisify.custom] = invokeWithNoAsar(exec[util.promisify.custom])
  599. childProcess.execSync = invokeWithNoAsar(execSync)
  600. overrideAPI(childProcess, 'execFile')
  601. overrideAPISync(childProcess, 'execFileSync')
  602. }
  603. }
  604. return loadResult
  605. }
  606. }
  607. })()