fixed #14 Replace BAT with WSH

This commit is contained in:
anseki 2015-03-13 18:34:41 +09:00
parent 2d29dc0c8a
commit 8e141597ba
5 changed files with 184 additions and 135 deletions

View file

@ -1,10 +0,0 @@
process.stdout.write(decodeDOS(process.argv[2] /*text*/ || ''),
process.env.RLS_ENCODING || 'binary', function() {
process.exit(0);
});
function decodeDOS(arg) {
return arg.replace(/#(\d+);/g, function(str, charCode) {
return String.fromCharCode(+charCode);
});
}

View file

@ -1,20 +0,0 @@
/*jshint wsh:true */
var oExec;
// exit-code is not returned even if an error is thrown.
try {
WScript.StdOut.Write(WScript.CreateObject('ScriptPW.Password').GetPassword()
// Bug? Illegal data may be returned when user types before initializing.
.replace(/[\u4000-\u40FF]/g, function(chr) {
var charCode = chr.charCodeAt(0);
return charCode >= 0x4020 && charCode <= 0x407F ?
String.fromCharCode(charCode - 0x4000) : '';
}));
} catch (e) {
WScript.StdErr.Write(e.description);
WScript.Quit(1);
}
oExec = WScript.CreateObject('WScript.Shell').Exec('cmd /c echo; >CON');
while (oExec.Status === 0) { WScript.Sleep(100); }

View file

@ -1,68 +0,0 @@
@echo off
setlocal ENABLEDELAYEDEXPANSION
:args_loop
if "%~1"=="" (
goto args_end
) else if "%~1"=="--noechoback" (
set noechoback=1
) else if "%~1"=="--keyin" (
set keyin=1
) else if "%~1"=="--display" (
set "display=%~2"
shift /1
)
shift /1
goto args_loop
:args_end
:: type tmpfile.txt >CON
if "%display%" NEQ "" if "%NODE_EXEC_PATH%" NEQ "" (
"%NODE_EXEC_PATH%" "%~dp0decodedos.js" "%display%" >CON
if ERRORLEVEL 1 exit /b 1
)
if "%noechoback%"=="1" (
call :read_s
if ERRORLEVEL 1 exit /b 1
) else (
set /p input=<CON >CON
)
set /p ="'%input%'"<NUL
endlocal
exit /b 0
:: Silent Read
:read_s
:: where /q powershell
:: Win <Vista and <Server2008 don't have `where`.
powershell /? >NUL 2>&1
:: Win <7 and <Server2008R2 don't have PowerShell as default.
:: Win XP and Server2003 have `ScriptPW` (`scriptpw.dll`).
:: In the systems that don't have both, an error is thrown.
if ERRORLEVEL 1 (
set "exec_line=cscript //nologo "%~dp0read-s.cs.js""
) else (
set "exec_line=powershell -Command "$text = read-host -AsSecureString; ^
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR^($text^); ^
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto^($BSTR^)""
)
:: Can't get `ERRORLEVEL` from sub-shell.
:: 2 `%ERRCODE%` lines are returned if an error is thrown.
set ERRCODE=ERR
set "exec_line=%exec_line% ^& if ERRORLEVEL 1 ^(echo %ERRCODE%^& echo %ERRCODE%^)"
:: echo %exec_line%
for /f "usebackq delims=" %%i in (`%exec_line%`) do (
if "%%i"=="%ERRCODE%" if "!input!"=="%ERRCODE%" exit /b 1
set "input=%%i"
)
exit /b 0

148
lib/read.cs.js Normal file
View file

@ -0,0 +1,148 @@
/*jshint wsh:true */
var
FSO_ForReading = 1, FSO_ForWriting = 2,
fso, tty, shell,
args =// Array.prototype.slice.call(WScript.Arguments),
(function() {
var args = [], i, iLen;
for (i = 0, iLen = WScript.Arguments.length; i < iLen; i++) {
args.push(WScript.Arguments(i));
}
return args;
})(),
arg, options = {};
while (typeof(arg = args.shift()) === 'string') {
if (arg === '--noechoback') {
options.noEchoBack = true;
} else if (arg === '--keyin') {
options.keyIn = true;
} else if (arg === '--display') {
options.display = args.shift();
} else if (arg === '--encoded') {
options.encoded = true;
}
}
if (typeof(options.display) === 'string' && options.display !== '') {
ttyWrite(options.encoded ? decodeDOS(options.display) : options.display);
}
WScript.StdOut.Write("'" + (options.noEchoBack ? readS() : ttyRead()) + "'");
WScript.Quit();
function ttyRead() {
var text;
try {
text = getFso().OpenTextFile('CONIN$', FSO_ForReading).ReadLine();
} catch (e) {
WScript.StdErr.WriteLine('TTY Read Error: ' + e.number +
'\n' + e.description);
WScript.Quit(1);
}
return text;
}
function ttyWrite(text) {
try {
tty = tty || getFso().OpenTextFile('CONOUT$', FSO_ForWriting, true);
tty.Write(text);
} catch (e) {
WScript.StdErr.WriteLine('TTY Write Error: ' + e.number +
'\n' + e.description);
WScript.Quit(1);
}
}
function getFso() {
if (!fso) { fso = new ActiveXObject('Scripting.FileSystemObject'); }
return fso;
}
function readS() {
var pw;
shellExec('powershell /?', function(exitCode, stdout, stderr, error) {
if (error || exitCode !== 0) {
pw = scriptPW();
} else {
shellExec('powershell -Command "$text = read-host -AsSecureString;' +
'$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($text);' +
'[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)"',
function(exitCode, stdout, stderr, error) {
if (error || exitCode !== 0) {
WScript.StdErr.WriteLine('Windows PowerShell Error: ' + exitCode +
(error && error.description ? '\n' + error.description : '') +
(stderr ? '\n' + stderr : '') +
(stdout ? '\n' + stdout : ''));
WScript.Quit(1);
}
pw = stdout.replace(/[\r\n]+$/, '');
});
}
});
return pw;
}
function scriptPW() {
var pw;
// exit-code is not returned even if an error is thrown.
try {
pw = WScript.CreateObject('ScriptPW.Password').GetPassword()
// Bug? Illegal data may be returned when user types before initializing.
.replace(/[\u4000-\u40FF]/g, function(chr) {
var charCode = chr.charCodeAt(0);
return charCode >= 0x4020 && charCode <= 0x407F ?
String.fromCharCode(charCode - 0x4000) : '';
});
} catch (e) {
WScript.StdErr.WriteLine('ScriptPW.Password Error: ' + e.number +
'\n' + e.description);
WScript.Quit(1);
}
ttyWrite('\n');
return pw;
}
function shellExec(cmd, callback) { // callback(exitCode, stdout, stderr, error)
var wsExec, stdout = '', stderr = '', noOutput;
function getShell() {
if (!shell) { shell = WScript.CreateObject('WScript.Shell'); }
return shell;
}
try {
wsExec = getShell().Exec(cmd);
} catch (e) {
callback(e.number, stdout, stderr, e);
return wsExec;
}
while (true) {
noOutput = true;
if (!wsExec.StdOut.AtEndOfStream) {
stdout += wsExec.StdOut.ReadAll();
noOutput = false;
}
if (!wsExec.StdErr.AtEndOfStream) {
stderr += wsExec.StdErr.ReadAll();
noOutput = false;
}
if (noOutput) {
if (wsExec.Status === 1 /*WshFinished*/) { break; }
WScript.Sleep(100);
}
}
callback(wsExec.ExitCode, stdout, stderr);
return wsExec;
}
function decodeDOS(arg) {
return arg.replace(/#(\d+);/g, function(str, charCode) {
return String.fromCharCode(+charCode);
});
}

View file

@ -10,8 +10,8 @@
var
IS_WIN = process.platform === 'win32',
SHELL_PATH = IS_WIN ? process.env.ComSpec || 'cmd.exe' : '/bin/sh',
SHELL_CMD = __dirname + (IS_WIN ? '\\read.bat' : '/read.sh'),
SHELL_PATH = IS_WIN ? 'cscript.exe' : '/bin/sh',
SHELL_CMD = __dirname + (IS_WIN ? '\\read.cs.js' : '/read.sh'),
ALGORITHM_CIPHER = 'aes-256-cbc',
ALGORITHM_HASH = 'sha256',
DEFAULT_ERR_MSG = 'The platform doesn\'t support interactive reading',
@ -31,7 +31,7 @@ function _readlineSync(options) { // options.display is string
if (options.display !== '' && typeof print === 'function')
{ print(options.display, encoding); }
if (useShell || options.noEchoBack) {
if (useShell || options.noEchoBack || options.keyIn) {
res = _readlineShell(options);
if (res.error) { throw res.error; }
input = res.input;
@ -129,7 +129,7 @@ function _readlineShell(options) {
var cmdArgs = [], execArgs, res = {},
execOptions = {
env: process.env,
// ScriptPW (Win XP and Server2003) for `noEchoBack` needs TTY stream.
// ScriptPW (Win XP and Server2003) needs TTY stream as STDIN.
// In this case, If STDIN isn't TTY, an error is thrown.
stdio: [process.stdin],
encoding: encoding
@ -142,28 +142,15 @@ function _readlineShell(options) {
});
}
if (options.noEchoBack) { cmdArgs.push('--noechoback'); }
if (options.keyIn) { cmdArgs.push('--keyin'); }
else if (options.noEchoBack) { cmdArgs.push('--noechoback'); }
if (options.display !== '') {
if (IS_WIN) {
cmdArgs.push('--display', encodeDOS(options.display));
process.env.NODE_EXEC_PATH = process.execPath;
process.env.RLS_ENCODING = encoding;
} else {
cmdArgs.push('--display', options.display);
}
cmdArgs = cmdArgs.concat('--display', IS_WIN ?
[encodeDOS(options.display), '--encoded'] : options.display);
}
if (childProc.execFileSync) {
if (IS_WIN) {
process.env.Q = '"'; // The quote (") that isn't escaped.
execArgs = ['/V:ON', '/S', '/C',
'%Q%' + SHELL_CMD + '%Q%' +
cmdArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') +
' & exit !ERRORLEVEL!'];
} else {
execArgs = [SHELL_CMD].concat(cmdArgs);
}
execArgs = (IS_WIN ? ['//nologo', SHELL_CMD] : [SHELL_CMD]).concat(cmdArgs);
try {
res.input = childProc.execFileSync(SHELL_PATH, execArgs, execOptions);
} catch (e) { // non-zero exit code
@ -176,8 +163,10 @@ function _readlineShell(options) {
} else {
res = _execSyncByFile(cmdArgs, execOptions);
}
if (!res.error) { res.input = res.input.replace(/^'|'$/g, ''); }
options.display = '';
if (!res.error) {
res.input = res.input.replace(/^'|'$/g, '');
options.display = '';
}
return res;
}
@ -207,11 +196,11 @@ function _execSyncByFile(cmdArgs, execOptions) {
return filepath;
}
var execArgs, res = {},
pathStdout = getTempfile('readline-sync.stdout'),
pathStderr = getTempfile('readline-sync.stderr'),
pathStatus = getTempfile('readline-sync.status'),
pathDone = getTempfile('readline-sync.done'),
var execArgs, interpreter, res = {},
pathStdout = getTempfile('readline-sync.stdout'),
pathStderr = getTempfile('readline-sync.stderr'),
pathExit = getTempfile('readline-sync.exit'),
pathDone = getTempfile('readline-sync.done'),
crypto = require('crypto'), shasum, decipher, password;
shasum = crypto.createHash(ALGORITHM_HASH);
@ -220,32 +209,41 @@ function _execSyncByFile(cmdArgs, execOptions) {
decipher = crypto.createDecipher(ALGORITHM_CIPHER, password);
if (IS_WIN) {
interpreter = process.env.ComSpec || 'cmd.exe';
process.env.Q = '"'; // The quote (") that isn't escaped.
// `()` for ignore space by echo
execArgs = ['/V:ON', '/S', '/C',
'(%Q%' + SHELL_PATH + '%Q% /V:ON /S /C %Q%%Q%' + SHELL_CMD + '%Q%' +
'(%Q%' + interpreter + '%Q% /V:ON /S /C %Q%' +
'%Q%' + SHELL_PATH + '%Q% //nologo %Q%' + SHELL_CMD + '%Q%' +
cmdArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') +
' & (echo !ERRORLEVEL!)>%Q%' + pathStatus + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' +
' & (echo !ERRORLEVEL!)>%Q%' + pathExit + '%Q%%Q%) 2>%Q%' + pathStderr + '%Q%' +
' |%Q%' + process.execPath + '%Q% %Q%' + __dirname + '\\encrypt.js%Q%' +
' %Q%' + ALGORITHM_CIPHER + '%Q% %Q%' + password + '%Q%' +
' >%Q%' + pathStdout + '%Q%' +
' & (echo 1)>%Q%' + pathDone + '%Q%'];
} else {
interpreter = SHELL_PATH;
execArgs = ['-c',
// Use `()`, not `{}` for `-c` (text param)
'("' + SHELL_PATH + '" "' + SHELL_CMD + '"' +
cmdArgs.map(function(arg)
{ return ' "' + arg.replace(/[\\"`]/g, '\\\$&') + '"'; }).join('') +
'; echo $?>"' + pathStatus + '") 2>"' + pathStderr + '"' +
{ return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') +
'; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' +
' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' +
' "' + ALGORITHM_CIPHER + '" "' + password + '"' +
' >"' + pathStdout + '"' +
'; echo 1 >"' + pathDone + '"'];
}
childProc.spawn(SHELL_PATH, execArgs, execOptions);
try {
childProc.spawn(interpreter, execArgs, execOptions);
} catch (e) {
res.error = new Error(e.message);
res.error.method = '_execSyncByFile - spawn';
res.error.interpreter = interpreter;
}
while (fs.readFileSync(pathDone, {encoding: encoding}).trim() !== '1') {}
if (fs.readFileSync(pathStatus, {encoding: encoding}).trim() === '0') {
if (fs.readFileSync(pathExit, {encoding: encoding}).trim() === '0') {
res.input =
decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), 'hex', encoding) +
decipher.final(encoding);
@ -259,7 +257,7 @@ function _execSyncByFile(cmdArgs, execOptions) {
fs.unlinkSync(pathStdout);
fs.unlinkSync(pathStderr);
fs.unlinkSync(pathStatus);
fs.unlinkSync(pathExit);
fs.unlinkSync(pathDone);
return res;
@ -287,7 +285,8 @@ exports.setEncoding = function(newEncoding) {
};
exports.setBufferSize = function(newBufSize) {
if (typeof newBufSize === 'number') {
newBufSize = parseInt(newBufSize, 10);
if (!isNaN(newBufSize) && typeof newBufSize === 'number') {
bufSize = newBufSize;
}
return bufSize;
@ -320,7 +319,7 @@ exports.keyIn = function(query, options) {
/* jshint eqnull:true */
display: query != null ? query + '' : '',
/* jshint eqnull:false */
noEchoBack: false,
noEchoBack: !!(options && options.noEchoBack),
keyIn: true,
noTrim: true
};