From 67d20da312cccec68ed33c9ef4296b98625e0cb6 Mon Sep 17 00:00:00 2001 From: anseki Date: Fri, 27 Mar 2015 20:25:59 +0900 Subject: [PATCH] Rewrite code that call external programs. --- lib/read.cs.js | 133 +++++++++++++++++++++---------------------- lib/read.ps1 | 69 ++++++++++++---------- lib/readline-sync.js | 133 ++++++++++++++++++++++++++++--------------- 3 files changed, 190 insertions(+), 145 deletions(-) diff --git a/lib/read.cs.js b/lib/read.cs.js index cf1b2cd..c7bff07 100644 --- a/lib/read.cs.js +++ b/lib/read.cs.js @@ -2,43 +2,67 @@ var FSO_ForReading = 1, FSO_ForWriting = 2, + PS_MSG = 'Microsoft Windows PowerShell is required.\n' + + 'https://technet.microsoft.com/en-us/library/hh847837.aspx', - fso, tty, - 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 = {}; + input, fso, tty, + options = (function(conf) { + var options = {}, arg, 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; + })(), + confLc = {}, key; -while (typeof(arg = args.shift()) === 'string') { - arg = arg.toLowerCase(); - if (arg === '--display') { - options.display = args.shift(); - } else if (arg === '--noechoback') { - options.noEchoBack = true; - } else if (arg === '--mask') { - options.mask = args.shift(); - } else if (arg === '--keyin') { - options.keyIn = true; - } else if (arg === '--encoded') { - options.encoded = true; - } + function decodeDOS(arg) { + return arg.replace(/#(\d+);/g, function(str, charCode) { + return String.fromCharCode(+charCode); + }); + } + + for (key in conf) { + if (conf.hasOwnProperty(key)) + { confLc[key.toLowerCase()] = {key: key, type: conf[key]}; } + } + + while (typeof(arg = args.shift()) === 'string') { + if (!(arg = (arg.match(/^\-+(.+)$/) || [])[1])) { continue; } + arg = arg.toLowerCase(); + if (confLc[arg]) { + options[confLc[arg].key] = + confLc[arg].type === 'boolean' ? true : + confLc[arg].type === 'string' ? args.shift() : null; + } + } + for (key in conf) { + if (conf.hasOwnProperty(key) && conf[key] === 'string') { + if (typeof options[key] !== 'string') { options[key] = ''; } + else if (options.encoded) { options[key] = decodeDOS(options[key]); } + } + } + return options; + })({ + display: 'string', + noEchoBack: 'boolean', + mask: 'string', + keyIn: 'boolean', + encoded: 'boolean' + }); + +if (!options.noEchoBack && !options.keyIn) { + if (options.display !== '') { writeTTY(options.display); } + input = readByCS(); +} else if (options.noEchoBack && !options.keyIn && options.mask === '*') { + if (options.display !== '') { writeTTY(options.display); } + input = readByPW(); +} else { + WScript.StdErr.WriteLine(PS_MSG); + WScript.Quit(1); } -if (options.encoded) { - if (typeof options.display === 'string') - { options.display = decodeDOS(options.display); } - if (typeof options.mask === 'string') - { options.mask = decodeDOS(options.mask); } -} - -if (typeof options.display === 'string' && options.display !== '') - { writeTTY(options.display); } - -WScript.StdOut.Write("'" + readTTY() + "'"); +WScript.StdOut.Write('\'' + input + '\''); WScript.Quit(); @@ -48,50 +72,28 @@ function writeTTY(text) { tty.Write(text); } catch (e) { WScript.StdErr.WriteLine('TTY Write Error: ' + e.number + - '\n' + e.description); - WScript.Quit(1); - } -} - -function readTTY() { - - // function psExists() { - // var envPs = getShell().Environment('System')('PSModulePath'); - // return typeof envPs === 'string' && envPs !== ''; - // } - - if (!options.noEchoBack && !options.keyIn) { - return readByCS(); - // } else if (psExists()) { - // return readByPS(); - } else if (options.noEchoBack && !options.keyIn && options.mask === '*') { - return readByPW(); - } else { - WScript.StdErr.WriteLine('Microsoft Windows PowerShell is required.\n' + - 'https://technet.microsoft.com/ja-jp/library/hh847837.aspx'); + '\n' + e.description + '\n' + PS_MSG); WScript.Quit(1); } } function readByCS() { - WScript.StdErr.Write('<>'); //_DBG_ var text; try { text = getFso().OpenTextFile('CONIN$', FSO_ForReading).ReadLine(); } catch (e) { WScript.StdErr.WriteLine('TTY Read Error: ' + e.number + - '\n' + e.description); + '\n' + e.description + '\n' + PS_MSG); WScript.Quit(1); } return text; } +// TTY must be STDIN that is not redirected and not piped. function readByPW() { - WScript.StdErr.Write('<>'); //_DBG_ - var pw; - // exit-code is not returned even if an error is thrown. + var text; try { - pw = WScript.CreateObject('ScriptPW.Password').GetPassword() + text = 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); @@ -100,21 +102,14 @@ function readByPW() { }); } catch (e) { WScript.StdErr.WriteLine('ScriptPW.Password Error: ' + e.number + - '\n' + e.description); + '\n' + e.description + '\n' + PS_MSG); WScript.Quit(1); } writeTTY('\n'); - return pw; + return text; } function getFso() { if (!fso) { fso = new ActiveXObject('Scripting.FileSystemObject'); } return fso; } - -function decodeDOS(arg) { - return arg.replace(/#(\d+);/g, function(str, charCode) { - return String.fromCharCode(+charCode); - }); -} - diff --git a/lib/read.ps1 b/lib/read.ps1 index c281dd2..09babe8 100644 --- a/lib/read.ps1 +++ b/lib/read.ps1 @@ -2,7 +2,7 @@ Param( [string] $display, [switch] $noEchoBack, - [string] $mask = '*', + [string] $mask, [switch] $keyIn, [switch] $encoded ) @@ -14,24 +14,35 @@ trap { exit 1 } -function decodeDOS($arg) { +function decodeDOS ($arg) { [Regex]::Replace($arg, '#(\d+);', { [char][int] $args[0].Groups[1].Value }) } -if ($encoded) { - if ($display -ne '') { $display = decodeDOS $display } - if ($mask -ne '') { $mask = decodeDOS $mask } +$options = @{} +foreach ($arg in @('display', 'noEchoBack', 'mask', 'keyIn', 'encoded')) { + $options.Add($arg, (Get-Variable $arg -ValueOnly)) +} +if ($options.encoded) { + $argList = New-Object string[] $options.Keys.Count + $options.Keys.CopyTo($argList, 0); + foreach ($arg in $argList) { + if (($options[$arg] -is [string]) -and ($options[$arg] -ne '')) + { $options[$arg] = decodeDOS $options[$arg] } + } } -Write-Warning "[PS] display: <$display>" #_DBG -Write-Warning "[PS] noEchoBack: $noEchoBack" #_DBG -Write-Warning "[PS] mask: <$mask>" #_DBG -Write-Warning "[PS] keyIn: $keyIn" #_DBG +[string] $inputTTY = '' +[bool] $isInputLine = $False +[bool] $isEditable = (-not $options.noEchoBack) -and (-not $options.keyIn) + +function writeTTY ($text) { + execWithTTY ('Write-Host ''' + ($text -replace '''', '''''') + ''' -NoNewline') + $script:isInputLine = $True +} # Instant method that opens TTY without CreateFile via P/Invoke in .NET Framework -# **NOTE** Don't include special characters in $command when $getRes is True. +# **NOTE** Don't include special characters of DOS in $command when $getRes is True. function execWithTTY ($command, $getRes = $False) { - Write-Warning "[PS] command: <$command> getRes: $getRes" #_DBG if ($getRes) { $res = (cmd.exe /C "%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 = extHostPath; - execArgs = ['-c', + shellPath = extHostPath; + shellArgs = ['-c', // Use `()`, not `{}` for `-c` (text param) - '("' + extHostPath + '" "' + extScriptPath + '"' + - cmdArgs.map(function(arg) + '("' + extHostPath + '"' + + hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') + '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' + ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + @@ -302,11 +308,12 @@ function _execFileSync(cmdArgs, execOptions) { '; echo 1 >"' + pathDone + '"']; } try { - childProc.spawn(interpreter, execArgs, execOptions); + childProc.spawn(shellPath, shellArgs, execOptions); } catch (e) { res.error = new Error(e.message); res.error.method = '_execFileSync - spawn'; - res.error.interpreter = interpreter; + res.error.program = shellPath; + res.error.args = shellArgs; } while (fs.readFileSync(pathDone, {encoding: encoding}).trim() !== '1') {} @@ -317,9 +324,9 @@ function _execFileSync(cmdArgs, execOptions) { } else { res.error = new Error(DEFAULT_ERR_MSG); res.error.method = '_execFileSync'; - res.error.command = extScriptPath; - res.error.args = cmdArgs; - res.error.ExtMessage = fs.readFileSync(pathStderr, {encoding: encoding}).trim(); + res.error.program = shellPath; + res.error.args = shellArgs; + res.error.extMessage = fs.readFileSync(pathStderr, {encoding: encoding}).trim(); res.error.exitCode = +exitCode; } @@ -331,6 +338,38 @@ function _execFileSync(cmdArgs, execOptions) { return res; } +function getHostArgs(options) { + // To send any text to crazy Windows shell safely. + function encodeDOS(arg) { + return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) { + return '#' + chr.charCodeAt(0) + ';'; + }); + } + + return extHostArgs.concat((function(conf) { + var args = [], key; + for (key in conf) { + if (conf.hasOwnProperty(key)) { + if (conf[key] === 'boolean') { + if (options[key]) { args.push('--' + key); } + } else if (conf[key] === 'string') { + if (options[key] !== '') { + args.push('--' + key, + options.encoded ? encodeDOS(options[key]) : options[key]); + } + } + } + } + return args; + })({ + display: 'string', + noEchoBack: 'boolean', + mask: 'string', + keyIn: 'boolean', + encoded: 'boolean' + })); +} + // for dev exports._useExtSet = function(use) { useExt = use; };