asar.js 24 KB

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