Override `child_process.execFile` asar patch for `child_process.exec`
authorJuan Cruz Viotti <jviottidc@gmail.com>
Thu, 19 May 2016 15:08:08 +0000 (11:08 -0400)
committerJuan Cruz Viotti <jviottidc@gmail.com>
Thu, 19 May 2016 21:19:36 +0000 (17:19 -0400)
NodeJS implements `child_process.exec` by simply passing the whole
command to `child_process.execFile`. See:

- https://github.com/nodejs/node/blob/master/lib/child_process.js#L90
- https://github.com/nodejs/node/blob/master/lib/child_process.js#L99

Electron patches `child_process.execFile` to add support for `asar`
archives by injecting logic that extracts the required files from the
`asar` to a temporary location before delegating the work to the
original `child_process.execFile`.

In order to decide whether to inject the custom `asar` extracting logic,
Electron makes use of a helper function called `splitPath()`. See:

- https://github.com/electron/electron/blob/master/lib/common/asar.js#L37

If the first argument of the returned array equals `true`, means that
the path is considered to be an `asar` archive, and thus the extraction
logic takes place. The problem is that if the command passed to
`child_process.execFile` *contains* a path to an asar archive, padded
with other commands/arguments, `splitPath()` will consider it to be an
`asar` archive, and will try to extract it, throwing a rightfully
`Invalid package` error.

Fixes: https://github.com/electron/electron/issues/5571
Signed-off-by: Juan Cruz Viotti <jviottidc@gmail.com>
lib/common/asar.js
spec/asar-spec.js

index 2624128..1d8474d 100644 (file)
         return mkdirSync(p, mode)
       }
     }
+
+    // Executing a command string containing a path to an asar
+    // archive confuses `child_process.execFile`, which is internally
+    // called by `child_process.{exec,execSync}`, causing
+    // Electron to consider the full command as a single path
+    // to an archive.
+    [ 'exec', 'execSync' ].forEach(function (functionName) {
+      var old = child_process[functionName]
+      child_process[functionName] = function () {
+        var processNoAsarOriginalValue = process.noAsar
+        process.noAsar = true
+        var result = old.apply(this, arguments)
+        process.noAsar = processNoAsarOriginalValue
+        return result
+      }
+    })
+
     overrideAPI(fs, 'open')
     overrideAPI(child_process, 'execFile')
     overrideAPISync(process, 'dlopen', 1)
index f9c4e98..ac0779d 100644 (file)
@@ -555,6 +555,30 @@ describe('asar package', function () {
       })
     })
 
+    describe('child_process.exec', function () {
+      var child_process = require('child_process');
+      var echo = path.join(fixtures, 'asar', 'echo.asar', 'echo')
+
+      it('should not try to extract the command if there is a reference to a file inside an .asar', function (done) {
+        child_process.exec('echo ' + echo + ' foo bar', function (error, stdout) {
+          assert.equal(error, null)
+          assert.equal(stdout.toString().replace(/\r/g, ''), echo + ' foo bar\n')
+          done()
+        })
+      })
+    })
+
+    describe('child_process.execSync', function () {
+      var child_process = require('child_process');
+      var echo = path.join(fixtures, 'asar', 'echo.asar', 'echo')
+
+      it('should not try to extract the command if there is a reference to a file inside an .asar', function (done) {
+        var stdout = child_process.execSync('echo ' + echo + ' foo bar')
+        assert.equal(stdout.toString().replace(/\r/g, ''), echo + ' foo bar\n')
+        done()
+      })
+    })
+
     describe('child_process.execFile', function () {
       var echo, execFile, execFileSync, ref2
       if (process.platform !== 'darwin') {