From 7480c8efc8ceda2563cd10a5694272d6d53a7c49 Mon Sep 17 00:00:00 2001 From: Marc Date: Sat, 27 Nov 2021 13:51:55 +0100 Subject: [PATCH] ajout de Sheebr + support de shutdown + auto install kernel --- .gitignore | 5 +- .vscode/settings.json | 3 + binding.gyp | 11 + clean.sh | 4 + clibs.sh | 5 + clibs/chibrax/syscall.cc | 52 + copyRootfsIntoTheImage.sh | 8 + createLiveImage.sh | 4 + kernel/.gitignore | 2 +- kernel/from_current.sh | 5 +- package.json | 5 + package.sh | 20 - rootfs overrides/chibrax/hook.js | 27 + rootfs overrides/chibrax/index.js | 68 +- rootfs overrides/chibrax/input.js | 27 + rootfs overrides/chibrax/package.json | 5 + rootfs overrides/chibrax/shutdown.js | 2 + rootfs overrides/chibrax/sleep.js | 9 + rootfs overrides/chibrax/syscall.node | Bin 0 -> 17472 bytes rootfs overrides/chibrax/tsconfig.json | 16 + rootfs overrides/usr/bin/echo.js | 3 + rootfs overrides/usr/bin/ls.js | 14 + rootfs overrides/usr/bin/mkdir.js | 6 + rootfs overrides/usr/bin/pwd.js | 1 + rootfs overrides/usr/bin/sheebr.js | 60 + rootfs overrides/usr/bin/touch.js | 4 + .../usr/lib/sheebr/environment.js | 5 + rootfs overrides/usr/lib/sheebr/lexer.js | 57 + .../usr/lib/sheebr/readLineSync.js | 1329 +++++++++++++++++ rootfs overrides/usr/lib/sheebr/utils.js | 31 + 30 files changed, 1743 insertions(+), 45 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 binding.gyp create mode 100755 clean.sh create mode 100755 clibs.sh create mode 100644 clibs/chibrax/syscall.cc create mode 100644 package.json delete mode 100755 package.sh create mode 100644 rootfs overrides/chibrax/hook.js create mode 100644 rootfs overrides/chibrax/input.js create mode 100644 rootfs overrides/chibrax/package.json create mode 100644 rootfs overrides/chibrax/shutdown.js create mode 100644 rootfs overrides/chibrax/sleep.js create mode 100755 rootfs overrides/chibrax/syscall.node create mode 100644 rootfs overrides/chibrax/tsconfig.json create mode 100644 rootfs overrides/usr/bin/echo.js create mode 100644 rootfs overrides/usr/bin/ls.js create mode 100644 rootfs overrides/usr/bin/mkdir.js create mode 100644 rootfs overrides/usr/bin/pwd.js create mode 100644 rootfs overrides/usr/bin/sheebr.js create mode 100644 rootfs overrides/usr/bin/touch.js create mode 100644 rootfs overrides/usr/lib/sheebr/environment.js create mode 100644 rootfs overrides/usr/lib/sheebr/lexer.js create mode 100644 rootfs overrides/usr/lib/sheebr/readLineSync.js create mode 100644 rootfs overrides/usr/lib/sheebr/utils.js diff --git a/.gitignore b/.gitignore index fa32696..d633cc6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ chibrax.tar.gz rootfs linux-firmware disk.img -diskoffset \ No newline at end of file +diskoffset +package-lock.json +build +node_modules \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..26df38b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.linting.enabled": false +} \ No newline at end of file diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..fba7035 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "syscall", + 'cflags': [ '-Wall' ], + "sources": [ + "clibs/chibrax/syscall.cc" + ] + } + ] +} \ No newline at end of file diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..36cd887 --- /dev/null +++ b/clean.sh @@ -0,0 +1,4 @@ +rm -rf rootfs +rm -rf build +rm -f disk.img +rm -f diskoffset \ No newline at end of file diff --git a/clibs.sh b/clibs.sh new file mode 100755 index 0000000..6edeabb --- /dev/null +++ b/clibs.sh @@ -0,0 +1,5 @@ +#!/bin/bash +node node_modules/node-gyp/bin/node-gyp.js configure +node node_modules/node-gyp/bin/node-gyp.js build + +cp build/Release/syscall.node rootfs\ overrides/chibrax/syscall.node \ No newline at end of file diff --git a/clibs/chibrax/syscall.cc b/clibs/chibrax/syscall.cc new file mode 100644 index 0000000..4d0794d --- /dev/null +++ b/clibs/chibrax/syscall.cc @@ -0,0 +1,52 @@ +#include /* Definition of SYS_* constants */ +#include +#include +#include + + +#ifdef RB_HALT_SYSTEM +# define BMAGIC_HALT RB_HALT_SYSTEM +#else +# define BMAGIC_HALT RB_HALT +#endif + +#define BMAGIC_REBOOT RB_AUTOBOOT + +#define BMAGIC_POWEROFF RB_POWER_OFF + + +namespace syscallNode { + void nodeSyscall(const v8::FunctionCallbackInfo& args) { + v8::Isolate* isolate = args.GetIsolate(); + + if (!args[0]->IsNumber()) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, "Argument must be a number").ToLocalChecked())); + return; + } + + long argsValue = args[0].As()->Value(); + syscall(argsValue); + } + + void nodeHalt(const v8::FunctionCallbackInfo& args) { + reboot(BMAGIC_HALT); + } + + void nodeReboot(const v8::FunctionCallbackInfo& args) { + reboot(BMAGIC_REBOOT); + } + + void nodePoweroff(const v8::FunctionCallbackInfo& args) { + reboot(BMAGIC_POWEROFF); + } + + void Initialize(v8::Local exports) { + NODE_SET_METHOD(exports, "halt", nodeHalt); + NODE_SET_METHOD(exports, "reboot", nodeReboot); + NODE_SET_METHOD(exports, "syscall", nodeSyscall); + NODE_SET_METHOD(exports, "poweroff", nodePoweroff); + } + + NODE_MODULE(syscall_api, Initialize) +} diff --git a/copyRootfsIntoTheImage.sh b/copyRootfsIntoTheImage.sh index 92cf441..2c6a3e7 100755 --- a/copyRootfsIntoTheImage.sh +++ b/copyRootfsIntoTheImage.sh @@ -10,6 +10,14 @@ then echo "Please run createLiveImage.sh first" exit fi +echo "rebuilding C++ hooks" +./clibs.sh +echo "done" + +echo "coping overrides..." +sudo cp rootfs\ overrides/* rootfs/ -r +echo "done" + CHIBRAX_MOUNT_POINT="/tmp/chibraxMountPoint" diff --git a/createLiveImage.sh b/createLiveImage.sh index 4eb1127..1dd301d 100755 --- a/createLiveImage.sh +++ b/createLiveImage.sh @@ -33,6 +33,10 @@ mkdir $CHIBRAX_MOUNT_POINT -p mkfs.ext4 ${LOOP} mount ${LOOP} $CHIBRAX_MOUNT_POINT +echo "rebuilding C++ hooks" +./clibs.sh +echo "done" + echo "coping rootfs" cp rootfs/* $CHIBRAX_MOUNT_POINT -r echo "done" diff --git a/kernel/.gitignore b/kernel/.gitignore index 496f9a4..6052cdb 100644 --- a/kernel/.gitignore +++ b/kernel/.gitignore @@ -1,2 +1,2 @@ dist/ -src/ +src/ \ No newline at end of file diff --git a/kernel/from_current.sh b/kernel/from_current.sh index e48c144..ed9ddc7 100755 --- a/kernel/from_current.sh +++ b/kernel/from_current.sh @@ -1,4 +1,7 @@ -pwd +if [ ! -d src ] +then + git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ src +fi cd src make mrproper cp ../.config .config diff --git a/package.json b/package.json new file mode 100644 index 0000000..66450fc --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "node-gyp": "^8.4.1" + } +} diff --git a/package.sh b/package.sh deleted file mode 100755 index 06664e7..0000000 --- a/package.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -sudo rm root.iso -sudo rm chibrax.tar.gz - -echo "Cloning the firmware" -git clone https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git -cd linux-firmware && git pull && cd - -sudo mkdir -p rootfs/usr/lib/firmware -sudo cp linux-firmware/* rootfs/usr/lib/firmware/ -r - -sudo cp rootfs\ overrides/* rootfs/ -r - -sudo rm rootfs/var/cache -rf - -sudo tar -czvf chibrax.tar.gz rootfs -sudo mkisofs -o rootfs.iso chibrax.tar.gz - -sudo rm rootfs/etc/grub.d/10_linux -sudo rm rootfs/etc/grub.d/10_linux_xen -sudo rm rootfs/etc/grub.d/30_os-prober \ No newline at end of file diff --git a/rootfs overrides/chibrax/hook.js b/rootfs overrides/chibrax/hook.js new file mode 100644 index 0000000..4c0f0cf --- /dev/null +++ b/rootfs overrides/chibrax/hook.js @@ -0,0 +1,27 @@ +//@ts-check +const { exec, fork } = require('child_process'); +/** + * @param {string} path + * @deprecated + */ +function hookTo(path) { + exec(`node ${path}`) +} + +/** + * @param {string} path + */ +async function fHook(path) { + const child = fork(path) + return new Promise((res, rej) => { + child.on('exit', () => { + res() + }) + }) +} + +function rHook(path) { + +} + +module.exports = { hookTo, fHook } \ No newline at end of file diff --git a/rootfs overrides/chibrax/index.js b/rootfs overrides/chibrax/index.js index 720f616..287cb9e 100644 --- a/rootfs overrides/chibrax/index.js +++ b/rootfs overrides/chibrax/index.js @@ -1,29 +1,53 @@ -const audio = require('./audio.js') +//@ts-check +function panic() { + console.log("/!\\ INIT PANIK /!\\ LOSE CHIBRE") + while(1) {} +} -console.log("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") -console.log(` +async function main() { + try { + const audio = require('./audio.js') + //@ts-expect-error + const { poweroff, halt } = require('./shutdown.js') + const input = require('./input.js') + const { fHook } = require("./hook.js") + + console.log("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") + console.log(` - _______ _________ ______ _______ _______ -( ____ \\|\\ /|\\__ __/( ___ \\ ( ____ )( ___ )|\\ /| -| ( \\/| ) ( | ) ( | ( ) )| ( )|| ( ) |( \\ / ) -| | | (___) | | | | (__/ / | (____)|| (___) | \\ (_) / -| | | ___ | | | | __ ( | __)| ___ | ) _ ( -| | | ( ) | | | | ( \\ \\ | (\\ ( | ( ) | / ( ) \\ -| (____/\\| ) ( |___) (___| )___) )| ) \\ \\__| ) ( |( / \\ ) -(_______/|/ \\|\\_______/|/ \\___/ |/ \\__/|/ \\||/ \\| + _______ _________ ______ _______ _______ + ( ____ \\|\\ /|\\__ __/( ___ \\ ( ____ )( ___ )|\\ /| + | ( \\/| ) ( | ) ( | ( ) )| ( )|| ( ) |( \\ / ) + | | | (___) | | | | (__/ / | (____)|| (___) | \\ (_) / + | | | ___ | | | | __ ( | __)| ___ | ) _ ( + | | | ( ) | | | | ( \\ \\ | (\\ ( | ( ) | / ( ) \\ + | (____/\\| ) ( |___) (___| )___) )| ) \\ \\__| ) ( |( / \\ ) + (_______/|/ \\|\\_______/|/ \\___/ |/ \\__/|/ \\||/ \\| - _______ _______ - ( ___ )( ____ \\ - | ( ) || ( \\/ - | | | || (_____ - | | | |(_____ ) - | | | | ) | - | (___) |/\\____) | - (_______)\\_______) + _______ _______ + ( ___ )( ____ \\ + | ( ) || ( \\/ + | | | || (_____ + | | | |(_____ ) + | | | | ) | + | (___) |/\\____) | + (_______)\\_______) -`) + `) -audio('/chibrax/sheeebr.wav') + audio('/chibrax/sheeebr.wav') -while(1) {} + //chainload into sheebr + await fHook("/usr/bin/sheebr.js") + poweroff() + + /*while(1){} + console.log("End of init reached") + panic()*/ + } catch(e) { + console.log(e) + panic() + } +} +main() diff --git a/rootfs overrides/chibrax/input.js b/rootfs overrides/chibrax/input.js new file mode 100644 index 0000000..5e87c3e --- /dev/null +++ b/rootfs overrides/chibrax/input.js @@ -0,0 +1,27 @@ +//@ts-check + +/** + * + * @param {string} str + * @returns + */ +async function input(str) { + console.log(str) + process.stdin.resume(); + process.stdin.setEncoding('utf8'); + let line = ""; + return new Promise((res, rej) => { + process.stdin.on('data', function(chunk) { + line += chunk + if (chunk.toString('utf-8').endsWith('\n')) { + process.stdin.pause() + res(line.substring(0, line.length -1)) + } + }); + process.stdin.on('error', function(err) { + rej(err) + }) + }) +} + +module.exports = input \ No newline at end of file diff --git a/rootfs overrides/chibrax/package.json b/rootfs overrides/chibrax/package.json new file mode 100644 index 0000000..6b09ba8 --- /dev/null +++ b/rootfs overrides/chibrax/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/node": "^16.11.10" + } +} diff --git a/rootfs overrides/chibrax/shutdown.js b/rootfs overrides/chibrax/shutdown.js new file mode 100644 index 0000000..bcb676a --- /dev/null +++ b/rootfs overrides/chibrax/shutdown.js @@ -0,0 +1,2 @@ +syscalls = require('./syscall.node') +module.exports = syscalls \ No newline at end of file diff --git a/rootfs overrides/chibrax/sleep.js b/rootfs overrides/chibrax/sleep.js new file mode 100644 index 0000000..7858f70 --- /dev/null +++ b/rootfs overrides/chibrax/sleep.js @@ -0,0 +1,9 @@ +async function mSleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +async function sleep(s) { + return mSleep(s * 1000) +} + +module.exports = { mSleep, sleep } \ No newline at end of file diff --git a/rootfs overrides/chibrax/syscall.node b/rootfs overrides/chibrax/syscall.node new file mode 100755 index 0000000000000000000000000000000000000000..f1d198b1976f632a41e1a02df87b5cfa8231691a GIT binary patch literal 17472 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 3){ + console.log(process.argv[3]) +} \ No newline at end of file diff --git a/rootfs overrides/usr/bin/ls.js b/rootfs overrides/usr/bin/ls.js new file mode 100644 index 0000000..f2de14a --- /dev/null +++ b/rootfs overrides/usr/bin/ls.js @@ -0,0 +1,14 @@ +const fs = require('fs') + +let path = process.argv[3] ?? '.' +let optionsArray = process.argv.slice(4) ?? [] +let options = [] +for(let option of optionsArray){ + if(option.startwith("--")) options.push(option.slice) + else if(options.startwith("-")) options.push += option.slice(1).split('').map(el => {return '-' + el}) +} + +const files = fs.readdirSync(path); +const nonHiddenFiles = files.filter(file => file[0] != '.') + +console.log(options.includes('-a') ? files.join(' ') : nonHiddenFiles.join(' ')) \ No newline at end of file diff --git a/rootfs overrides/usr/bin/mkdir.js b/rootfs overrides/usr/bin/mkdir.js new file mode 100644 index 0000000..4f01325 --- /dev/null +++ b/rootfs overrides/usr/bin/mkdir.js @@ -0,0 +1,6 @@ +const fs = require('fs') +if(process.argv.length >= 3){ + fs.mkdir(process.argv[3], { recursive: true }, (err) => { + if (err) throw err; + }); +} \ No newline at end of file diff --git a/rootfs overrides/usr/bin/pwd.js b/rootfs overrides/usr/bin/pwd.js new file mode 100644 index 0000000..23bd76f --- /dev/null +++ b/rootfs overrides/usr/bin/pwd.js @@ -0,0 +1 @@ +console.log(process.cwd()) \ No newline at end of file diff --git a/rootfs overrides/usr/bin/sheebr.js b/rootfs overrides/usr/bin/sheebr.js new file mode 100644 index 0000000..b7928e5 --- /dev/null +++ b/rootfs overrides/usr/bin/sheebr.js @@ -0,0 +1,60 @@ +const { Lexer } = require('../lib/sheebr/lexer') +const { getline } = require('../lib/sheebr/utils') +const fs = require('fs') +const environment = require('../lib/sheebr/environment') +const readline = require('readline').createInterface({ + input: process.stdin, + output: process.stdout, + terminal: false +}); +const readlineSync = require('../lib/sheebr/readLineSync') +let execSync = require('child_process').execSync + + +let current_dir = process.cwd() + +start() + +async function start(){ + while(true){ + input = await getline("sheebr> "); + if(input == "exit") break; + else evaluate(input); + } +} + +function evaluate(input){ + let lexer = new Lexer(input) + args = lexer.scanTokens() + + if(args[0] == "cd"){ + if(args.length != 2){ + console.log("Wrong number of arguments"); return;} + if(!fs.existsSync(args[1])){console.log("Folder does not exist"); return;} + current_dir = args[1][0] == "/" ? args[1] : current_dir + '/' + args[1]; + console.log(current_dir) + return; + } + // check for all folders in path + + let found = false; + for(let path of environment.path){ + let tobreak = false; + const files = fs.readdirSync(path); + if(files.includes(`${args[0]}.js`)){ + found = true; + script = execSync(`node ${path + '/' + args[0]}.js ${args.map(arg => {return arg.includes(' ') ? `"${arg}"` : arg}).join(' ')}`, {cwd: current_dir}); + process.stdout.write(script) + tobreak = true; + } + + if(tobreak) break; + } + if(!found){ + console.log("Program not found") + } + let program = args[0] +} + + + diff --git a/rootfs overrides/usr/bin/touch.js b/rootfs overrides/usr/bin/touch.js new file mode 100644 index 0000000..9df0db5 --- /dev/null +++ b/rootfs overrides/usr/bin/touch.js @@ -0,0 +1,4 @@ +const fs = require('fs') +if(process.argv.length >= 3){ + fs.openSync(process.argv[3], 'w'); +} \ No newline at end of file diff --git a/rootfs overrides/usr/lib/sheebr/environment.js b/rootfs overrides/usr/lib/sheebr/environment.js new file mode 100644 index 0000000..2f5ebbf --- /dev/null +++ b/rootfs overrides/usr/lib/sheebr/environment.js @@ -0,0 +1,5 @@ +let environment = { + path: ["/sheebr", "/usr/bin"] +} + +module.exports = environment \ No newline at end of file diff --git a/rootfs overrides/usr/lib/sheebr/lexer.js b/rootfs overrides/usr/lib/sheebr/lexer.js new file mode 100644 index 0000000..994ae4a --- /dev/null +++ b/rootfs overrides/usr/lib/sheebr/lexer.js @@ -0,0 +1,57 @@ +const { isAlpha } = require('./utils') + + +class Lexer{ + constructor(source){ + this.tokens = []; + this.current = 0; + this.start = 0; + this.source = source + } + + scanTokens(){ + while(this.current < this.source.length){ + this.start = this.current + this.scanToken() + + } + return this.tokens; + } + + scanToken(){ + + if(this.source[this.current] == "\""){ + this.string() + } + else{ + let content = "" + while(this.source[this.current] != ' ' && this.source[this.current] != '\n' && this.current < this.source.length ){ + content += this.source[this.current]; + this.current++; + + } + this.tokens.push(content) + } + this.current++; + + } + + string(){ + this.current++; + let content = "" + while(this.source[this.current] != "\"" && this.current < this.source.length){ + content += this.source[this.current]; + this.current++; + } + this.tokens.push(content) + } + + addToken(tokenType, value=""){ + this.tokens.push(Token(tokenType, value)) + } +} + + +module.exports = { + Lexer +} diff --git a/rootfs overrides/usr/lib/sheebr/readLineSync.js b/rootfs overrides/usr/lib/sheebr/readLineSync.js new file mode 100644 index 0000000..f6a512c --- /dev/null +++ b/rootfs overrides/usr/lib/sheebr/readLineSync.js @@ -0,0 +1,1329 @@ +/* + * readlineSync + * https://github.com/anseki/readline-sync + * + * Copyright (c) 2019 anseki + * Licensed under the MIT license. + */ + +'use strict'; + +var + IS_WIN = process.platform === 'win32', + + ALGORITHM_CIPHER = 'aes-256-cbc', + ALGORITHM_HASH = 'sha256', + DEFAULT_ERR_MSG = 'The current environment doesn\'t support interactive reading from TTY.', + + fs = require('fs'), + TTY = process.binding('tty_wrap').TTY, + childProc = require('child_process'), + pathUtil = require('path'), + + defaultOptions = { + /* eslint-disable key-spacing */ + prompt: '> ', + hideEchoBack: false, + mask: '*', + limit: [], + limitMessage: 'Input another, please.$<( [)limit(])>', + defaultInput: '', + trueValue: [], + falseValue: [], + caseSensitive: false, + keepWhitespace: false, + encoding: 'utf8', + bufferSize: 1024, + print: void 0, + history: true, + cd: false, + phContent: void 0, + preCheck: void 0 + /* eslint-enable key-spacing */ + }, + + fdR = 'none', + isRawMode = false, + salt = 0, + lastInput = '', + inputHistory = [], + _DBG_useExt = false, + _DBG_checkOptions = false, + _DBG_checkMethod = false, + fdW, ttyR, extHostPath, extHostArgs, tempdir, rawInput; + +function getHostArgs(options) { + // Send any text to crazy Windows shell safely. + function encodeArg(arg) { + return arg.replace(/[^\w\u0080-\uFFFF]/g, function(chr) { + return '#' + chr.charCodeAt(0) + ';'; + }); + } + + return extHostArgs.concat((function(conf) { + var args = []; + Object.keys(conf).forEach(function(optionName) { + if (conf[optionName] === 'boolean') { + if (options[optionName]) { args.push('--' + optionName); } + } else if (conf[optionName] === 'string') { + if (options[optionName]) { + args.push('--' + optionName, encodeArg(options[optionName])); + } + } + }); + return args; + })({ + /* eslint-disable key-spacing */ + display: 'string', + displayOnly: 'boolean', + keyIn: 'boolean', + hideEchoBack: 'boolean', + mask: 'string', + limit: 'string', + caseSensitive: 'boolean' + /* eslint-enable key-spacing */ + })); +} + +// piping via files (for Node.js v0.10-) +function _execFileSync(options, execOptions) { + + function getTempfile(name) { + var suffix = '', + filepath, fd; + tempdir = tempdir || require('os').tmpdir(); + + while (true) { + filepath = pathUtil.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 res = {}, + pathStdout = getTempfile('readline-sync.stdout'), + pathStderr = getTempfile('readline-sync.stderr'), + pathExit = getTempfile('readline-sync.exit'), + pathDone = getTempfile('readline-sync.done'), + crypto = require('crypto'), + hostArgs, shellPath, shellArgs, exitCode, extMessage, shasum, decipher, password; + + shasum = crypto.createHash(ALGORITHM_HASH); + shasum.update('' + process.pid + (salt++) + Math.random()); + password = shasum.digest('hex'); + decipher = crypto.createDecipher(ALGORITHM_CIPHER, password); + + hostArgs = getHostArgs(options); + if (IS_WIN) { + shellPath = process.env.ComSpec || 'cmd.exe'; + process.env.Q = '"'; // The quote (") that isn't escaped. + // `()` for ignore space by echo + shellArgs = ['/V:ON', '/S', '/C', + '(%Q%' + shellPath + '%Q% /V:ON /S /C %Q%' + /* ESLint bug? */ // eslint-disable-line no-path-concat + '%Q%' + extHostPath + '%Q%' + + hostArgs.map(function(arg) { return ' %Q%' + arg + '%Q%'; }).join('') + + ' & (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 { + shellPath = '/bin/sh'; + shellArgs = ['-c', + // Use `()`, not `{}` for `-c` (text param) + '("' + extHostPath + '"' + /* ESLint bug? */ // eslint-disable-line no-path-concat + hostArgs.map(function(arg) { return " '" + arg.replace(/'/g, "'\\''") + "'"; }).join('') + + '; echo $?>"' + pathExit + '") 2>"' + pathStderr + '"' + + ' |"' + process.execPath + '" "' + __dirname + '/encrypt.js"' + + ' "' + ALGORITHM_CIPHER + '" "' + password + '"' + + ' >"' + pathStdout + '"' + + '; echo 1 >"' + pathDone + '"']; + } + if (_DBG_checkMethod) { _DBG_checkMethod('_execFileSync', hostArgs); } + try { + childProc.spawn(shellPath, shellArgs, execOptions); + } catch (e) { + res.error = new Error(e.message); + res.error.method = '_execFileSync - spawn'; + res.error.program = shellPath; + res.error.args = shellArgs; + } + + while (fs.readFileSync(pathDone, {encoding: options.encoding}).trim() !== '1') {} // eslint-disable-line no-empty + if ((exitCode = + fs.readFileSync(pathExit, {encoding: options.encoding}).trim()) === '0') { + res.input = + decipher.update(fs.readFileSync(pathStdout, {encoding: 'binary'}), + 'hex', options.encoding) + + decipher.final(options.encoding); + } else { + extMessage = fs.readFileSync(pathStderr, {encoding: options.encoding}).trim(); + res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); + res.error.method = '_execFileSync'; + res.error.program = shellPath; + res.error.args = shellArgs; + res.error.extMessage = extMessage; + res.error.exitCode = +exitCode; + } + + fs.unlinkSync(pathStdout); + fs.unlinkSync(pathStderr); + fs.unlinkSync(pathExit); + fs.unlinkSync(pathDone); + + return res; +} + +function readlineExt(options) { + var res = {}, + execOptions = {env: process.env, encoding: options.encoding}, + hostArgs, extMessage; + + if (!extHostPath) { + if (IS_WIN) { + if (process.env.PSModulePath) { // Windows PowerShell + extHostPath = 'powershell.exe'; + extHostArgs = ['-ExecutionPolicy', 'Bypass', + '-File', __dirname + '\\read.ps1']; // eslint-disable-line no-path-concat + } else { // Windows Script Host + extHostPath = 'cscript.exe'; + extHostArgs = ['//nologo', __dirname + '\\read.wsh.js']; // eslint-disable-line no-path-concat + } + } else { + extHostPath = '/bin/sh'; + extHostArgs = [__dirname + '/read.sh']; // eslint-disable-line no-path-concat + } + } + if (IS_WIN && !process.env.PSModulePath) { // Windows Script Host + // ScriptPW (Win XP and Server2003) needs TTY stream as STDIN. + // In this case, If STDIN isn't TTY, an error is thrown. + execOptions.stdio = [process.stdin]; + } + + if (childProc.execFileSync) { + hostArgs = getHostArgs(options); + if (_DBG_checkMethod) { _DBG_checkMethod('execFileSync', hostArgs); } + try { + res.input = childProc.execFileSync(extHostPath, hostArgs, execOptions); + } catch (e) { // non-zero exit code + extMessage = e.stderr ? (e.stderr + '').trim() : ''; + res.error = new Error(DEFAULT_ERR_MSG + (extMessage ? '\n' + extMessage : '')); + res.error.method = 'execFileSync'; + res.error.program = extHostPath; + res.error.args = hostArgs; + res.error.extMessage = extMessage; + res.error.exitCode = e.status; + res.error.code = e.code; + res.error.signal = e.signal; + } + } else { + res = _execFileSync(options, execOptions); + } + if (!res.error) { + res.input = res.input.replace(/^\s*'|'\s*$/g, ''); + options.display = ''; + } + + return res; +} + +/* + display: string + displayOnly: boolean + keyIn: boolean + hideEchoBack: boolean + mask: string + limit: string (pattern) + caseSensitive: boolean + keepWhitespace: boolean + encoding, bufferSize, print +*/ +function _readlineSync(options) { + var input = '', + displaySave = options.display, + silent = !options.display && options.keyIn && options.hideEchoBack && !options.mask; + + function tryExt() { + var res = readlineExt(options); + if (res.error) { throw res.error; } + return res.input; + } + + if (_DBG_checkOptions) { _DBG_checkOptions(options); } + + (function() { // open TTY + var fsB, constants, verNum; + + function getFsB() { + if (!fsB) { + fsB = process.binding('fs'); // For raw device path + constants = process.binding('constants'); + // for v6.3.0+ + constants = constants && constants.fs && typeof constants.fs.O_RDWR === 'number' + ? constants.fs : constants; + } + return fsB; + } + + if (typeof fdR !== 'string') { return; } + fdR = null; + + if (IS_WIN) { + // iojs-v2.3.2+ input stream can't read first line. (#18) + // ** Don't get process.stdin before check! ** + // Fixed v5.1.0 + // Fixed v4.2.4 + // It regressed again in v5.6.0, it is fixed in v6.2.0. + verNum = (function(ver) { // getVerNum + var nums = ver.replace(/^\D+/, '').split('.'); + var verNum = 0; + if ((nums[0] = +nums[0])) { verNum += nums[0] * 10000; } + if ((nums[1] = +nums[1])) { verNum += nums[1] * 100; } + if ((nums[2] = +nums[2])) { verNum += nums[2]; } + return verNum; + })(process.version); + if (!(verNum >= 20302 && verNum < 40204 || verNum >= 50000 && verNum < 50100 || verNum >= 50600 && verNum < 60200) && + process.stdin.isTTY) { + process.stdin.pause(); + fdR = process.stdin.fd; + ttyR = process.stdin._handle; + } else { + try { + // The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode. + // 'CONIN$' might fail on XP, 2000, 7 (x86). + fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8)); + ttyR = new TTY(fdR, true); + } catch (e) { /* ignore */ } + } + + if (process.stdout.isTTY) { + fdW = process.stdout.fd; + } else { + try { + fdW = fs.openSync('\\\\.\\CON', 'w'); + } catch (e) { /* ignore */ } + if (typeof fdW !== 'number') { // Retry + try { + fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8)); + } catch (e) { /* ignore */ } + } + } + + } else { + if (process.stdin.isTTY) { + process.stdin.pause(); + try { + fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin + ttyR = process.stdin._handle; + } catch (e) { /* ignore */ } + } else { + // Node.js v0.12 read() fails. + try { + fdR = fs.openSync('/dev/tty', 'r'); + ttyR = new TTY(fdR, false); + } catch (e) { /* ignore */ } + } + + if (process.stdout.isTTY) { + fdW = process.stdout.fd; + } else { + try { + fdW = fs.openSync('/dev/tty', 'w'); + } catch (e) { /* ignore */ } + } + } + })(); + + (function() { // try read + var isCooked = !options.hideEchoBack && !options.keyIn, + atEol, limit, buffer, reqSize, readSize, chunk, line; + rawInput = ''; + + // Node.js 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 (_DBG_useExt || !ttyR || + typeof fdW !== 'number' && (options.display || !isCooked)) { + input = tryExt(); + return; + } + + if (options.display) { + fs.writeSync(fdW, options.display); + options.display = ''; + } + if (options.displayOnly) { return; } + + if (!setRawMode(!isCooked)) { + input = tryExt(); + return; + } + + reqSize = options.keyIn ? 1 : options.bufferSize; + // Check `allocUnsafe` to make sure of the new API. + buffer = Buffer.allocUnsafe && Buffer.alloc ? Buffer.alloc(reqSize) : new Buffer(reqSize); + + if (options.keyIn && options.limit) { + limit = new RegExp('[^' + options.limit + ']', + 'g' + (options.caseSensitive ? '' : 'i')); + } + + while (true) { + readSize = 0; + try { + readSize = fs.readSync(fdR, buffer, 0, reqSize); + } catch (e) { + if (e.code !== 'EOF') { + setRawMode(false); + input += tryExt(); + return; + } + } + if (readSize > 0) { + chunk = buffer.toString(options.encoding, 0, readSize); + rawInput += chunk; + } else { + chunk = '\n'; + rawInput += String.fromCharCode(0); + } + + if (chunk && typeof (line = (chunk.match(/^(.*?)[\r\n]/) || [])[1]) === 'string') { + chunk = line; + atEol = true; + } + + // other ctrl-chars + // eslint-disable-next-line no-control-regex + if (chunk) { chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, ''); } + if (chunk && limit) { chunk = chunk.replace(limit, ''); } + + if (chunk) { + if (!isCooked) { + if (!options.hideEchoBack) { + fs.writeSync(fdW, chunk); + } else if (options.mask) { + fs.writeSync(fdW, (new Array(chunk.length + 1)).join(options.mask)); + } + } + input += chunk; + } + + if (!options.keyIn && atEol || + options.keyIn && input.length >= reqSize) { break; } + } + + if (!isCooked && !silent) { fs.writeSync(fdW, '\n'); } + setRawMode(false); + })(); + + if (options.print && !silent) { + options.print( + displaySave + ( + options.displayOnly ? '' : ( + options.hideEchoBack ? (new Array(input.length + 1)).join(options.mask) : input + ) + '\n' // must at least write '\n' + ), + options.encoding); + } + + return options.displayOnly ? '' : + (lastInput = options.keepWhitespace || options.keyIn ? input : input.trim()); +} + +function flattenArray(array, validator) { + var flatArray = []; + function _flattenArray(array) { + if (array == null) { return; } + if (Array.isArray(array)) { + array.forEach(_flattenArray); + } else if (!validator || validator(array)) { + flatArray.push(array); + } + } + _flattenArray(array); + return flatArray; +} + +function escapePattern(pattern) { + return pattern.replace(/[\x00-\x7f]/g, // eslint-disable-line no-control-regex + function(s) { return '\\x' + ('00' + s.charCodeAt().toString(16)).substr(-2); }); +} + +// mergeOptions(options1, options2 ... ) +// mergeOptions(true, options1, options2 ... ) +// arg1=true : Start from defaultOptions and pick elements of that. +function mergeOptions() { + var optionsList = Array.prototype.slice.call(arguments), + optionNames, fromDefault; + + if (optionsList.length && typeof optionsList[0] === 'boolean') { + fromDefault = optionsList.shift(); + if (fromDefault) { + optionNames = Object.keys(defaultOptions); + optionsList.unshift(defaultOptions); + } + } + + return optionsList.reduce(function(options, optionsPart) { + if (optionsPart == null) { return options; } + + // ======== DEPRECATED ======== + if (optionsPart.hasOwnProperty('noEchoBack') && + !optionsPart.hasOwnProperty('hideEchoBack')) { + optionsPart.hideEchoBack = optionsPart.noEchoBack; + delete optionsPart.noEchoBack; + } + if (optionsPart.hasOwnProperty('noTrim') && + !optionsPart.hasOwnProperty('keepWhitespace')) { + optionsPart.keepWhitespace = optionsPart.noTrim; + delete optionsPart.noTrim; + } + // ======== /DEPRECATED ======== + + if (!fromDefault) { optionNames = Object.keys(optionsPart); } + optionNames.forEach(function(optionName) { + var value; + if (!optionsPart.hasOwnProperty(optionName)) { return; } + value = optionsPart[optionName]; + /* eslint-disable no-multi-spaces */ + switch (optionName) { + // _readlineSync <- * * -> defaultOptions + // ================ string + case 'mask': // * * + case 'limitMessage': // * + case 'defaultInput': // * + case 'encoding': // * * + value = value != null ? value + '' : ''; + if (value && optionName !== 'limitMessage') { value = value.replace(/[\r\n]/g, ''); } + options[optionName] = value; + break; + // ================ number(int) + case 'bufferSize': // * * + if (!isNaN(value = parseInt(value, 10)) && typeof value === 'number') { + options[optionName] = value; // limited updating (number is needed) + } + break; + // ================ boolean + case 'displayOnly': // * + case 'keyIn': // * + case 'hideEchoBack': // * * + case 'caseSensitive': // * * + case 'keepWhitespace': // * * + case 'history': // * + case 'cd': // * + options[optionName] = !!value; + break; + // ================ array + case 'limit': // * * to string for readlineExt + case 'trueValue': // * + case 'falseValue': // * + options[optionName] = flattenArray(value, function(value) { + var type = typeof value; + return type === 'string' || type === 'number' || + type === 'function' || value instanceof RegExp; + }).map(function(value) { + return typeof value === 'string' ? value.replace(/[\r\n]/g, '') : value; + }); + break; + // ================ function + case 'print': // * * + case 'phContent': // * + case 'preCheck': // * + options[optionName] = typeof value === 'function' ? value : void 0; + break; + // ================ other + case 'prompt': // * + case 'display': // * + options[optionName] = value != null ? value : ''; + break; + // no default + } + /* eslint-enable no-multi-spaces */ + }); + return options; + }, {}); +} + +function isMatched(res, comps, caseSensitive) { + return comps.some(function(comp) { + var type = typeof comp; + return type === 'string' + ? (caseSensitive ? res === comp : res.toLowerCase() === comp.toLowerCase()) : + type === 'number' ? parseFloat(res) === comp : + type === 'function' ? comp(res) : + comp instanceof RegExp ? comp.test(res) : false; + }); +} + +function replaceHomePath(path, expand) { + var homePath = pathUtil.normalize( + IS_WIN ? (process.env.HOMEDRIVE || '') + (process.env.HOMEPATH || '') : + process.env.HOME || '').replace(/[/\\]+$/, ''); + path = pathUtil.normalize(path); + return expand ? path.replace(/^~(?=\/|\\|$)/, homePath) : + path.replace(new RegExp('^' + escapePattern(homePath) + + '(?=\\/|\\\\|$)', IS_WIN ? 'i' : ''), '~'); +} + +function replacePlaceholder(text, generator) { + var PTN_INNER = '(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?', + rePlaceholder = new RegExp('(\\$)?(\\$<' + PTN_INNER + '>)', 'g'), + rePlaceholderCompat = new RegExp('(\\$)?(\\$\\{' + PTN_INNER + '\\})', 'g'); + + function getPlaceholderText(s, escape, placeholder, pre, param, post) { + var text; + return escape || typeof (text = generator(param)) !== 'string' ? placeholder : + text ? (pre || '') + text + (post || '') : ''; + } + + return text.replace(rePlaceholder, getPlaceholderText) + .replace(rePlaceholderCompat, getPlaceholderText); +} + +function array2charlist(array, caseSensitive, collectSymbols) { + var group = [], + groupClass = -1, + charCode = 0, + symbols = '', + values, suppressed; + function addGroup(groups, group) { + if (group.length > 3) { // ellipsis + groups.push(group[0] + '...' + group[group.length - 1]); + suppressed = true; + } else if (group.length) { + groups = groups.concat(group); + } + return groups; + } + + values = array.reduce(function(chars, value) { + return chars.concat((value + '').split('')); + }, []).reduce(function(groups, curChar) { + var curGroupClass, curCharCode; + if (!caseSensitive) { curChar = curChar.toLowerCase(); } + curGroupClass = /^\d$/.test(curChar) ? 1 : + /^[A-Z]$/.test(curChar) ? 2 : /^[a-z]$/.test(curChar) ? 3 : 0; + if (collectSymbols && curGroupClass === 0) { + symbols += curChar; + } else { + curCharCode = curChar.charCodeAt(0); + if (curGroupClass && curGroupClass === groupClass && + curCharCode === charCode + 1) { + group.push(curChar); + } else { + groups = addGroup(groups, group); + group = [curChar]; + groupClass = curGroupClass; + } + charCode = curCharCode; + } + return groups; + }, []); + values = addGroup(values, group); // last group + if (symbols) { values.push(symbols); suppressed = true; } + return {values: values, suppressed: suppressed}; +} + +function joinChunks(chunks, suppressed) { + return chunks.join(chunks.length > 2 ? ', ' : suppressed ? ' / ' : '/'); +} + +function getPhContent(param, options) { + var resCharlist = {}, + text, values, arg; + if (options.phContent) { + text = options.phContent(param, options); + } + if (typeof text !== 'string') { + switch (param) { + case 'hideEchoBack': + case 'mask': + case 'defaultInput': + case 'caseSensitive': + case 'keepWhitespace': + case 'encoding': + case 'bufferSize': + case 'history': + case 'cd': + text = !options.hasOwnProperty(param) ? '' : + typeof options[param] === 'boolean' ? (options[param] ? 'on' : 'off') : + options[param] + ''; + break; + // case 'prompt': + // case 'query': + // case 'display': + // text = options.hasOwnProperty('displaySrc') ? options.displaySrc + '' : ''; + // break; + case 'limit': + case 'trueValue': + case 'falseValue': + values = options[options.hasOwnProperty(param + 'Src') ? param + 'Src' : param]; + if (options.keyIn) { // suppress + resCharlist = array2charlist(values, options.caseSensitive); + values = resCharlist.values; + } else { + values = values.filter(function(value) { + var type = typeof value; + return type === 'string' || type === 'number'; + }); + } + text = joinChunks(values, resCharlist.suppressed); + break; + case 'limitCount': + case 'limitCountNotZero': + text = options[options.hasOwnProperty('limitSrc') ? 'limitSrc' : 'limit'].length; + text = text || param !== 'limitCountNotZero' ? text + '' : ''; + break; + case 'lastInput': + text = lastInput; + break; + case 'cwd': + case 'CWD': + case 'cwdHome': + text = process.cwd(); + if (param === 'CWD') { + text = pathUtil.basename(text); + } else if (param === 'cwdHome') { + text = replaceHomePath(text); + } + break; + case 'date': + case 'time': + case 'localeDate': + case 'localeTime': + text = (new Date())['to' + + param.replace(/^./, function(str) { return str.toUpperCase(); }) + + 'String'](); + break; + default: // with arg + if (typeof (arg = (param.match(/^history_m(\d+)$/) || [])[1]) === 'string') { + text = inputHistory[inputHistory.length - arg] || ''; + } + } + } + return text; +} + +function getPhCharlist(param) { + var matches = /^(.)-(.)$/.exec(param), + text = '', + from, to, code, step; + if (!matches) { return null; } + from = matches[1].charCodeAt(0); + to = matches[2].charCodeAt(0); + step = from < to ? 1 : -1; + for (code = from; code !== to + step; code += step) { text += String.fromCharCode(code); } + return text; +} + +// cmd "arg" " a r g " "" 'a"r"g' "a""rg" "arg +function parseCl(cl) { + var reToken = new RegExp(/(\s*)(?:("|')(.*?)(?:\2|$)|(\S+))/g), + taken = '', + args = [], + matches, part; + cl = cl.trim(); + while ((matches = reToken.exec(cl))) { + part = matches[3] || matches[4] || ''; + if (matches[1]) { + args.push(taken); + taken = ''; + } + taken += part; + } + if (taken) { args.push(taken); } + return args; +} + +function toBool(res, options) { + return ( + (options.trueValue.length && + isMatched(res, options.trueValue, options.caseSensitive)) ? true : + (options.falseValue.length && + isMatched(res, options.falseValue, options.caseSensitive)) ? false : res); +} + +function getValidLine(options) { + var res, forceNext, limitMessage, + matches, histInput, args, resCheck; + + function _getPhContent(param) { return getPhContent(param, options); } + function addDisplay(text) { options.display += (/[^\r\n]$/.test(options.display) ? '\n' : '') + text; } + + options.limitSrc = options.limit; + options.displaySrc = options.display; + options.limit = ''; // for readlineExt + options.display = replacePlaceholder(options.display + '', _getPhContent); + + while (true) { + res = _readlineSync(options); + forceNext = false; + limitMessage = ''; + + if (options.defaultInput && !res) { res = options.defaultInput; } + + if (options.history) { + if ((matches = /^\s*!(?:!|-1)(:p)?\s*$/.exec(res))) { // `!!` `!-1` +`:p` + histInput = inputHistory[0] || ''; + if (matches[1]) { // only display + forceNext = true; + } else { // replace input + res = histInput; + } + // Show it even if it is empty (NL only). + addDisplay(histInput + '\n'); + if (!forceNext) { // Loop may break + options.displayOnly = true; + _readlineSync(options); + options.displayOnly = false; + } + } else if (res && res !== inputHistory[inputHistory.length - 1]) { + inputHistory = [res]; + } + } + + if (!forceNext && options.cd && res) { + args = parseCl(res); + switch (args[0].toLowerCase()) { + case 'cd': + if (args[1]) { + try { + process.chdir(replaceHomePath(args[1], true)); + } catch (e) { + addDisplay(e + ''); + } + } + forceNext = true; + break; + case 'pwd': + addDisplay(process.cwd()); + forceNext = true; + break; + // no default + } + } + + if (!forceNext && options.preCheck) { + resCheck = options.preCheck(res, options); + res = resCheck.res; + if (resCheck.forceNext) { forceNext = true; } // Don't switch to false. + } + + if (!forceNext) { + if (!options.limitSrc.length || + isMatched(res, options.limitSrc, options.caseSensitive)) { break; } + if (options.limitMessage) { + limitMessage = replacePlaceholder(options.limitMessage, _getPhContent); + } + } + + addDisplay((limitMessage ? limitMessage + '\n' : '') + + replacePlaceholder(options.displaySrc + '', _getPhContent)); + } + return toBool(res, options); +} + +// for dev +exports._DBG_set_useExt = function(val) { _DBG_useExt = val; }; +exports._DBG_set_checkOptions = function(val) { _DBG_checkOptions = val; }; +exports._DBG_set_checkMethod = function(val) { _DBG_checkMethod = val; }; +exports._DBG_clearHistory = function() { lastInput = ''; inputHistory = []; }; + +// ------------------------------------ + +exports.setDefaultOptions = function(options) { + defaultOptions = mergeOptions(true, options); + return mergeOptions(true); // copy +}; + +exports.question = function(query, options) { + /* eslint-disable key-spacing */ + return getValidLine(mergeOptions(mergeOptions(true, options), { + display: query + })); + /* eslint-enable key-spacing */ +}; + +exports.prompt = function(options) { + var readOptions = mergeOptions(true, options); + readOptions.display = readOptions.prompt; + return getValidLine(readOptions); +}; + +exports.keyIn = function(query, options) { + /* eslint-disable key-spacing */ + var readOptions = mergeOptions(mergeOptions(true, options), { + display: query, + keyIn: true, + keepWhitespace: true + }); + /* eslint-enable key-spacing */ + + // char list + readOptions.limitSrc = readOptions.limit.filter(function(value) { + var type = typeof value; + return type === 'string' || type === 'number'; + }).map(function(text) { + return replacePlaceholder(text + '', getPhCharlist); + }); + // pattern + readOptions.limit = escapePattern(readOptions.limitSrc.join('')); + + ['trueValue', 'falseValue'].forEach(function(optionName) { + readOptions[optionName] = readOptions[optionName].reduce(function(comps, comp) { + var type = typeof comp; + if (type === 'string' || type === 'number') { + comps = comps.concat((comp + '').split('')); + } else { comps.push(comp); } + return comps; + }, []); + }); + + readOptions.display = replacePlaceholder(readOptions.display + '', + function(param) { return getPhContent(param, readOptions); }); + + return toBool(_readlineSync(readOptions), readOptions); +}; + +// ------------------------------------ + +exports.questionEMail = function(query, options) { + if (query == null) { query = 'Input e-mail address: '; } + /* eslint-disable key-spacing */ + return exports.question(query, mergeOptions({ + // -------- default + hideEchoBack: false, + // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address + limit: /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/, + limitMessage: 'Input valid e-mail address, please.', + trueValue: null, + falseValue: null + }, options, { + // -------- forced + keepWhitespace: false, + cd: false + })); + /* eslint-enable key-spacing */ +}; + +exports.questionNewPassword = function(query, options) { + /* eslint-disable key-spacing */ + var resCharlist, min, max, + readOptions = mergeOptions({ + // -------- default + hideEchoBack: true, + mask: '*', + limitMessage: 'It can include: $\n' + + 'And the length must be: $', + trueValue: null, + falseValue: null, + caseSensitive: true + }, options, { + // -------- forced + history: false, + cd: false, + // limit (by charlist etc.), + phContent: function(param) { + return param === 'charlist' ? resCharlist.text : + param === 'length' ? min + '...' + max : null; + } + }), + // added: charlist, min, max, confirmMessage, unmatchMessage + charlist, confirmMessage, unmatchMessage, + limit, limitMessage, res1, res2; + /* eslint-enable key-spacing */ + options = options || {}; + + charlist = replacePlaceholder( + options.charlist ? options.charlist + '' : '$', getPhCharlist); + if (isNaN(min = parseInt(options.min, 10)) || typeof min !== 'number') { min = 12; } + if (isNaN(max = parseInt(options.max, 10)) || typeof max !== 'number') { max = 24; } + limit = new RegExp('^[' + escapePattern(charlist) + + ']{' + min + ',' + max + '}$'); + resCharlist = array2charlist([charlist], readOptions.caseSensitive, true); + resCharlist.text = joinChunks(resCharlist.values, resCharlist.suppressed); + + confirmMessage = options.confirmMessage != null ? options.confirmMessage : + 'Reinput a same one to confirm it: '; + unmatchMessage = options.unmatchMessage != null ? options.unmatchMessage : + 'It differs from first one.' + + ' Hit only the Enter key if you want to retry from first one.'; + + if (query == null) { query = 'Input new password: '; } + + limitMessage = readOptions.limitMessage; + while (!res2) { + readOptions.limit = limit; + readOptions.limitMessage = limitMessage; + res1 = exports.question(query, readOptions); + + readOptions.limit = [res1, '']; + readOptions.limitMessage = unmatchMessage; + res2 = exports.question(confirmMessage, readOptions); + } + + return res1; +}; + +function _questionNum(query, options, parser) { + var validValue; + function getValidValue(value) { + validValue = parser(value); + return !isNaN(validValue) && typeof validValue === 'number'; + } + /* eslint-disable key-spacing */ + exports.question(query, mergeOptions({ + // -------- default + limitMessage: 'Input valid number, please.' + }, options, { + // -------- forced + limit: getValidValue, + cd: false + // trueValue, falseValue, caseSensitive, keepWhitespace don't work. + })); + /* eslint-enable key-spacing */ + return validValue; +} +exports.questionInt = function(query, options) { + return _questionNum(query, options, function(value) { return parseInt(value, 10); }); +}; +exports.questionFloat = function(query, options) { + return _questionNum(query, options, parseFloat); +}; + +exports.questionPath = function(query, options) { + /* eslint-disable key-spacing */ + var error = '', + validPath, // before readOptions + readOptions = mergeOptions({ + // -------- default + hideEchoBack: false, + limitMessage: '$Input valid path, please.' + + '$<( Min:)min>$<( Max:)max>', + history: true, + cd: true + }, options, { + // -------- forced + keepWhitespace: false, + limit: function(value) { + var exists, stat, res; + value = replaceHomePath(value, true); + error = ''; // for validate + // mkdir -p + function mkdirParents(dirPath) { + dirPath.split(/\/|\\/).reduce(function(parents, dir) { + var path = pathUtil.resolve((parents += dir + pathUtil.sep)); + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } else if (!fs.statSync(path).isDirectory()) { + throw new Error('Non directory already exists: ' + path); + } + return parents; + }, ''); + } + + try { + exists = fs.existsSync(value); + validPath = exists ? fs.realpathSync(value) : pathUtil.resolve(value); + // options.exists default: true, not-bool: no-check + if (!options.hasOwnProperty('exists') && !exists || + typeof options.exists === 'boolean' && options.exists !== exists) { + error = (exists ? 'Already exists' : 'No such file or directory') + + ': ' + validPath; + return false; + } + if (!exists && options.create) { + if (options.isDirectory) { + mkdirParents(validPath); + } else { + mkdirParents(pathUtil.dirname(validPath)); + fs.closeSync(fs.openSync(validPath, 'w')); // touch + } + validPath = fs.realpathSync(validPath); + } + if (exists && (options.min || options.max || + options.isFile || options.isDirectory)) { + stat = fs.statSync(validPath); + // type check first (directory has zero size) + if (options.isFile && !stat.isFile()) { + error = 'Not file: ' + validPath; + return false; + } else if (options.isDirectory && !stat.isDirectory()) { + error = 'Not directory: ' + validPath; + return false; + } else if (options.min && stat.size < +options.min || + options.max && stat.size > +options.max) { + error = 'Size ' + stat.size + ' is out of range: ' + validPath; + return false; + } + } + if (typeof options.validate === 'function' && + (res = options.validate(validPath)) !== true) { + if (typeof res === 'string') { error = res; } + return false; + } + } catch (e) { + error = e + ''; + return false; + } + return true; + }, + // trueValue, falseValue, caseSensitive don't work. + phContent: function(param) { + return param === 'error' ? error : + param !== 'min' && param !== 'max' ? null : + options.hasOwnProperty(param) ? options[param] + '' : ''; + } + }); + // added: exists, create, min, max, isFile, isDirectory, validate + /* eslint-enable key-spacing */ + options = options || {}; + + if (query == null) { query = 'Input path (you can "cd" and "pwd"): '; } + + exports.question(query, readOptions); + return validPath; +}; + +// props: preCheck, args, hRes, limit +function getClHandler(commandHandler, options) { + var clHandler = {}, + hIndex = {}; + if (typeof commandHandler === 'object') { + Object.keys(commandHandler).forEach(function(cmd) { + if (typeof commandHandler[cmd] === 'function') { + hIndex[options.caseSensitive ? cmd : cmd.toLowerCase()] = commandHandler[cmd]; + } + }); + clHandler.preCheck = function(res) { + var cmdKey; + clHandler.args = parseCl(res); + cmdKey = clHandler.args[0] || ''; + if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); } + clHandler.hRes = + cmdKey !== '_' && hIndex.hasOwnProperty(cmdKey) + ? hIndex[cmdKey].apply(res, clHandler.args.slice(1)) : + hIndex.hasOwnProperty('_') ? hIndex._.apply(res, clHandler.args) : null; + return {res: res, forceNext: false}; + }; + if (!hIndex.hasOwnProperty('_')) { + clHandler.limit = function() { // It's called after preCheck. + var cmdKey = clHandler.args[0] || ''; + if (!options.caseSensitive) { cmdKey = cmdKey.toLowerCase(); } + return hIndex.hasOwnProperty(cmdKey); + }; + } + } else { + clHandler.preCheck = function(res) { + clHandler.args = parseCl(res); + clHandler.hRes = typeof commandHandler === 'function' + ? commandHandler.apply(res, clHandler.args) : true; // true for break loop + return {res: res, forceNext: false}; + }; + } + return clHandler; +} + +exports.promptCL = function(commandHandler, options) { + /* eslint-disable key-spacing */ + var readOptions = mergeOptions({ + // -------- default + hideEchoBack: false, + limitMessage: 'Requested command is not available.', + caseSensitive: false, + history: true + }, options), + // -------- forced + // trueValue, falseValue, keepWhitespace don't work. + // preCheck, limit (by clHandler) + clHandler = getClHandler(commandHandler, readOptions); + /* eslint-enable key-spacing */ + readOptions.limit = clHandler.limit; + readOptions.preCheck = clHandler.preCheck; + exports.prompt(readOptions); + return clHandler.args; +}; + +exports.promptLoop = function(inputHandler, options) { + /* eslint-disable key-spacing */ + var readOptions = mergeOptions({ + // -------- default + hideEchoBack: false, + trueValue: null, + falseValue: null, + caseSensitive: false, + history: true + }, options); + /* eslint-enable key-spacing */ + while (true) { if (inputHandler(exports.prompt(readOptions))) { break; } } + // return; // nothing is returned +}; + +exports.promptCLLoop = function(commandHandler, options) { + /* eslint-disable key-spacing */ + var readOptions = mergeOptions({ + // -------- default + hideEchoBack: false, + limitMessage: 'Requested command is not available.', + caseSensitive: false, + history: true + }, options), + // -------- forced + // trueValue, falseValue, keepWhitespace don't work. + // preCheck, limit (by clHandler) + clHandler = getClHandler(commandHandler, readOptions); + /* eslint-enable key-spacing */ + readOptions.limit = clHandler.limit; + readOptions.preCheck = clHandler.preCheck; + while (true) { + exports.prompt(readOptions); + if (clHandler.hRes) { break; } + } + // return; // nothing is returned +}; + +exports.promptSimShell = function(options) { + /* eslint-disable key-spacing */ + return exports.prompt(mergeOptions({ + // -------- default + hideEchoBack: false, + history: true + }, options, { + // -------- forced + prompt: (function() { + return IS_WIN ? '$>' : + // 'user@host:cwd$ ' + (process.env.USER || '') + + (process.env.HOSTNAME ? '@' + process.env.HOSTNAME.replace(/\..*$/, '') : '') + + ':$$ '; + })() + })); + /* eslint-enable key-spacing */ +}; + +function _keyInYN(query, options, limit) { + var res; + if (query == null) { query = 'Are you sure? '; } + if ((!options || options.guide !== false) && (query += '')) { + query = query.replace(/\s*:?\s*$/, '') + ' [y/n]: '; + } + /* eslint-disable key-spacing */ + res = exports.keyIn(query, mergeOptions(options, { + // -------- forced + hideEchoBack: false, + limit: limit, + trueValue: 'y', + falseValue: 'n', + caseSensitive: false + // mask doesn't work. + })); + // added: guide + /* eslint-enable key-spacing */ + return typeof res === 'boolean' ? res : ''; +} +exports.keyInYN = function(query, options) { return _keyInYN(query, options); }; +exports.keyInYNStrict = function(query, options) { return _keyInYN(query, options, 'yn'); }; + +exports.keyInPause = function(query, options) { + if (query == null) { query = 'Continue...'; } + if ((!options || options.guide !== false) && (query += '')) { + query = query.replace(/\s+$/, '') + ' (Hit any key)'; + } + /* eslint-disable key-spacing */ + exports.keyIn(query, mergeOptions({ + // -------- default + limit: null + }, options, { + // -------- forced + hideEchoBack: true, + mask: '' + })); + // added: guide + /* eslint-enable key-spacing */ + // return; // nothing is returned +}; + +exports.keyInSelect = function(items, query, options) { + /* eslint-disable key-spacing */ + var readOptions = mergeOptions({ + // -------- default + hideEchoBack: false + }, options, { + // -------- forced + trueValue: null, + falseValue: null, + caseSensitive: false, + // limit (by items), + phContent: function(param) { + return param === 'itemsCount' ? items.length + '' : + param === 'firstItem' ? (items[0] + '').trim() : + param === 'lastItem' ? (items[items.length - 1] + '').trim() : null; + } + }), + // added: guide, cancel + keylist = '', + key2i = {}, + charCode = 49 /* '1' */, + display = '\n'; + /* eslint-enable key-spacing */ + if (!Array.isArray(items) || !items.length || items.length > 35) { + throw '`items` must be Array (max length: 35).'; + } + + items.forEach(function(item, i) { + var key = String.fromCharCode(charCode); + keylist += key; + key2i[key] = i; + display += '[' + key + '] ' + (item + '').trim() + '\n'; + charCode = charCode === 57 /* '9' */ ? 97 /* 'a' */ : charCode + 1; + }); + if (!options || options.cancel !== false) { + keylist += '0'; + key2i['0'] = -1; + display += '[0] ' + + (options && options.cancel != null && typeof options.cancel !== 'boolean' + ? (options.cancel + '').trim() : 'CANCEL') + '\n'; + } + readOptions.limit = keylist; + display += '\n'; + + if (query == null) { query = 'Choose one from list: '; } + if ((query += '')) { + if (!options || options.guide !== false) { + query = query.replace(/\s*:?\s*$/, '') + ' [$]: '; + } + display += query; + } + + return key2i[exports.keyIn(display, readOptions).toLowerCase()]; +}; + +exports.getRawInput = function() { return rawInput; }; + +// ======== DEPRECATED ======== +function _setOption(optionName, args) { + var options; + if (args.length) { options = {}; options[optionName] = args[0]; } + return exports.setDefaultOptions(options)[optionName]; +} +exports.setPrint = function() { return _setOption('print', arguments); }; +exports.setPrompt = function() { return _setOption('prompt', arguments); }; +exports.setEncoding = function() { return _setOption('encoding', arguments); }; +exports.setMask = function() { return _setOption('mask', arguments); }; +exports.setBufferSize = function() { return _setOption('bufferSize', arguments); }; diff --git a/rootfs overrides/usr/lib/sheebr/utils.js b/rootfs overrides/usr/lib/sheebr/utils.js new file mode 100644 index 0000000..da82cb8 --- /dev/null +++ b/rootfs overrides/usr/lib/sheebr/utils.js @@ -0,0 +1,31 @@ +async function getline(str) { + process.stdout.write(str) + process.stdin.resume(); + process.stdin.setEncoding('utf8') + let line = "" + return new Promise((res, rej) => { + process.stdin.on('data', function(chunk) { + + line += chunk + if (chunk.toString('utf-8').endsWith('\n')) { + process.stdin.pause() + res(line.substring(0, line.length -1)) + } + }) + process.stdin.on('error', function(err) { + rej(err) + }) + }) +} + +function isAlpha(str){ + return /^[a-zA-Z]*$/.test(str); +} + + + + +module.exports = { + isAlpha, getline + +} \ No newline at end of file