From ab94a1ea2b2d9783f0bf13b526d6c8cd58151996 Mon Sep 17 00:00:00 2001 From: anseki Date: Thu, 12 Feb 2015 07:39:59 +0900 Subject: [PATCH] Support the Synchronous Process Execution of Node v0.12(v0.11). --- README.md | 9 ++-- lib/readline-sync.js | 125 +++++++++++++++++++++++++------------------ package.json | 2 +- 3 files changed, 76 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 77605ea..3095001 100644 --- a/README.md +++ b/README.md @@ -191,8 +191,7 @@ grunt.initConfig({ ### Platforms -The your Node and OS may not support interactively reading from stdin. The stdin interfaces are different by platforms. -If in those platforms, an error is thrown. +The stdin interfaces are different by platforms. If the platform doesn't support interactively reading from stdin, an error is thrown. ```js try { @@ -205,18 +204,16 @@ try { ### Reading by shell -readlineSync tries reading from stdin by shell if it is needed. And, it use "piping via files" for synchronous running. +readlineSync tries reading from stdin by shell if it is needed. And if the running Node doesn't support the [Synchronous Process Execution](http://nodejs.org/api/child_process.html#child_process_synchronous_process_creation) (i.e. Node v0.10-), it use "piping via files" for synchronous running. As everyone knows, "piping via files" is no good. It blocks event loop and a process. It may make your script be slow. Why did I choose it? : -+ The best solution is [child_process.execSync](https://github.com/joyent/node/blob/master/doc/api/child_process.markdown#child_processexecsynccommand-options) in core modules of Node. But it is not supported by current version. + The good modules (native addon) for synchronous execution exist. But node-gyp can't compile those in some platforms or Node versions. + I think that the security is important more than the speed. Some modules have problem about security. (Those don't protect data.) I think that the speed is not needed usually, because readlineSync is used while user types keys. -Someday, I may rewrite readlineSync to use child_process.execSync, or safety module. - ## Release History + * 2015-02-12 v0.5.5 Support the Synchronous Process Execution of Node v0.12(v0.11). * 2015-01-27 v0.5.0 Add `options.noTrim`. * 2014-07-12 v0.4.0 Add `options.noEchoBack`. * 2014-07-12 v0.3.0 Add `setPrint()`. diff --git a/lib/readline-sync.js b/lib/readline-sync.js index 566d9a7..6fef6d4 100644 --- a/lib/readline-sync.js +++ b/lib/readline-sync.js @@ -10,6 +10,9 @@ var BUF_SIZE = 256, + IS_WIN = process.platform === 'win32', + SHELL_PATH = IS_WIN ? 'cmd.exe' : '/bin/sh', + ALGORITHM_CIPHER = 'aes-256-cbc', ALGORITHM_HASH = 'sha256', @@ -79,14 +82,63 @@ function _readlineSync(display, options) { } function _readlineShell(noEchoBack) { - // piping via files instead of execSync (node v0.12+) - // https://github.com/joyent/node/blob/master/doc/api/child_process.markdown#child_processexecsynccommand-options - // see README > Note - var shellPath, args, shellStdout, + var shellStdout, command, + options = { + env: process.env, + stdio: [stdin], // ScriptPW needs piped stdin + encoding: encoding + }, + optEchoBack = noEchoBack ? ' noechoback' : ''; + + if (IS_WIN) { + // The quote (") is escaped by node before parsed by shell. Then use ENV{Q}. + process.env.Q = '"'; + command = '%Q%' + __dirname + '\\read.bat%Q%' + optEchoBack; + } else { + command = '"' + __dirname + '/read.sh"' + optEchoBack; + } + + stdin.pause(); // re-start in child process + if (childProc.execFileSync) { + shellStdout = childProc.execFileSync(SHELL_PATH, + IS_WIN ? ['/S', '/C', command] : ['-c', command], options); + shellStdout = shellStdout.replace(/^'|'$/g, ''); + } else { + shellStdout = _execSyncByFile(command, options); + } + + return shellStdout; +} + +// piping via files (node v0.10-) +function _execSyncByFile(command, options) { + + function getTempfile(name) { + var path = require('path'), filepath, suffix = '', fd; + tempdir = tempdir || require('os').tmpdir(); + + while (true) { + filepath = path.join(tempdir, name + suffix); + try { + fd = fs.openSync(filepath, 'wx'); + } catch (e) { + if (e.code === 'EEXIST') { + suffix++; + continue; + } else { + throw e; + } + } + fs.closeSync(fd); + break; + } + return filepath; + } + + var shellStdout, pathStdout = getTempfile('readline-sync.stdout'), pathStatus = getTempfile('readline-sync.status'), pathDone = getTempfile('readline-sync.done'), - optEchoBack = noEchoBack ? ' noechoback' : '', crypto = require('crypto'), shasum, decipher, password; shasum = crypto.createHash(ALGORITHM_HASH); @@ -94,32 +146,20 @@ function _readlineShell(noEchoBack) { password = shasum.digest('hex'); decipher = crypto.createDecipher(ALGORITHM_CIPHER, password); - if (process.platform === 'win32') { - // The quote (") is escaped by node before parsed by shell. Then use ENV{Q}. - shellPath = 'cmd.exe'; - args = ['/V:ON', '/S', '/C', - '%Q%' + __dirname + '\\read.bat%Q%' + optEchoBack + - ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' + - ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' + - ' >%Q%' + pathStdout + '%Q%' + - ' & (echo !ERRORLEVEL!)>%Q%' + pathStatus + '%Q% & (echo 1)>%Q%' + pathDone + '%Q%']; - process.env.Q = '"'; - } else { - shellPath = '/bin/sh'; - args = ['-c', - 'DATA=`(' + shellPath + ' "' + __dirname + '/read.sh"' + optEchoBack + ')`; RTN=$?;' + - ' if [ $RTN -eq 0 ]; then (printf \'%s\' "$DATA" |' + - '"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + - ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + - ' >"' + pathStdout + '") fi;' + - ' expr $RTN + $? >"' + pathStatus + '"; echo 1 >"' + pathDone + '"']; - } - - stdin.pause(); // re-start in child process - childProc.spawn(shellPath, args, { - env: process.env, - stdio: [stdin] // ScriptPW needs piped stdin - }); + childProc.spawn(SHELL_PATH, + IS_WIN ? ['/V:ON', '/S', '/C', + command + ' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' + + ' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' + + ' >%Q%' + pathStdout + '%Q%' + + ' & (echo !ERRORLEVEL!)>%Q%' + pathStatus + '%Q% & (echo 1)>%Q%' + pathDone + '%Q%'] : + ['-c', + 'DATA=`(' + SHELL_PATH + ' ' + command + ')`; RTN=$?;' + + ' if [ $RTN -eq 0 ]; then (printf \'%s\' "$DATA" |' + + '"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + + ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + + ' >"' + pathStdout + '") fi;' + + ' expr $RTN + $? >"' + pathStatus + '"; echo 1 >"' + pathDone + '"'], + options); while (fs.readFileSync(pathDone, {encoding: encoding}).trim() !== '1') {} if (fs.readFileSync(pathStatus, {encoding: encoding}).trim() === '0') { @@ -132,31 +172,10 @@ function _readlineShell(noEchoBack) { fs.unlinkSync(pathStdout); fs.unlinkSync(pathStatus); fs.unlinkSync(pathDone); + return shellStdout; } -function getTempfile(name) { - var path = require('path'), filepath, suffix = '', fd; - tempdir = tempdir || require('os').tmpdir(); - - while (true) { - filepath = path.join(tempdir, name + suffix); - try { - fd = fs.openSync(filepath, 'wx'); - } catch (e) { - if (e.code === 'EEXIST') { - suffix++; - continue; - } else { - throw e; - } - } - fs.closeSync(fd); - break; - } - return filepath; -} - // for dev exports.useShellSet = function(use) { useShell = use; }; diff --git a/package.json b/package.json index b48f095..7e8aea2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "readline-sync", - "version": "0.5.4", + "version": "0.5.5", "title": "readlineSync", "description": "Synchronous Readline", "keywords": [