asar.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  1. 'use strict';
  2. (function () {
  3. const asar = process._linkedBinding('atom_common_asar')
  4. const assert = require('assert')
  5. const { Buffer } = require('buffer')
  6. const childProcess = require('child_process')
  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. assert.fail(`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 { realpathSync } = fs
  247. fs.realpathSync = 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. fs.realpathSync.native = function (pathArgument, options) {
  261. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  262. if (!isAsar) return realpathSync.native.apply(this, arguments)
  263. const archive = getOrCreateArchive(asarPath)
  264. if (!archive) {
  265. throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  266. }
  267. const fileRealPath = archive.realpath(filePath)
  268. if (fileRealPath === false) {
  269. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  270. }
  271. return path.join(realpathSync.native(asarPath, options), fileRealPath)
  272. }
  273. const { realpath } = fs
  274. fs.realpath = function (pathArgument, options, callback) {
  275. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  276. if (!isAsar) return realpath.apply(this, arguments)
  277. if (arguments.length < 3) {
  278. callback = options
  279. options = {}
  280. }
  281. const archive = getOrCreateArchive(asarPath)
  282. if (!archive) {
  283. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  284. nextTick(callback, [error])
  285. return
  286. }
  287. const fileRealPath = archive.realpath(filePath)
  288. if (fileRealPath === false) {
  289. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  290. nextTick(callback, [error])
  291. return
  292. }
  293. realpath(asarPath, options, (error, archiveRealPath) => {
  294. if (error === null) {
  295. const fullPath = path.join(archiveRealPath, fileRealPath)
  296. callback(null, fullPath)
  297. } else {
  298. callback(error)
  299. }
  300. })
  301. }
  302. fs.realpath.native = function (pathArgument, options, callback) {
  303. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  304. if (!isAsar) return realpath.native.apply(this, arguments)
  305. if (arguments.length < 3) {
  306. callback = options
  307. options = {}
  308. }
  309. const archive = getOrCreateArchive(asarPath)
  310. if (!archive) {
  311. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  312. nextTick(callback, [error])
  313. return
  314. }
  315. const fileRealPath = archive.realpath(filePath)
  316. if (fileRealPath === false) {
  317. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  318. nextTick(callback, [error])
  319. return
  320. }
  321. realpath.native(asarPath, options, (error, archiveRealPath) => {
  322. if (error === null) {
  323. const fullPath = path.join(archiveRealPath, fileRealPath)
  324. callback(null, fullPath)
  325. } else {
  326. callback(error)
  327. }
  328. })
  329. }
  330. fs.promises.realpath = util.promisify(fs.realpath.native)
  331. const { exists } = fs
  332. fs.exists = (pathArgument, callback) => {
  333. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  334. if (!isAsar) return exists(pathArgument, callback)
  335. const archive = getOrCreateArchive(asarPath)
  336. if (!archive) {
  337. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  338. nextTick(callback, [error])
  339. return
  340. }
  341. const pathExists = (archive.stat(filePath) !== false)
  342. nextTick(callback, [pathExists])
  343. }
  344. fs.exists[util.promisify.custom] = pathArgument => {
  345. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  346. if (!isAsar) return exists[util.promisify.custom](pathArgument)
  347. const archive = getOrCreateArchive(asarPath)
  348. if (!archive) {
  349. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  350. return Promise.reject(error)
  351. }
  352. return Promise.resolve(archive.stat(filePath) !== false)
  353. }
  354. const { existsSync } = fs
  355. fs.existsSync = pathArgument => {
  356. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  357. if (!isAsar) return existsSync(pathArgument)
  358. const archive = getOrCreateArchive(asarPath)
  359. if (!archive) return false
  360. return archive.stat(filePath) !== false
  361. }
  362. const { access } = fs
  363. fs.access = function (pathArgument, mode, callback) {
  364. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  365. if (!isAsar) return access.apply(this, arguments)
  366. if (typeof mode === 'function') {
  367. callback = mode
  368. mode = fs.constants.F_OK
  369. }
  370. const archive = getOrCreateArchive(asarPath)
  371. if (!archive) {
  372. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  373. nextTick(callback, [error])
  374. return
  375. }
  376. const info = archive.getFileInfo(filePath)
  377. if (!info) {
  378. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  379. nextTick(callback, [error])
  380. return
  381. }
  382. if (info.unpacked) {
  383. const realPath = archive.copyFileOut(filePath)
  384. return fs.access(realPath, mode, callback)
  385. }
  386. const stats = archive.stat(filePath)
  387. if (!stats) {
  388. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  389. nextTick(callback, [error])
  390. return
  391. }
  392. if (mode & fs.constants.W_OK) {
  393. const error = createError(AsarError.NO_ACCESS, { asarPath, filePath })
  394. nextTick(callback, [error])
  395. return
  396. }
  397. nextTick(callback)
  398. }
  399. fs.promises.access = util.promisify(fs.access)
  400. const { accessSync } = fs
  401. fs.accessSync = function (pathArgument, mode) {
  402. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  403. if (!isAsar) return accessSync.apply(this, arguments)
  404. if (mode == null) mode = fs.constants.F_OK
  405. const archive = getOrCreateArchive(asarPath)
  406. if (!archive) {
  407. throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  408. }
  409. const info = archive.getFileInfo(filePath)
  410. if (!info) {
  411. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  412. }
  413. if (info.unpacked) {
  414. const realPath = archive.copyFileOut(filePath)
  415. return fs.accessSync(realPath, mode)
  416. }
  417. const stats = archive.stat(filePath)
  418. if (!stats) {
  419. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  420. }
  421. if (mode & fs.constants.W_OK) {
  422. throw createError(AsarError.NO_ACCESS, { asarPath, filePath })
  423. }
  424. }
  425. const { readFile } = fs
  426. fs.readFile = function (pathArgument, options, callback) {
  427. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  428. if (!isAsar) return readFile.apply(this, arguments)
  429. if (typeof options === 'function') {
  430. callback = options
  431. options = { encoding: null }
  432. } else if (typeof options === 'string') {
  433. options = { encoding: options }
  434. } else if (options === null || options === undefined) {
  435. options = { encoding: null }
  436. } else if (typeof options !== 'object') {
  437. throw new TypeError('Bad arguments')
  438. }
  439. const { encoding } = options
  440. const archive = getOrCreateArchive(asarPath)
  441. if (!archive) {
  442. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  443. nextTick(callback, [error])
  444. return
  445. }
  446. const info = archive.getFileInfo(filePath)
  447. if (!info) {
  448. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  449. nextTick(callback, [error])
  450. return
  451. }
  452. if (info.size === 0) {
  453. nextTick(callback, [null, encoding ? '' : Buffer.alloc(0)])
  454. return
  455. }
  456. if (info.unpacked) {
  457. const realPath = archive.copyFileOut(filePath)
  458. return fs.readFile(realPath, options, callback)
  459. }
  460. const buffer = Buffer.alloc(info.size)
  461. const fd = archive.getFd()
  462. if (!(fd >= 0)) {
  463. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  464. nextTick(callback, [error])
  465. return
  466. }
  467. logASARAccess(asarPath, filePath, info.offset)
  468. fs.read(fd, buffer, 0, info.size, info.offset, error => {
  469. callback(error, encoding ? buffer.toString(encoding) : buffer)
  470. })
  471. }
  472. fs.promises.readFile = util.promisify(fs.readFile)
  473. const { readFileSync } = fs
  474. fs.readFileSync = function (pathArgument, options) {
  475. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  476. if (!isAsar) return readFileSync.apply(this, arguments)
  477. const archive = getOrCreateArchive(asarPath)
  478. if (!archive) throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  479. const info = archive.getFileInfo(filePath)
  480. if (!info) throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  481. if (info.size === 0) return (options) ? '' : Buffer.alloc(0)
  482. if (info.unpacked) {
  483. const realPath = archive.copyFileOut(filePath)
  484. return fs.readFileSync(realPath, options)
  485. }
  486. if (!options) {
  487. options = { encoding: null }
  488. } else if (typeof options === 'string') {
  489. options = { encoding: options }
  490. } else if (typeof options !== 'object') {
  491. throw new TypeError('Bad arguments')
  492. }
  493. const { encoding } = options
  494. const buffer = Buffer.alloc(info.size)
  495. const fd = archive.getFd()
  496. if (!(fd >= 0)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  497. logASARAccess(asarPath, filePath, info.offset)
  498. fs.readSync(fd, buffer, 0, info.size, info.offset)
  499. return (encoding) ? buffer.toString(encoding) : buffer
  500. }
  501. const { readdir } = fs
  502. fs.readdir = function (pathArgument, options, callback) {
  503. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  504. if (typeof options === 'function') {
  505. callback = options
  506. options = {}
  507. }
  508. if (!isAsar) return readdir.apply(this, arguments)
  509. const archive = getOrCreateArchive(asarPath)
  510. if (!archive) {
  511. const error = createError(AsarError.INVALID_ARCHIVE, { asarPath })
  512. nextTick(callback, [error])
  513. return
  514. }
  515. const files = archive.readdir(filePath)
  516. if (!files) {
  517. const error = createError(AsarError.NOT_FOUND, { asarPath, filePath })
  518. nextTick(callback, [error])
  519. return
  520. }
  521. nextTick(callback, [null, files])
  522. }
  523. fs.promises.readdir = util.promisify(fs.readdir)
  524. const { readdirSync } = fs
  525. fs.readdirSync = function (pathArgument, options) {
  526. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  527. if (!isAsar) return readdirSync.apply(this, arguments)
  528. const archive = getOrCreateArchive(asarPath)
  529. if (!archive) {
  530. throw createError(AsarError.INVALID_ARCHIVE, { asarPath })
  531. }
  532. const files = archive.readdir(filePath)
  533. if (!files) {
  534. throw createError(AsarError.NOT_FOUND, { asarPath, filePath })
  535. }
  536. return files
  537. }
  538. const { internalModuleReadJSON } = internalBinding('fs')
  539. internalBinding('fs').internalModuleReadJSON = pathArgument => {
  540. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  541. if (!isAsar) return internalModuleReadJSON(pathArgument)
  542. const archive = getOrCreateArchive(asarPath)
  543. if (!archive) return
  544. const info = archive.getFileInfo(filePath)
  545. if (!info) return
  546. if (info.size === 0) return ''
  547. if (info.unpacked) {
  548. const realPath = archive.copyFileOut(filePath)
  549. return fs.readFileSync(realPath, { encoding: 'utf8' })
  550. }
  551. const buffer = Buffer.alloc(info.size)
  552. const fd = archive.getFd()
  553. if (!(fd >= 0)) return
  554. logASARAccess(asarPath, filePath, info.offset)
  555. fs.readSync(fd, buffer, 0, info.size, info.offset)
  556. return buffer.toString('utf8')
  557. }
  558. const { internalModuleStat } = internalBinding('fs')
  559. internalBinding('fs').internalModuleStat = pathArgument => {
  560. const { isAsar, asarPath, filePath } = splitPath(pathArgument)
  561. if (!isAsar) return internalModuleStat(pathArgument)
  562. // -ENOENT
  563. const archive = getOrCreateArchive(asarPath)
  564. if (!archive) return -34
  565. // -ENOENT
  566. const stats = archive.stat(filePath)
  567. if (!stats) return -34
  568. return (stats.isDirectory) ? 1 : 0
  569. }
  570. // Calling mkdir for directory inside asar archive should throw ENOTDIR
  571. // error, but on Windows it throws ENOENT.
  572. if (process.platform === 'win32') {
  573. const { mkdir } = fs
  574. fs.mkdir = (pathArgument, options, callback) => {
  575. if (typeof options === 'function') {
  576. callback = options
  577. options = {}
  578. }
  579. const { isAsar, filePath } = splitPath(pathArgument)
  580. if (isAsar && filePath.length > 0) {
  581. const error = createError(AsarError.NOT_DIR)
  582. nextTick(callback, [error])
  583. return
  584. }
  585. mkdir(pathArgument, options, callback)
  586. }
  587. fs.promises.mkdir = util.promisify(fs.mkdir)
  588. const { mkdirSync } = fs
  589. fs.mkdirSync = function (pathArgument, options) {
  590. const { isAsar, filePath } = splitPath(pathArgument)
  591. if (isAsar && filePath.length) throw createError(AsarError.NOT_DIR)
  592. return mkdirSync(pathArgument, options)
  593. }
  594. }
  595. // Executing a command string containing a path to an asar
  596. // archive confuses `childProcess.execFile`, which is internally
  597. // called by `childProcess.{exec,execSync}`, causing
  598. // Electron to consider the full command as a single path
  599. // to an archive.
  600. const { exec, execSync } = childProcess
  601. childProcess.exec = invokeWithNoAsar(exec)
  602. childProcess.exec[util.promisify.custom] = invokeWithNoAsar(exec[util.promisify.custom])
  603. childProcess.execSync = invokeWithNoAsar(execSync)
  604. function invokeWithNoAsar (func) {
  605. return function () {
  606. const processNoAsarOriginalValue = process.noAsar
  607. process.noAsar = true
  608. try {
  609. return func.apply(this, arguments)
  610. } finally {
  611. process.noAsar = processNoAsarOriginalValue
  612. }
  613. }
  614. }
  615. // Strictly implementing the flags of fs.copyFile is hard, just do a simple
  616. // implementation for now. Doing 2 copies won't spend much time more as OS
  617. // has filesystem caching.
  618. overrideAPI(fs, 'copyFile')
  619. overrideAPISync(fs, 'copyFileSync')
  620. overrideAPI(fs, 'open')
  621. overrideAPI(childProcess, 'execFile')
  622. overrideAPISync(process, 'dlopen', 1)
  623. overrideAPISync(require('module')._extensions, '.node', 1)
  624. overrideAPISync(fs, 'openSync')
  625. overrideAPISync(childProcess, 'execFileSync')
  626. }
  627. })()