diff --git a/lib/read.cs.js b/lib/read.cs.js index fa843d2..cf1b2cd 100644 --- a/lib/read.cs.js +++ b/lib/read.cs.js @@ -3,34 +3,42 @@ var FSO_ForReading = 1, FSO_ForWriting = 2, - fso, tty, shell, + 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)); - } + 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') { + 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 === '--display') { - options.display = args.shift(); } else if (arg === '--encoded') { options.encoded = true; } } -if (typeof options.display === 'string' && options.display !== '') { - writeTTY(options.encoded ? decodeDOS(options.display) : options.display); +if (options.encoded) { + if (typeof options.display === 'string') + { options.display = decodeDOS(options.display); } + if (typeof options.mask === 'string') + { options.mask = decodeDOS(options.mask); } } -WScript.StdOut.Write("'" + (options.noEchoBack ? readS() : readTTY()) + "'"); +if (typeof options.display === 'string' && options.display !== '') + { writeTTY(options.display); } + +WScript.StdOut.Write("'" + readTTY() + "'"); WScript.Quit(); @@ -46,6 +54,27 @@ function writeTTY(text) { } 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'); + WScript.Quit(1); + } +} + +function readByCS() { + WScript.StdErr.Write('<>'); //_DBG_ var text; try { text = getFso().OpenTextFile('CONIN$', FSO_ForReading).ReadLine(); @@ -57,32 +86,8 @@ function readTTY() { return text; } -function readS() { - var pw; - // Microsoft Windows PowerShell https://technet.microsoft.com/ja-jp/library/hh847837.aspx - 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() { +function readByPW() { + WScript.StdErr.Write('<>'); //_DBG_ var pw; // exit-code is not returned even if an error is thrown. try { @@ -107,43 +112,9 @@ function getFso() { return fso; } -function getShell() { - if (!shell) { shell = WScript.CreateObject('WScript.Shell'); } - return shell; -} - -function shellExec(cmd, callback) { // callback(exitCode, stdout, stderr, error) - var wsExec, stdout = '', stderr = '', noOutput; - - 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 arg.replace(/#(\d+);/g, function(str, charCode) { return String.fromCharCode(+charCode); }); } + diff --git a/lib/read.ps1 b/lib/read.ps1 new file mode 100644 index 0000000..c281dd2 --- /dev/null +++ b/lib/read.ps1 @@ -0,0 +1,93 @@ + +Param( + [string] $display, + [switch] $noEchoBack, + [string] $mask = '*', + [switch] $keyIn, + [switch] $encoded +) + +$ErrorActionPreference = 'Stop' # for cmdlet +trap { + # `throw $_` and `Write-Error $_` return exit-code 0 + $Host.UI.WriteErrorLine($_) + exit 1 +} + +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 } +} + +Write-Warning "[PS] display: <$display>" #_DBG +Write-Warning "[PS] noEchoBack: $noEchoBack" #_DBG +Write-Warning "[PS] mask: <$mask>" #_DBG +Write-Warning "[PS] keyIn: $keyIn" #_DBG + +# 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. +function execWithTTY ($command, $getRes = $False) { + Write-Warning "[PS] command: <$command> getRes: $getRes" #_DBG + if ($getRes) { + $res = (cmd.exe /C "CON powershell.exe -Command -" + if ($LastExitCode -ne 0) { exit 1 } + $res = '' + } + return $res +} + +function writeTTY ($text) { + execWithTTY ('Write-Host ''' + ($text -replace '''', '''''') + ''' -NoNewline') +} + +[string] $inputTTY = '' + +if ($noEchoBack -and (-not $keyIn) -and ($mask -eq '*')) { + $inputTTY = execWithTTY ('$inputTTY = Read-Host -AsSecureString;' + + '$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($inputTTY);' + + '[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)') $True + return $inputTTY +} + +$isEditable = (-not $noEchoBack) -and (-not $keyIn) +if ($keyIn) { $reqSize = 1 } +else { $reqSize = 1024 } # dummy + +while ($True) { + if ($isEditable) { + $chunk = execWithTTY 'Read-Host' $True + $chunk += "`n" + } else { # raw + $chunk = execWithTTY '[System.Console]::ReadKey($True).KeyChar' $True + } + + if ($chunk -eq '') { break } + $chunk = $chunk -replace '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '' + if ($chunk -eq '') { continue } + + if (-not $isEditable) { + $displayInput = $chunk -replace '[\r\n]', '' + if ($displayInput -ne '') { + if ($noEchoBack) { + if ($mask -eq '') { $displayInput = '' } + else { $displayInput = $displayInput -replace '.', $mask } + } + if ($displayInput -ne '') { writeTTY $displayInput } + } + } + + $inputTTY += $chunk + if (($inputTTY -match '[\r\n]$') -or ($keyIn -and ($inputTTY.Length -ge $reqSize))) + { break } +} + +if (-not $isEditable) { writeTTY "`n" } # new-line + +return '''' + $inputTTY + '''' #DBG diff --git a/lib/readline-sync.js b/lib/readline-sync.js index b37e9c4..1526020 100644 --- a/lib/readline-sync.js +++ b/lib/readline-sync.js @@ -27,10 +27,10 @@ var useExt = false, fdR = 'none', fdW, ttyR, isRawMode = false, - extHostPath, extScriptPath, tempdir, salt = 0; + extHostPath, extScriptPath, tempdir, salt = 0; function readlineSync(options) { // display, mask are string - var input = '', isEditable, displayInput; + var input = '', displayTmp; function tryExt() { var res = readlineExt(options); @@ -38,14 +38,6 @@ function readlineSync(options) { // display, mask are string return res.input; } - // Node v0.10- returns an error if same mode is set. - function setRawMode(mode) { - if (mode === isRawMode) { return true; } - if (ttyR.setRawMode(mode) !== 0) { return false; } - isRawMode = mode; - return true; - } - (function() { var fsB, constants; @@ -114,10 +106,22 @@ function readlineSync(options) { // display, mask are string if (options.display !== '' && typeof print === 'function') { print(options.display, encoding); } - isEditable = !options.noEchoBack && !options.keyIn; - (function() { // try read - var buffer, reqSize, readSize, chunk; + var buffer, reqSize, readSize, chunk, isInputLine = false, + isEditable = !options.noEchoBack && !options.keyIn; + + function writeTTY(text) { + fs.writeSync(fdW, text); + isInputLine = true; + } + + // Node v0.10- returns an error if same mode is set. + function setRawMode(mode) { + if (mode === isRawMode) { return true; } + if (ttyR.setRawMode(mode) !== 0) { return false; } + isRawMode = mode; + return true; + } if (useExt || !ttyR || typeof fdW !== 'number' && (options.display !== '' || !isEditable)) { @@ -126,7 +130,7 @@ function readlineSync(options) { // display, mask are string } if (options.display !== '') { - fs.writeSync(fdW, options.display); + writeTTY(options.display); options.display = ''; } @@ -154,12 +158,12 @@ function readlineSync(options) { // display, mask are string if ((chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')) === '') { continue; } - if (!isEditable && (displayInput = chunk.replace(/[\r\n]/g, '')) !== '') { + if (!isEditable && (displayTmp = chunk.replace(/[\r\n]/g, '')) !== '') { if (options.noEchoBack) { - displayInput = options.mask === '' ? '' : - (new Array(displayInput.length + 1)).join(options.mask); + displayTmp = options.mask === '' ? '' : + (new Array(displayTmp.length + 1)).join(options.mask); } - if (displayInput !== '') { fs.writeSync(fdW, displayInput); } + if (displayTmp !== '') { writeTTY(displayTmp); } } input += chunk; @@ -167,14 +171,14 @@ function readlineSync(options) { // display, mask are string options.keyIn && input.length >= reqSize) { break; } } - if (!isEditable) { fs.writeSync(fdW, '\n'); } + if (!isEditable && !(options.keyIn && !isInputLine)) { writeTTY('\n'); } setRawMode(false); })(); if (typeof print === 'function') { - displayInput = input.replace(/[\r\n]/g, ''); + displayTmp = input.replace(/[\r\n]/g, ''); print((options.noEchoBack ? - (new Array(displayInput.length + 1)).join(options.mask) : displayInput) + + (new Array(displayTmp.length + 1)).join(options.mask) : displayTmp) + '\n', encoding); }