asar.js 24 KB

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