From 747ab029372cdfcd7dc8bfcd5c80bb7ef5cb207c Mon Sep 17 00:00:00 2001 From: akida31 Date: Wed, 19 May 2021 11:23:43 +0200 Subject: [PATCH 1/2] improved stdout for commands and added simple shell variables --- .../windows/terminal/terminal-states.ts | 1131 +++++++++++------ 1 file changed, 737 insertions(+), 394 deletions(-) diff --git a/src/app/desktop/windows/terminal/terminal-states.ts b/src/app/desktop/windows/terminal/terminal-states.ts index 51e24cad..fd6c11fc 100644 --- a/src/app/desktop/windows/terminal/terminal-states.ts +++ b/src/app/desktop/windows/terminal/terminal-states.ts @@ -1,18 +1,18 @@ -import { TerminalAPI, TerminalState } from './terminal-api'; -import { WebsocketService } from '../../../websocket.service'; -import { catchError, map } from 'rxjs/operators'; -import { DomSanitizer } from '@angular/platform-browser'; -import { SecurityContext } from '@angular/core'; -import { SettingsService } from '../settings/settings.service'; -import { FileService } from '../../../api/files/file.service'; -import { Path } from '../../../api/files/path'; -import { of } from 'rxjs'; -import { Device } from '../../../api/devices/device'; -import { WindowDelegate } from '../../window/window-delegate'; -import { File } from '../../../api/files/file'; - - -function escapeHtml(html) { +import {TerminalAPI, TerminalState} from './terminal-api'; +import {WebsocketService} from '../../../websocket.service'; +import {catchError, map} from 'rxjs/operators'; +import {DomSanitizer} from '@angular/platform-browser'; +import {SecurityContext} from '@angular/core'; +import {SettingsService} from '../settings/settings.service'; +import {FileService} from '../../../api/files/file.service'; +import {Path} from '../../../api/files/path'; +import {of} from 'rxjs'; +import {Device} from '../../../api/devices/device'; +import {WindowDelegate} from '../../window/window-delegate'; +import {File} from '../../../api/files/file'; + + +function escapeHtml(html: string): string { return html .replace(/&/g, '&') .replace(/ void, description: string, hidden?: boolean } }; + abstract commands: {[name: string]: {executor: (io: IOHandler) => void, description: string, hidden?: boolean}}; + protected abstract terminal: TerminalAPI; protocol: string[] = []; + variables: Map = new Map(); executeCommand(command: string, args: string[]) { - command = command.toLowerCase(); + command = command.toLowerCase(); const iohandler: IOHandler = {stdout: this.stdoutHandler.bind(this), stdin: this.stdinHandler.bind(this), stderr: this.stderrHandler.bind(this), args: args}; + // reset the exit code + this.setExitCode(0); if (this.commands.hasOwnProperty(command)) { - this.commands[command].executor(args); + this.commands[command].executor(iohandler); } else if (command !== '') { - this.commandNotFound(command); + this.commandNotFound(command, iohandler); } } - execute(command: string) { - const command_ = command.trim().split(' '); - if (command_.length === 0) { - return; + reportError(error) { + console.warn(new Error(error.message)); + this.setExitCode(1); + } + + + stdinHandler(_: Stdin) {} + + /** default implementaion for stderr: printing to console**/ + stderrHandler(stderr: Stderr) { + switch (stderr.outputType) { + case OutputType.HTML: + this.terminal.output(stderr.data); + break; + case OutputType.RAW: + this.terminal.outputRaw(stderr.data); + break; + case OutputType.TEXT: + this.terminal.outputText(stderr.data); + break; + case OutputType.NODE: + this.terminal.outputNode(stderr.dataNode); + break; } - this.executeCommand(command_[0], command_.slice(1)); - if (command) { - this.protocol.unshift(command); + } + + /** default implementaion for stdout: printing to console**/ + stdoutHandler(stdout: Stdout) { + switch (stdout.outputType) { + case OutputType.HTML: + this.terminal.output(stdout.data); + break; + case OutputType.RAW: + this.terminal.outputRaw(stdout.data); + break; + case OutputType.TEXT: + this.terminal.outputText(stdout.data); + break; + case OutputType.NODE: + this.terminal.outputNode(stdout.dataNode); + break; } } - abstract commandNotFound(command: string); + setExitCode(exitCode: number) { + this.variables.set('?', String(exitCode)); + } + + execute(command: string) { + let commands = command.trim().split(';'); + commands = [].concat(...commands.map((command_) => command_.split("\n"))); + commands.forEach((command__) => { + let command_ = command__.trim().split(' '); + if (command_.length !== 0) { + // replace variables with their values + command_ = command_.map((arg) => { + if (arg.startsWith('$')) { + const name = arg.slice(1); + if (this.variables.has(name)) { + return this.variables.get(name); + } + return ''; + } + return arg; + }) + + this.executeCommand(command_[0], command_.slice(1)); + if (command) { + this.protocol.unshift(command); + } + } + }); + } + + abstract commandNotFound(command: string, iohandler: IOHandler): void; autocomplete(content: string): string { return content @@ -66,7 +130,7 @@ export abstract class CommandTerminalState implements TerminalState { return this.protocol; } - abstract refreshPrompt(); + abstract refreshPrompt(): void; } @@ -178,10 +242,22 @@ export class DefaultTerminalState extends CommandTerminalState { executor: this.info.bind(this), description: 'shows info of the current device' }, + 'run': { + executor: this.run.bind(this), + description: 'run an executable file' + }, + 'set': { + executor: this.setVariable.bind(this), + description: 'set the value of a variable' + }, + 'echo': { + executor: this.echo.bind(this), + description: 'display a line of text' + }, // easter egg 'chaozz': { - executor: () => this.terminal.outputText('"mess with the best, die like the rest :D`" - chaozz'), + executor: (iohandler: IOHandler) => iohandler.stdout(Stdout.text('"mess with the best, die like the rest :D`" - chaozz')), description: '', hidden: true } @@ -191,8 +267,8 @@ export class DefaultTerminalState extends CommandTerminalState { working_dir: string = Path.ROOT; // UUID of the working directory constructor(protected websocket: WebsocketService, private settings: SettingsService, private fileService: FileService, - private domSanitizer: DomSanitizer, protected windowDelegate: WindowDelegate, protected activeDevice: Device, - protected terminal: TerminalAPI, public promptColor: string = null) { + private domSanitizer: DomSanitizer, protected windowDelegate: WindowDelegate, protected activeDevice: Device, + protected terminal: TerminalAPI, public promptColor: string = null) { super(); } @@ -227,8 +303,9 @@ export class DefaultTerminalState extends CommandTerminalState { } } - commandNotFound(command: string) { - this.terminal.output('Command could not be found.
Type `help` for a list of commands.'); + commandNotFound(_: string, iohandler: IOHandler) { + iohandler.stderr(Stderr.html('Command could not be found.
Type `help` for a list of commands.')); + this.setExitCode(1); } refreshPrompt() { @@ -246,130 +323,138 @@ export class DefaultTerminalState extends CommandTerminalState { } - help() { + help(iohandler: IOHandler) { const table = document.createElement('table'); Object.entries(this.commands) .filter(command => !('hidden' in command[1])) - .map(([name, value]) => ({ name: name, description: value.description })) + .map(([name, value]) => ({name: name, description: value.description})) .map(command => `${command.name}${command.description}`) .forEach(row => { table.innerHTML += row; }); - this.terminal.outputNode(table); + iohandler.stdout(Stdout.node(table)); + this.setExitCode(0); } - miner(args: string[]) { + miner(iohandler: IOHandler) { let miner; let wallet; let power; let text; + const args = iohandler.args; if (args.length === 0) { - this.terminal.outputText('usage: miner look|wallet|power|start'); + iohandler.stderr(Stderr.text('usage: miner look|wallet|power|start')); + this.setExitCode(1); return; } - if (args[0] === 'look') { - this.websocket.ms('service', ['list'], { - 'device_uuid': this.activeDevice['uuid'], - }).subscribe((listData) => { - listData.services.forEach((service) => { - if (service.name === 'miner') { - miner = service; - this.websocket.ms('service', ['miner', 'get'], { - 'service_uuid': miner.uuid, - }).subscribe(data => { - wallet = data['wallet']; - power = Math.round(data['power'] * 100); - text = - 'Wallet: ' + wallet + '
' + - 'Mining Speed: ' + String(Number(miner.speed) * 60 * 60) + ' MC/h
' + - 'Power: ' + power + '%'; - this.terminal.output(text); - }); - } + switch (args[0]) { + case 'look': + this.websocket.ms('service', ['list'], { + 'device_uuid': this.activeDevice['uuid'], + }).subscribe((listData) => { + listData.services.forEach((service) => { + if (service.name === 'miner') { + miner = service; + this.websocket.ms('service', ['miner', 'get'], { + 'service_uuid': miner.uuid, + }).subscribe(data => { + wallet = data['wallet']; + power = Math.round(data['power'] * 100); + text = + 'Wallet: ' + wallet + '
' + + 'Mining Speed: ' + String(Number(miner.speed) * 60 * 60) + ' MC/h
' + + 'Power: ' + power + '%'; + iohandler.stdout(Stdout.html(text)); + this.setExitCode(0); + }); + } + }); }); - }); - - } else if (args[0] === 'wallet') { - if (args.length !== 2) { - this.terminal.outputText('usage: miner wallet '); - return; - } - this.websocket.ms('service', ['list'], { - 'device_uuid': this.activeDevice['uuid'], - }).subscribe((listData) => { - listData.services.forEach((service) => { - if (service.name === 'miner') { - miner = service; - this.websocket.ms('service', ['miner', 'wallet'], { - 'service_uuid': miner.uuid, - 'wallet_uuid': args[1], - }).subscribe((walletData) => { - wallet = args[1]; - power = walletData.power; - this.terminal.outputText(`Set wallet to ${args[1]}`); - }, () => { - this.terminal.outputText('Wallet is invalid.'); - }); - } + break; + case 'wallet': + if (args.length !== 2) { + iohandler.stderr(Stderr.text('usage: miner wallet ')); + this.setExitCode(1); + return; + } + this.websocket.ms('service', ['list'], { + 'device_uuid': this.activeDevice['uuid'], + }).subscribe((listData) => { + listData.services.forEach((service) => { + if (service.name === 'miner') { + miner = service; + this.websocket.ms('service', ['miner', 'wallet'], { + 'service_uuid': miner.uuid, + 'wallet_uuid': args[1], + }).subscribe((walletData) => { + wallet = args[1]; + power = walletData.power; + iohandler.stdout(Stdout.text(`Set wallet to ${args[1]}`)); + this.setExitCode(0); + }, () => { + iohandler.stderr(Stderr.text('Wallet is invalid.')); + this.setExitCode(1); + }); + } + }); }); - }); - } else if (args[0] === 'power') { - if (args.length !== 2) { - this.terminal.outputText('usage: miner power <0-100>'); - return; - } - if (isNaN(Number(args[1]))) { - return this.terminal.outputText('usage: miner power <0-100>'); - } - if (0 > Number(args[1]) || Number(args[1]) > 100) { - return this.terminal.outputText('usage: miner power <0-100>'); - } - this.websocket.ms('service', ['list'], { - 'device_uuid': this.activeDevice['uuid'], - }).subscribe((listData) => { - listData.services.forEach((service) => { - if (service.name === 'miner') { - miner = service; - this.websocket.ms('service', ['miner', 'power'], { - 'service_uuid': miner.uuid, - 'power': Number(args[1]) / 100, - }).subscribe((data: { power: number }) => { - this.terminal.outputText('Set Power to ' + args[1] + '%'); - }); - } + break; + case 'power': + if (args.length !== 2 || isNaN(Number(args[1])) || 0 > Number(args[1]) || Number(args[1]) > 100) { + iohandler.stderr(Stderr.text('usage: miner power <0-100>')); + this.setExitCode(1); + } + this.websocket.ms('service', ['list'], { + 'device_uuid': this.activeDevice['uuid'], + }).subscribe((listData) => { + listData.services.forEach((service) => { + if (service.name === 'miner') { + miner = service; + this.websocket.ms('service', ['miner', 'power'], { + 'service_uuid': miner.uuid, + 'power': Number(args[1]) / 100, + }).subscribe((_: {power: number}) => { + iohandler.stdout(Stdout.text('Set Power to ' + args[1] + '%')); + this.setExitCode(0); + }); + } + }); }); - }); - } else if (args[0] === 'start') { - if (args.length !== 2) { - this.terminal.outputText('usage: miner start '); - return; - } - this.websocket.ms('service', ['create'], { - 'device_uuid': this.activeDevice['uuid'], - 'name': 'miner', - 'wallet_uuid': args[1], - }).subscribe((service) => { - miner = service; - }, () => { - this.terminal.outputText('Invalid wallet'); - return of(); - }); - } else { - this.terminal.outputText('usage: miner look|wallet|power|start'); - return; + break; + case 'start': + if (args.length !== 2) { + iohandler.stderr(Stderr.text('usage: miner start ')); + this.setExitCode(1); + return; + } + this.websocket.ms('service', ['create'], { + 'device_uuid': this.activeDevice['uuid'], + 'name': 'miner', + 'wallet_uuid': args[1], + }).subscribe((service) => { + miner = service; + }, () => { + iohandler.stderr(Stderr.text('Invalid wallet')); + this.setExitCode(1); + }); + break; + default: + iohandler.stderr(Stderr.text('usage: miner look|wallet|power|start')); + this.setExitCode(1); } - } - status() { + status(iohandler: IOHandler) { this.websocket.request({ action: 'info' }).subscribe(r => { - this.terminal.outputText('Online players: ' + r.online); + iohandler.stdout(Stdout.text('Online players: ' + r.online)); + this.setExitCode(0); }); } - hostname(args: string[]) { + hostname(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 1) { const hostname = args[0]; this.websocket.ms('device', ['device', 'change_name'], { @@ -383,28 +468,33 @@ export class DefaultTerminalState extends CommandTerminalState { Object.assign(this.windowDelegate.device, newDevice); } }, () => { - this.terminal.outputText('The hostname couldn\'t be changed'); + iohandler.stderr(Stderr.text('The hostname couldn\'t be changed')); + this.setExitCode(1); }); } else { - this.websocket.ms('device', ['device', 'info'], { device_uuid: this.activeDevice['uuid'] }).subscribe(device => { + this.websocket.ms('device', ['device', 'info'], {device_uuid: this.activeDevice['uuid']}).subscribe(device => { if (device['name'] !== this.activeDevice['name']) { this.activeDevice = device; this.refreshPrompt(); } - this.terminal.outputText(device['name']); + iohandler.stdout(Stdout.text(device['name'])); + this.setExitCode(0); }, () => { - this.terminal.outputText(this.activeDevice['name']); + iohandler.stdout(Stdout.text(this.activeDevice['name'])); + this.setExitCode(0); }); } } - cd(args: string[]) { + cd(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 1) { let path: Path; try { path = Path.fromString(args[0], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { @@ -412,23 +502,25 @@ export class DefaultTerminalState extends CommandTerminalState { this.working_dir = file.uuid; this.refreshPrompt(); } else { - this.terminal.outputText('That is not a directory'); + iohandler.stderr(Stderr.text('That is not a directory')); + this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That directory does not exist'); + iohandler.stderr(Stderr.text('That directory does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } } - list_files(files: File[]) { + list_files(files: File[], stdout: (_: Stdout) => void) { files.filter((file) => { return file.is_directory; }).sort().forEach(folder => { - this.terminal.output(`${(this.settings.getLSPrefix()) ? '[Folder] ' : ''}${folder.filename}`); + stdout(Stdout.html(`${(this.settings.getLSPrefix()) ? '[Folder] ' : ''}${folder.filename}`)); }); files.filter((file) => { @@ -438,52 +530,60 @@ export class DefaultTerminalState extends CommandTerminalState { }); } - ls(args: string[]) { + ls(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 0) { this.fileService.getFiles(this.activeDevice['uuid'], this.working_dir).subscribe(files => { - this.list_files(files); + this.list_files(files, iohandler.stdout); }); } else if (args.length === 1) { let path: Path; try { path = Path.fromString(args[0], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(target => { if (target.is_directory) { this.fileService.getFiles(this.activeDevice['uuid'], target.uuid).subscribe(files => - this.list_files(files) + this.list_files(files, iohandler.stdout) ); } else { - this.terminal.outputText('That is not a directory'); + iohandler.stderr(Stderr.text('That is not a directory')); + this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That directory does not exist'); + iohandler.stderr(Stderr.text('That directory does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else { - this.terminal.outputText('usage: ls [directory]'); + iohandler.stderr(Stderr.text('usage: ls [directory]')); + this.setExitCode(1); } } - touch(args: string[]) { + touch(iohandler: IOHandler) { + const args = iohandler.args; if (args.length >= 1) { const filename = args[0]; let content = ''; if (!filename.match(/^[a-zA-Z0-9.\-_]+$/)) { - this.terminal.outputText('That filename is not valid'); + iohandler.stderr(Stderr.text('That filename is not valid')); + this.setExitCode(1); return; } if (filename.length > 64) { - this.terminal.outputText('That filename is too long'); + iohandler.stderr(Stderr.text('That filename is too long')); + this.setExitCode(1); return; } @@ -494,52 +594,61 @@ export class DefaultTerminalState extends CommandTerminalState { this.fileService.createFile(this.activeDevice['uuid'], filename, content, this.working_dir).subscribe({ error: err => { if (err.message === 'file_already_exists') { - this.terminal.outputText('That file already exists'); + iohandler.stderr(Stderr.text('That file already exists')); + this.setExitCode(1); } else { - reportError(err); + this.reportError(err); } } }); } else { - this.terminal.outputText('usage: touch [content]'); + iohandler.stderr(Stderr.text('usage: touch [content]')); + this.setExitCode(1); } } - cat(args: string[]) { + cat(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 1) { let path: Path; try { path = Path.fromString(args[0], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { if (file.is_directory) { - this.terminal.outputText('That is not a file'); + iohandler.stderr(Stderr.text('That is not a file')); + this.setExitCode(1); } else { - this.terminal.outputText(file.content); + iohandler.stdout(Stdout.text(file.content)); } }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else { - this.terminal.outputText('usage: cat '); + iohandler.stderr(Stderr.text('usage: cat ')); + this.setExitCode(1); } } - rm(args: string[]) { + rm(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 1) { let path: Path; try { path = Path.fromString(args[0], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } @@ -555,22 +664,23 @@ export class DefaultTerminalState extends CommandTerminalState { const uuid = walletCred[0]; const key = walletCred[1]; if (uuid.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/) && key.match(/^[a-f0-9]{10}$/)) { - this.websocket.ms('currency', ['get'], { source_uuid: uuid, key: key }).subscribe(() => { + this.websocket.ms('currency', ['get'], {source_uuid: uuid, key: key}).subscribe(() => { this.terminal.pushState( new YesNoTerminalState( this.terminal, 'Are you sure you want to delete your wallet? [yes|no]', answer => { if (answer) { - this.websocket.ms('currency', ['delete'], { source_uuid: uuid, key: key }).subscribe(() => { + this.websocket.ms('currency', ['delete'], {source_uuid: uuid, key: key}).subscribe(() => { this.websocket.ms('device', ['file', 'delete'], { device_uuid: this.activeDevice['uuid'], file_uuid: file.uuid }); }, error => { - this.terminal.output('The wallet couldn\'t be deleted successfully. ' + - 'Please report this bug.'); - reportError(error); + iohandler.stderr(Stderr.text('The wallet couldn\'t be deleted successfully. ' + + 'Please report this bug.')); + this.setExitCode(1); + this.reportError(error); }); } } @@ -585,17 +695,20 @@ export class DefaultTerminalState extends CommandTerminalState { } }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else { - this.terminal.outputText('usage: rm '); + iohandler.stderr(Stderr.text('usage: rm ')); + this.setExitCode(1); } } - cp(args: string[]) { + cp(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 2) { let srcPath: Path; let destPath: Path; @@ -603,7 +716,8 @@ export class DefaultTerminalState extends CommandTerminalState { srcPath = Path.fromString(args[0], this.working_dir); destPath = Path.fromString(args[1], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } const deviceUUID = this.activeDevice['uuid']; @@ -612,28 +726,32 @@ export class DefaultTerminalState extends CommandTerminalState { this.fileService.copyFile(source, destPath).subscribe({ error: error => { if (error.message === 'file_already_exists') { - this.terminal.outputText('That file already exists'); + iohandler.stderr(Stderr.text('That file already exists')); } else if (error.message === 'cannot_copy_directory') { - this.terminal.outputText('Cannot copy directories'); + iohandler.stderr(Stderr.text('Cannot copy directories')); } else if (error.message === 'destination_not_found') { - this.terminal.outputText('The destination folder was not found'); + iohandler.stderr(Stderr.text('The destination folder was not found')); } else { - reportError(error); + this.reportError(error); } + this.setExitCode(1); } }); }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } }); } else { - this.terminal.outputText('usage: cp '); + iohandler.stderr(Stderr.text('usage: cp ')); + this.setExitCode(1); } } - mv(args: string[]) { + mv(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 2) { let srcPath: Path; let destPath: Path; @@ -641,60 +759,71 @@ export class DefaultTerminalState extends CommandTerminalState { srcPath = Path.fromString(args[0], this.working_dir); destPath = Path.fromString(args[1], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], srcPath).subscribe(source => { if (source.is_directory) { - this.terminal.outputText('You cannot move directories'); + iohandler.stderr(Stderr.text('You cannot move directories')); + this.setExitCode(1); return; } this.fileService.moveToPath(source, destPath).subscribe({ error: err => { if (err.message === 'destination_is_file') { - this.terminal.outputText('The destination must be a directory'); + iohandler.stderr(Stderr.text('The destination must be a directory')); + this.setExitCode(1); } else if (err.message === 'file_already_exists') { - this.terminal.outputText('A file with the specified name already exists in the destination directory'); + iohandler.stderr(Stderr.text('A file with the specified name already exists in the destination directory')); + this.setExitCode(1); } else if (err.message === 'file_not_found') { - this.terminal.outputText('The destination directory does not exist'); + iohandler.stderr(Stderr.text('The destination directory does not exist')); + this.setExitCode(1); } else { - reportError(err); + this.reportError(err); } } }); }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else { - this.terminal.outputText('usage: mv '); + iohandler.stderr(Stderr.text('usage: mv ')); + this.setExitCode(1); } } - rename(args: string[]) { + rename(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 2) { let filePath: Path; try { filePath = Path.fromString(args[0], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } const name = args[1]; if (!name.match(/^[a-zA-Z0-9.\-_]+$/)) { - this.terminal.outputText('That name is not valid'); + iohandler.stderr(Stderr.text('That name is not valid')); + this.setExitCode(1); return; } if (name.length > 64) { - this.terminal.outputText('That name is too long'); + iohandler.stderr(Stderr.text('That name is too long')); + this.setExitCode(1); return; } @@ -702,49 +831,57 @@ export class DefaultTerminalState extends CommandTerminalState { this.fileService.rename(file, name).subscribe({ error: err => { if (err.message === 'file_already_exists') { - this.terminal.outputText('A file with the specified name already exists'); + iohandler.stderr(Stderr.text('A file with the specified name already exists')); + this.setExitCode(1); } else { - reportError(err); + this.reportError(err); } } }); }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else { - this.terminal.outputText('usage: rename '); + iohandler.stderr(Stderr.text('usage: rename ')); + this.setExitCode(1); } } - mkdir(args: string[]) { + mkdir(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 1) { const dirname = args[0]; if (!dirname.match(/^[a-zA-Z0-9.\-_]+$/)) { - this.terminal.outputText('That directory name is not valid'); + iohandler.stderr(Stderr.text('That directory name is not valid')); + this.setExitCode(1); return; } if (dirname.length > 64) { - this.terminal.outputText('That directory name is too long'); + iohandler.stderr(Stderr.text('That directory name is too long')); + this.setExitCode(1); return; } this.fileService.createDirectory(this.activeDevice['uuid'], dirname, this.working_dir).subscribe({ error: err => { if (err.message === 'file_already_exists') { - this.terminal.outputText('A file with the specified name already exists'); + iohandler.stderr(Stderr.text('A file with the specified name already exists')); + this.setExitCode(1); } else { - reportError(err); + this.reportError(err); } } }); } else { - this.terminal.outputText('usage: mkdir '); + iohandler.stderr(Stderr.text('usage: mkdir ')); + this.setExitCode(1); } } @@ -766,19 +903,21 @@ export class DefaultTerminalState extends CommandTerminalState { }); } - morphcoin(args: string[]) { + morphcoin(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 2) { if (args[0] === 'reset') { - this.websocket.ms('currency', ['reset'], { source_uuid: args[1] }).subscribe( + this.websocket.ms('currency', ['reset'], {source_uuid: args[1]}).subscribe( () => { - this.terminal.outputText('Wallet has been deleted successfully.'); + iohandler.stdout(Stdout.text('Wallet has been deleted successfully.')); }, error => { if (error.message === 'permission_denied') { - this.terminal.outputText('Permission denied.'); + iohandler.stderr(Stderr.text('Permission denied.')); } else { - this.terminal.outputText('Wallet does not exist.'); + iohandler.stderr(Stderr.text('Wallet does not exist.')); } + this.setExitCode(1); } ); return; @@ -788,14 +927,16 @@ export class DefaultTerminalState extends CommandTerminalState { try { path = Path.fromString(args[1], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } if (args[0] === 'look') { this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { if (file.is_directory) { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); return; } @@ -804,97 +945,110 @@ export class DefaultTerminalState extends CommandTerminalState { const uuid = walletCred[0]; const key = walletCred[1]; if (uuid.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/) && key.match(/^[a-f0-9]{10}$/)) { - this.websocket.ms('currency', ['get'], { source_uuid: uuid, key: key }).subscribe(wallet => { - this.terminal.outputText(new Intl.NumberFormat().format(wallet.amount / 1000) + ' morphcoin'); + this.websocket.ms('currency', ['get'], {source_uuid: uuid, key: key}).subscribe(wallet => { + iohandler.stdout(Stdout.text(new Intl.NumberFormat().format(wallet.amount / 1000) + ' morphcoin')); }, () => { - this.terminal.outputText('That file is not connected with a wallet'); + iohandler.stderr(Stderr.text('That file is not connected with a wallet')); + this.setExitCode(1); }); } else { - this.terminal.outputText('That file is not a wallet file'); + iohandler.stderr(Stderr.text('That file is not a wallet file')); + this.setExitCode(1); } } else { - this.terminal.outputText('That file is not a wallet file'); + iohandler.stderr(Stderr.text('That file is not a wallet file')); + this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else if (args[0] === 'create') { (path.path.length > 1 ? this.fileService.getFromPath(this.activeDevice['uuid'], new Path(path.path.slice(0, -1), path.parentUUID)) - : of({ uuid: path.parentUUID }) + : of({uuid: path.parentUUID}) ).subscribe(dest => { this.fileService.getFromPath(this.activeDevice['uuid'], new Path(path.path.slice(-1), dest.uuid)).subscribe(() => { - this.terminal.outputText('That file already exists'); + iohandler.stderr(Stderr.text('That file already exists')); + this.setExitCode(1); }, error => { if (error.message === 'file_not_found') { if (path.path[path.path.length - 1].length < 65) { - this.websocket.ms('currency', ['create'], {}).subscribe(wallet => { - const credentials = wallet.source_uuid + ' ' + wallet.key; - - this.fileService.createFile(this.activeDevice['uuid'], path.path[path.path.length - 1], credentials, this.working_dir) - .subscribe({ - error: err => { - this.terminal.outputText('That file couldn\'t be created. Please note your wallet credentials ' + - 'and put them in a new file with \'touch\' or contact the support: \'' + credentials + '\''); - reportError(err); - } - }); - }, error1 => { - if (error1.message === 'already_own_a_wallet') { - this.terminal.outputText('You already own a wallet'); - } else { - this.terminal.outputText(error1.message); - reportError(error1); - } - }); + this.websocket.ms('currency', ['create'], {}).subscribe(wallet => { + const credentials = wallet.source_uuid + ' ' + wallet.key; + + this.fileService.createFile(this.activeDevice['uuid'], path.path[path.path.length - 1], credentials, this.working_dir) + .subscribe({ + error: err => { + iohandler.stderr(Stderr.text('That file touldn\'t be created. Please note your wallet credentials ' + + 'and put them in a new file with \'touch\' or contact the support: \'' + credentials + '\'')); + this.setExitCode(1); + this.reportError(err); + } + }); + }, error1 => { + if (error1.message === 'already_own_a_wallet') { + iohandler.stderr(Stderr.text('You already own a wallet')); + } else { + iohandler.stderr(Stderr.text(error1.message)); + this.reportError(error1); + } + this.setExitCode(1); + }); } else { - this.terminal.outputText('Filename too long. Only 64 chars allowed'); + iohandler.stderr(Stderr.text('Filename too long. Only 64 chars allowed')); + this.setExitCode(1); } } else { - reportError(error); + this.reportError(error); } }); }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That path does not exist'); + iohandler.stderr(Stderr.text('That path does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } } else if (args.length === 1 && args[0] === 'list') { this.websocket.ms('currency', ['list'], {}).subscribe(data => { if (data.wallets.length === 0) { - this.terminal.outputText('You don\'t own any wallet.'); + iohandler.stderr(Stderr.text('You don\'t own any wallet.')); + this.setExitCode(1); } else { - this.terminal.outputText('Your wallets:'); + iohandler.stdout(Stdout.text('Your wallets:')); const el = document.createElement('ul'); el.innerHTML = data.wallets .map(wallet => '
  • ' + DefaultTerminalState.promptAppender(wallet) + '
  • ') .join(('')); - this.terminal.outputNode(el); + iohandler.stdout(Stdout.node(el)); DefaultTerminalState.registerPromptAppenders(el); } }); } else { - this.terminal.outputText('usage: morphcoin look|create|list|reset [|]'); + iohandler.stderr(Stderr.text('usage: morphcoin look|create|list|reset [|]')); + this.setExitCode(1); } } - pay(args: string[]) { + pay(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 3 || args.length === 4) { let walletPath: Path; try { walletPath = Path.fromString(args[0], this.working_dir); } catch { - this.terminal.outputText('The specified path is not valid'); + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); return; } const receiver = args[1]; @@ -906,11 +1060,13 @@ export class DefaultTerminalState extends CommandTerminalState { } if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) { - this.terminal.output('amount is not a valid number'); + iohandler.stderr(Stderr.html('amount is not a valid number')); + this.setExitCode(1); } else { this.fileService.getFromPath(this.activeDevice['uuid'], walletPath).subscribe(walletFile => { if (walletFile.is_directory) { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); return; } @@ -919,7 +1075,7 @@ export class DefaultTerminalState extends CommandTerminalState { const uuid = walletCred[0]; const key = walletCred[1]; if (uuid.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/) && key.match(/^[a-f0-9]{10}$/)) { - this.websocket.ms('currency', ['get'], { source_uuid: uuid, key: key }).subscribe(() => { + this.websocket.ms('currency', ['get'], {source_uuid: uuid, key: key}).subscribe(() => { this.websocket.ms('currency', ['send'], { source_uuid: uuid, key: key, @@ -927,41 +1083,47 @@ export class DefaultTerminalState extends CommandTerminalState { destination_uuid: receiver, usage: usage }).subscribe(() => { - this.terminal.outputText('Successfully sent ' + amount + ' to ' + receiver); + iohandler.stdout(Stdout.text('Successfully sent ' + amount + ' to ' + receiver)); }, error => { - this.terminal.outputText(error.message); - reportError(error); + iohandler.stderr(Stderr.text(error.message)); + this.reportError(error); }); }, () => { - this.terminal.outputText('That file is not connected with a wallet'); + iohandler.stderr(Stderr.text('That file is not connected with a wallet')); + this.setExitCode(1); }); } else { - this.terminal.outputText('That file is not a wallet file'); + iohandler.stderr(Stderr.text('That file is not a wallet file')); + this.setExitCode(1); } } else { - this.terminal.outputText('That file is not a wallet file'); + iohandler.stderr(Stderr.text('That file is not a wallet file')); + this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - this.terminal.outputText('That file does not exist'); + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } } else { - this.terminal.outputText('usage: pay [usage]'); + iohandler.stderr(Stderr.text('usage: pay [usage]')); + this.setExitCode(1); } } - service(args: string[]) { + service(iohandler: IOHandler) { + const args = iohandler.args; const activeDevice = this.activeDevice['uuid']; const getServices = () => - this.websocket.ms('service', ['list'], { device_uuid: activeDevice }).pipe(map(data => { + this.websocket.ms('service', ['list'], {device_uuid: activeDevice}).pipe(map(data => { return data['services']; }), catchError(error => { - reportError(error); + this.reportError(error); return []; })); @@ -971,34 +1133,39 @@ export class DefaultTerminalState extends CommandTerminalState { if (args.length >= 1 && args[0].toLowerCase() === 'create') { if (args.length !== 2) { - this.terminal.outputText('usage: service create '); + iohandler.stderr(Stderr.text('usage: service create ')); + this.setExitCode(1); return; } const service = args[1]; const services = ['bruteforce', 'portscan', 'telnet', 'ssh']; if (!services.includes(service)) { - this.terminal.outputText('Unknown service. Available services: ' + services.join(', ')); + iohandler.stderr(Stderr.text('Unknown service. Available services: ' + services.join(', '))); + this.setExitCode(1); return; } - this.websocket.ms('service', ['create'], { name: service, device_uuid: activeDevice }).subscribe(() => { - this.terminal.outputText('Service was created'); + this.websocket.ms('service', ['create'], {name: service, device_uuid: activeDevice}).subscribe(() => { + iohandler.stdout(Stdout.text('Service was created')); }, error => { if (error === 'already_own_this_service') { - this.terminal.outputText('You already created this service'); + iohandler.stderr(Stderr.text('You already created this service')); + this.setExitCode(1); } else { - reportError(error); + this.reportError(error); } }); } else if (args.length >= 1 && args[0] === 'list') { if (args.length !== 1) { - this.terminal.outputText('usage: service list'); + iohandler.stderr(Stderr.text('usage: service list')); + this.setExitCode(1); return; } getServices().subscribe(services => { if (services.length === 0) { - this.terminal.outputText('There is no service on this device'); + iohandler.stderr(Stderr.text('There is no service on this device')); + this.setExitCode(1); } else { const dev = document.createElement('span'); dev.innerHTML = '\'' + this.activeDevice['name'] + '\' (' + DefaultTerminalState.promptAppender(this.activeDevice['uuid']) + '):'; @@ -1012,22 +1179,24 @@ export class DefaultTerminalState extends CommandTerminalState { ')') .join(('')); - this.terminal.outputNode(dev); - this.terminal.outputNode(el); + iohandler.stdout(Stdout.node(dev)); + iohandler.stdout(Stdout.node(el)); DefaultTerminalState.registerPromptAppenders(dev); DefaultTerminalState.registerPromptAppenders(el); } }); } else if (args.length >= 1 && args[0] === 'bruteforce') { if (args.length !== 3) { - this.terminal.outputText('usage: service bruteforce '); + iohandler.stderr(Stderr.text('usage: service bruteforce ')); + this.setExitCode(1); return; } const [targetDevice, targetService] = args.slice(1); getService('bruteforce').subscribe(bruteforceService => { if (bruteforceService == null || bruteforceService['uuid'] == null) { - this.terminal.outputText('You have to create a bruteforce service before you use it'); + iohandler.stderr(Stderr.text('You have to create a bruteforce service before you use it')); + this.setExitCode(1); return; } @@ -1036,7 +1205,7 @@ export class DefaultTerminalState extends CommandTerminalState { service_uuid: bruteforceService['uuid'], device_uuid: activeDevice, target_device: targetDevice, target_service: targetService }).subscribe(() => { - this.terminal.outputText('You started a bruteforce attack'); + iohandler.stdout(Stdout.text('You started a bruteforce attack')); this.terminal.pushState(new BruteforceTerminalState(this.terminal, this.domSanitizer, stop => { if (stop) { this.executeCommand('service', ['bruteforce', targetDevice, targetService]); @@ -1044,12 +1213,13 @@ export class DefaultTerminalState extends CommandTerminalState { })); }, error1 => { if (error1.message === 'could_not_start_service') { - this.terminal.outputText('There was an error while starting the bruteforce attack'); + iohandler.stderr(Stderr.text('There was an error while starting the bruteforce attack')); } else if (error1.message === 'invalid_input_data') { - this.terminal.outputText('The specified UUID is not valid'); + iohandler.stderr(Stderr.text('The specified UUID is not valid')); } else { - reportError(error1); + this.reportError(error1); } + this.setExitCode(1); }); }; @@ -1062,7 +1232,7 @@ export class DefaultTerminalState extends CommandTerminalState { div.innerHTML = 'The bruteforce service already attacks another device: ' + DefaultTerminalState.promptAppender(status['target_device']) + '. Stopping...'; - this.terminal.outputNode(div); + iohandler.stdout(Stdout.node(div)); DefaultTerminalState.registerPromptAppenders(div); } @@ -1070,37 +1240,41 @@ export class DefaultTerminalState extends CommandTerminalState { service_uuid: bruteforceService['uuid'], device_uuid: activeDevice }).subscribe(stopData => { if (stopData['access'] === true) { - this.terminal.outputText('Access granted - use `connect `'); + iohandler.stdout(Stdout.text('Access granted - use `connect `')); } else { - this.terminal.outputText('Access denied. The bruteforce attack was not successful'); + iohandler.stderr(Stderr.text('Access denied. The bruteforce attack was not successful')); + this.setExitCode(255); } if (differentServiceAttacked) { startAttack(); } }, (err) => { - if (err.message === 'service_not_running') { - this.terminal.outputText('Target service is unreachable.'); - } + if (err.message === 'service_not_running') { + iohandler.stderr(Stderr.text('Target service is unreachable.')); + this.setExitCode(255); + } }); }, error => { if (error.message === 'attack_not_running') { startAttack(); } else { - reportError(error); + this.reportError(error); } }); }); } else if (args.length >= 1 && args[0] === 'portscan') { if (args.length !== 2) { - this.terminal.outputText('usage: service portscan '); + iohandler.stderr(Stderr.text('usage: service portscan ')); + this.setExitCode(1); return; } const targetDevice = args[1]; getService('portscan').subscribe(portscanService => { if (portscanService == null || portscanService['uuid'] == null) { - this.terminal.outputText('You have to create a portscan service before you use it'); + iohandler.stderr(Stderr.text('You have to create a portscan service before you use it')); + this.setExitCode(1); return; } @@ -1110,11 +1284,12 @@ export class DefaultTerminalState extends CommandTerminalState { }).subscribe(data => { const runningServices = data['services']; if (runningServices == null || !(runningServices instanceof Array) || (runningServices as any[]).length === 0) { - this.terminal.outputText('That device doesn\'t have any running services'); + iohandler.stderr(Stderr.text('That device doesn\'t have any running services')); + this.setExitCode(1); return; } - this.terminal.outputText('Open ports on that device:'); + iohandler.stdout(Stdout.text('Open ports on that device:')); const list = document.createElement('ul'); list.innerHTML = '
      ' + @@ -1126,25 +1301,27 @@ export class DefaultTerminalState extends CommandTerminalState { .join('\n') + '
    '; - this.terminal.outputNode(list); + iohandler.stdout(Stdout.node(list)); DefaultTerminalState.registerPromptAppenders(list); }); }); } else { - this.terminal.outputText('usage: service create|list|bruteforce|portscan'); + iohandler.stderr(Stderr.text('usage: service create|list|bruteforce|portscan')); + this.setExitCode(1); } } - spot() { + spot(iohandler: IOHandler) { this.websocket.ms('device', ['device', 'spot'], {}).subscribe(random_device => { - this.websocket.ms('service', ['list'], { 'device_uuid': this.activeDevice['uuid'] }).subscribe(localServices => { + this.websocket.ms('service', ['list'], {'device_uuid': this.activeDevice['uuid']}).subscribe(localServices => { const portScanner = (localServices['services'] || []).filter(service => service.name === 'portscan')[0]; if (portScanner == null || portScanner['uuid'] == null) { - this.terminal.outputText('\'' + random_device['name'] + '\':'); - this.terminal.outputRaw('
      ' + + iohandler.stderr(Stderr.text('\'' + random_device['name'] + '\':')); + iohandler.stderr(Stderr.raw('
        ' + '
      • UUID: ' + random_device['uuid'] + '
      • ' + '
      • Services: portscan failed
      • ' + - '
      '); + '
    ')); + this.setExitCode(1); return; } @@ -1152,7 +1329,7 @@ export class DefaultTerminalState extends CommandTerminalState { 'device_uuid': this.activeDevice['uuid'], 'service_uuid': portScanner['uuid'], 'target_device': random_device['uuid'] }).subscribe(remoteServices => { - this.terminal.outputText('\'' + escapeHtml(random_device['name']) + '\':'); + iohandler.stdout(Stdout.text('\'' + escapeHtml(random_device['name']) + '\':')); const list = document.createElement('ul'); list.innerHTML = '
  • UUID: ' + DefaultTerminalState.promptAppender(random_device['uuid']) + '
  • ' + '
  • Services:
  • ' + @@ -1161,49 +1338,53 @@ export class DefaultTerminalState extends CommandTerminalState { .map(service => '
  • ' + escapeHtml(service['name']) + ' (' + DefaultTerminalState.promptAppender(service['uuid']) + ')
  • ') .join('\n') + ''; - this.terminal.outputNode(list); + iohandler.stdout(Stdout.node(list)); DefaultTerminalState.registerPromptAppenders(list); }, error => { - this.terminal.output('An error occurred'); - reportError(error); + iohandler.stderr(Stderr.html('An error occurred')); + this.reportError(error); return; }); }); }); } - connect(args: string[]) { + connect(iohandler: IOHandler) { + const args = iohandler.args; if (args.length !== 1) { - this.terminal.outputText('usage: connect '); + iohandler.stderr(Stderr.text('usage: connect ')); + this.setExitCode(1); return; } - this.websocket.ms('device', ['device', 'info'], { device_uuid: args[0] }).subscribe(infoData => { - this.websocket.ms('service', ['part_owner'], { device_uuid: args[0] }).subscribe(partOwnerData => { + this.websocket.ms('device', ['device', 'info'], {device_uuid: args[0]}).subscribe(infoData => { + this.websocket.ms('service', ['part_owner'], {device_uuid: args[0]}).subscribe(partOwnerData => { if (infoData['owner'] === this.websocket.account.uuid || partOwnerData['ok'] === true) { this.terminal.pushState(new DefaultTerminalState(this.websocket, this.settings, this.fileService, this.domSanitizer, this.windowDelegate, infoData, this.terminal, '#DD2C00')); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); } }, error => { - this.terminal.outputText(error.message); - reportError(error); + iohandler.stderr(Stderr.text(error.message)); + this.reportError(error); }); }, error => { - this.terminal.outputText(error.message); - reportError(error); + iohandler.stderr(Stderr.text(error.message)); + this.reportError(error); }); } - network(args: string[]) { + network(iohandler: IOHandler) { + const args = iohandler.args; if (args.length === 1) { if (args[0] === 'public') { this.websocket.ms('network', ['public'], {}).subscribe(publicData => { const networks = publicData['networks']; if (networks != null && networks.length !== 0) { - this.terminal.outputText('Found ' + networks.length + ' public networks: '); + iohandler.stdout(Stdout.text('Found ' + networks.length + ' public networks: ')); const element = document.createElement('div'); element.innerHTML = ''; @@ -1213,11 +1394,12 @@ export class DefaultTerminalState extends CommandTerminalState { ' ' + DefaultTerminalState.promptAppender(network['uuid']) + ''; }); - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); DefaultTerminalState.registerPromptAppenders(element); } else { - this.terminal.outputText('No public networks found'); + iohandler.stderr(Stderr.text('No public networks found')); + this.setExitCode(1); } }); @@ -1231,8 +1413,8 @@ export class DefaultTerminalState extends CommandTerminalState { const memberNetworks = memberData['networks']; if (memberNetworks != null && memberNetworks.length > 0) { - this.terminal.outputText('Found ' + memberNetworks.length + ' networks: '); - this.terminal.outputText(''); + iohandler.stdout(Stdout.text('Found ' + memberNetworks.length + ' networks: ')); + iohandler.stdout(Stdout.text('')); const element = document.createElement('div'); element.innerHTML = ''; @@ -1247,11 +1429,12 @@ export class DefaultTerminalState extends CommandTerminalState { } }); - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); DefaultTerminalState.registerPromptAppenders(element); } else { - this.terminal.outputText('This device is not part of a network'); + iohandler.stderr(Stderr.text('This device is not part of a network')); + this.setExitCode(1); } }); @@ -1265,15 +1448,16 @@ export class DefaultTerminalState extends CommandTerminalState { const invitations = invitationsData['invitations']; if (invitations.length === 0) { - this.terminal.outputText('No invitations found'); + iohandler.stderr(Stderr.text('No invitations found')); + this.setExitCode(1); } else { - this.terminal.outputText('Found ' + invitations.length + ' invitations: '); + iohandler.stdout(Stdout.text('Found ' + invitations.length + ' invitations: ')); const element = document.createElement('div'); element.innerHTML = ''; invitations.forEach(invitation => { - this.websocket.ms('network', ['get'], { 'uuid': invitation['network'] }).subscribe(network => { + this.websocket.ms('network', ['get'], {'uuid': invitation['network']}).subscribe(network => { element.innerHTML += '
    Invitation: ' + '' + DefaultTerminalState.promptAppender(invitation['uuid']) + '
    ' + 'Network: ' + escapeHtml(network['name']) + '
    ' + @@ -1282,14 +1466,15 @@ export class DefaultTerminalState extends CommandTerminalState { }); }); - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); } }, error => { if (error.message === 'no_permissions') { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); } else { - reportError(error); + this.reportError(error); } + this.setExitCode(1); }); return; @@ -1302,9 +1487,10 @@ export class DefaultTerminalState extends CommandTerminalState { }; this.websocket.ms('network', ['delete'], data).subscribe(() => { - this.terminal.outputText('Network deleted'); + iohandler.stdout(Stdout.text('Network deleted')); }, () => { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); }); return; @@ -1315,18 +1501,19 @@ export class DefaultTerminalState extends CommandTerminalState { }; this.websocket.ms('network', ['request'], data).subscribe(requestData => { - this.terminal.outputText('Request sent:'); - this.terminal.outputText(this.activeDevice['name'] + ' -> ' + requestData['network']); + iohandler.stdout(Stdout.text('Request sent:')); + iohandler.stdout(Stdout.text(this.activeDevice['name'] + ' -> ' + requestData['network'])); }, error => { if (error.message === 'network_not_found') { - this.terminal.outputText('Network not found: ' + args[1]); + iohandler.stderr(Stderr.text('Network not found: ' + args[1])); } else if (error.message === 'already_member_of_network') { - this.terminal.outputText('You are already a member of this network'); + iohandler.stderr(Stderr.text('You are already a member of this network')); } else if (error.message === 'invitation_already_exists') { - this.terminal.outputText('You already requested to enter this network'); + iohandler.stderr(Stderr.text('You already requested to enter this network')); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); } + this.setExitCode(1); }); return; @@ -1339,9 +1526,10 @@ export class DefaultTerminalState extends CommandTerminalState { const requests = requestsData['requests']; if (requests.length === 0) { - this.terminal.outputText('No requests found'); + iohandler.stderr(Stderr.text('No requests found')); + this.setExitCode(1); } else { - this.terminal.outputText('Found ' + requests.length + ' requests: '); + iohandler.stdout(Stdout.text('Found ' + requests.length + ' requests: ')); const element = document.createElement('div'); element.innerHTML = ''; @@ -1353,12 +1541,13 @@ export class DefaultTerminalState extends CommandTerminalState { DefaultTerminalState.promptAppender(request['device']) + '
    '; }); - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); DefaultTerminalState.registerPromptAppenders(element); } }, () => { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); }); return; @@ -1368,12 +1557,14 @@ export class DefaultTerminalState extends CommandTerminalState { }; this.websocket.ms('network', [args[0]], data).subscribe(() => { - this.terminal.outputText(args[1] + ' -> ' + args[0]); + iohandler.stdout(Stdout.text(args[1] + ' -> ' + args[0])); }, error => { if (error.message === 'invitation_not_found') { - this.terminal.outputText('Invitation not found'); + iohandler.stderr(Stderr.text('Invitation not found')); + this.setExitCode(1); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); } }); @@ -1385,12 +1576,14 @@ export class DefaultTerminalState extends CommandTerminalState { }; this.websocket.ms('network', ['leave'], data).subscribe(() => { - this.terminal.outputText('You left the network: ' + args[1]); + iohandler.stdout(Stdout.text('You left the network: ' + args[1])); }, error => { if (error.message === 'cannot_leave_own_network') { - this.terminal.outputText('You cannot leave your own network'); + iohandler.stderr(Stderr.text('You cannot leave your own network')); + this.setExitCode(1); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); } }); @@ -1406,11 +1599,12 @@ export class DefaultTerminalState extends CommandTerminalState { element.innerHTML += 'Hidden: ' + (getData['hidden'] ? 'private' : 'public') + '
    '; element.innerHTML += 'Owner: ' + DefaultTerminalState.promptAppender(getData['owner']) + ''; - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); DefaultTerminalState.registerPromptAppenders(element); }, () => { - this.terminal.outputText('Network not found: ' + args[1]); + iohandler.stderr(Stderr.text('Network not found: ' + args[1])); + this.setExitCode(1); }); return; @@ -1423,27 +1617,29 @@ export class DefaultTerminalState extends CommandTerminalState { const members = membersData['members']; if (members != null && members.length > 0) { - this.terminal.outputText('Found ' + members.length + ' members: '); - this.terminal.outputText(''); + iohandler.stdout(Stdout.text('Found ' + members.length + ' members: ')); + iohandler.stdout(Stdout.text('')); const element = document.createElement('div'); element.innerHTML = ''; members.forEach(member => { - this.websocket.ms('device', ['device', 'info'], { 'device_uuid': member['device'] }).subscribe(deviceData => { + this.websocket.ms('device', ['device', 'info'], {'device_uuid': member['device']}).subscribe(deviceData => { element.innerHTML += ' ' + DefaultTerminalState.promptAppender(member['device']) + ' ' + deviceData['name'] + '
    '; }); }); - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); DefaultTerminalState.registerPromptAppenders(element); } else { - this.terminal.outputText('This network has no members'); + iohandler.stderr(Stderr.text('This network has no members')); + this.setExitCode(1); } }, () => { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); }); return; @@ -1461,19 +1657,23 @@ export class DefaultTerminalState extends CommandTerminalState { }; this.websocket.ms('network', ['create'], data).subscribe(createData => { - this.terminal.outputText('Name: ' + createData['name']); - this.terminal.outputText('Visibility: ' + (createData['hidden'] ? 'private' : 'public')); + iohandler.stdout(Stdout.text('Name: ' + createData['name'])); + iohandler.stdout(Stdout.text('Visibility: ' + (createData['hidden'] ? 'private' : 'public'))); }, error => { if (error.message === 'invalid_name') { - this.terminal.outputText('Name is invalid: Use 5 - 20 characters'); + iohandler.stderr(Stderr.text('Name is invalid: Use 5 - 20 characters')); + this.setExitCode(1); } else if (error.message === 'name_already_in_use') { - this.terminal.outputText('Name already in use'); + iohandler.stderr(Stderr.text('Name already in use')); + this.setExitCode(1); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); } }); } else { - this.terminal.outputText('Please use public or private as mode'); + iohandler.stderr(Stderr.text('Please use public or private as mode')); + this.setExitCode(1); } return; @@ -1484,16 +1684,20 @@ export class DefaultTerminalState extends CommandTerminalState { }; this.websocket.ms('network', ['invite'], data).subscribe(() => { - this.terminal.outputText(args[2] + ' invited to ' + args[1]); + iohandler.stdout(Stdout.text(args[2] + ' invited to ' + args[1])); }, error => { if (error.message === 'network_not_found') { - this.terminal.outputText('Network not found: ' + args[1]); + iohandler.stderr(Stderr.text('Network not found: ' + args[1])); + this.setExitCode(1); } else if (error.message === 'already_member_of_network') { - this.terminal.outputText('This device is already a member of this network'); + iohandler.stderr(Stderr.text('This device is already a member of this network')); + this.setExitCode(1); } else if (error.message === 'invitation_already_exists') { - this.terminal.outputText('You already invited this device'); + iohandler.stderr(Stderr.text('You already invited this device')); + this.setExitCode(1); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); } }); @@ -1505,60 +1709,127 @@ export class DefaultTerminalState extends CommandTerminalState { }; if (data['device'] === this.activeDevice['uuid']) { - this.terminal.outputText('You cannot kick yourself'); + iohandler.stderr(Stderr.text('You cannot kick yourself')); + this.setExitCode(1); return; } this.websocket.ms('network', ['kick'], data).subscribe(kickData => { if (kickData['result']) { - this.terminal.outputText('Kicked successfully'); + iohandler.stdout(Stdout.text('Kicked successfully')); } else { - this.terminal.outputText('The device is not a member of the network'); + iohandler.stderr(Stderr.text('The device is not a member of the network')); + this.setExitCode(1); } }, error => { if (error.message === 'cannot_kick_owner') { - this.terminal.outputText('You cannot kick the owner of the network'); + iohandler.stderr(Stderr.text('You cannot kick the owner of the network')); + this.setExitCode(1); } else { - this.terminal.outputText('Access denied'); + iohandler.stderr(Stderr.text('Access denied')); + this.setExitCode(255); } }); return; } } - this.terminal.outputText('network list # show all networks of this device'); - this.terminal.outputText('network public # show all public networks'); - this.terminal.outputText('network invitations # show invitations of a this device'); - this.terminal.outputText('network info # show info of network'); - this.terminal.outputText('network get # show info of network'); - this.terminal.outputText('network members # show members of network'); - this.terminal.outputText('network leave # leave a network'); - this.terminal.outputText('network delete # delete a network'); - this.terminal.outputText('network request # create a join request to a network'); - this.terminal.outputText('network requests # show requests of a network'); - this.terminal.outputText('network accept # accept an invitation or request'); - this.terminal.outputText('network deny # accept an invitation or request'); - this.terminal.outputText('network invite # invite to network'); - this.terminal.outputText('network revoke # revoke an invitation'); - this.terminal.outputText('network kick # kick device out of network'); - this.terminal.outputText('network create # create a network'); + iohandler.stdout(Stdout.text('network list # show all networks of this device')); + iohandler.stdout(Stdout.text('network public # show all public networks')); + iohandler.stdout(Stdout.text('network invitations # show invitations of a this device')); + iohandler.stdout(Stdout.text('network info # show info of network')); + iohandler.stdout(Stdout.text('network get # show info of network')); + iohandler.stdout(Stdout.text('network members # show members of network')); + iohandler.stdout(Stdout.text('network leave # leave a network')); + iohandler.stdout(Stdout.text('network delete # delete a network')); + iohandler.stdout(Stdout.text('network request # create a join request to a network')); + iohandler.stdout(Stdout.text('network requests # show requests of a network')); + iohandler.stdout(Stdout.text('network accept # accept an invitation or request')); + iohandler.stdout(Stdout.text('network deny # accept an invitation or request')); + iohandler.stdout(Stdout.text('network invite # invite to network')); + iohandler.stdout(Stdout.text('network revoke # revoke an invitation')); + iohandler.stdout(Stdout.text('network kick # kick device out of network')); + iohandler.stdout(Stdout.text('network create # create a network')); } - info() { - this.terminal.outputText('Username: ' + this.websocket.account.name); - this.terminal.outputText('Host: ' + this.activeDevice['name']); + info(iohandler: IOHandler) { + iohandler.stdout(Stdout.text('Username: ' + this.websocket.account.name)); + iohandler.stdout(Stdout.text('Host: ' + this.activeDevice['name'])); const element = document.createElement('div'); element.innerHTML = `Address: ${DefaultTerminalState.promptAppender(this.activeDevice['uuid'])}`; - this.terminal.outputNode(element); + iohandler.stdout(Stdout.node(element)); DefaultTerminalState.registerPromptAppenders(element); } + + run(iohandler: IOHandler) { + const args = iohandler.args; + if (args.length === 0) { + iohandler.stderr(Stderr.text('usage: run ')); + this.setExitCode(1); + return; + } + let path: Path; + try { + path = Path.fromString(args[0], this.working_dir); + } catch { + iohandler.stderr(Stderr.text('The specified path is not valid')); + this.setExitCode(1); + return; + } + this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { + if (file.is_directory) { + iohandler.stderr(Stderr.text('That is not a file')); + this.setExitCode(1); + } else { + // set special variables + this.variables.set('#', String(args.length - 1)); + this.variables.set('0', args[0]); + let numberOfArgs: number; + for (numberOfArgs = 1; numberOfArgs < Math.min(args.length, 10); numberOfArgs++) { + this.variables.set(String(numberOfArgs), args[numberOfArgs]); + } + const allArgs = args.slice(1).join(' '); + this.variables.set('*', allArgs); + this.variables.set('@', allArgs); + this.execute(file.content); + // reset special variables + '#0*@'.split('').forEach((variable: string) => { + this.variables.delete(variable); + }) + for (let i = 0; i <= numberOfArgs; i++) { + this.variables.delete(String(i)); + } + } + }, error => { + if (error.message === 'file_not_found') { + iohandler.stderr(Stderr.text('That file does not exist')); + this.setExitCode(1); + } else { + this.reportError(error); + } + }); + } + + setVariable(iohandler: IOHandler) { + const args = iohandler.args; + if (args.length !== 2) { + iohandler.stderr(Stderr.text('usage: set ')); + this.setExitCode(1); + return; + } + this.variables.set(args[0], args[1]); + } + + echo(iohandler: IOHandler) { + iohandler.stdout(Stdout.text(iohandler.args.join(' '))); + } } export abstract class ChoiceTerminalState implements TerminalState { - choices: { [choice: string]: () => void }; + choices: {[choice: string]: () => void}; protected constructor(protected terminal: TerminalAPI) { } @@ -1632,9 +1903,9 @@ export class BruteforceTerminalState extends ChoiceTerminalState { }; constructor(terminal: TerminalAPI, - private domSanitizer: DomSanitizer, - private callback: (response: boolean) => void, - private startSeconds: number = 0) { + private domSanitizer: DomSanitizer, + private callback: (response: boolean) => void, + private startSeconds: number = 0) { super(terminal); this.intervalHandle = setInterval(() => { @@ -1650,3 +1921,75 @@ export class BruteforceTerminalState extends ChoiceTerminalState { this.terminal.changePrompt(`${prompt}`, true); } } + +class IOHandler { + stdout: (stdout: Stdout) => void; + stdin: (stdin: Stdin) => void; + stderr: (stderr: Stderr) => void; + args: string[]; +} + +class Stdin {} +class Stderr { + outputType: OutputType; + data: string; + dataNode: Node; + + constructor(outputType: OutputType, data: string) { + this.outputType = outputType; + this.data = data; + this.dataNode = null; + } + + static html(data: string): Stdout { + return {outputType: OutputType.HTML, data: data, dataNode: null}; + } + + static raw(data: string): Stdout { + return {outputType: OutputType.RAW, data: data, dataNode: null}; + } + + static text(data: string): Stdout { + return {outputType: OutputType.TEXT, data: data, dataNode: null}; + } + + static node(data: Node): Stdout { + return {outputType: OutputType.NODE, data: null, dataNode: data}; + } +} + + +class Stdout { + outputType: OutputType; + data: string; + dataNode: Node; + + constructor(outputType: OutputType, data: string) { + this.outputType = outputType; + this.data = data; + this.dataNode = null; + } + + static html(data: string): Stdout { + return {outputType: OutputType.HTML, data: data, dataNode: null}; + } + + static raw(data: string): Stdout { + return {outputType: OutputType.RAW, data: data, dataNode: null}; + } + + static text(data: string): Stdout { + return {outputType: OutputType.TEXT, data: data, dataNode: null}; + } + + static node(data: Node): Stdout { + return {outputType: OutputType.NODE, data: null, dataNode: data}; + } +} + +enum OutputType { + HTML, + RAW, + TEXT, + NODE, +} From 9ad7c58af3fc55a64a72572e5aec8b16f13a9a8b Mon Sep 17 00:00:00 2001 From: akida31 Date: Fri, 21 May 2021 12:25:39 +0200 Subject: [PATCH 2/2] added stdin and piping; fixed some errors --- .../windows/terminal/terminal-states.ts | 647 +++++++++++------- 1 file changed, 399 insertions(+), 248 deletions(-) diff --git a/src/app/desktop/windows/terminal/terminal-states.ts b/src/app/desktop/windows/terminal/terminal-states.ts index fd6c11fc..9794f96d 100644 --- a/src/app/desktop/windows/terminal/terminal-states.ts +++ b/src/app/desktop/windows/terminal/terminal-states.ts @@ -29,10 +29,17 @@ export abstract class CommandTerminalState implements TerminalState { protocol: string[] = []; variables: Map = new Map(); - executeCommand(command: string, args: string[]) { - command = command.toLowerCase(); const iohandler: IOHandler = {stdout: this.stdoutHandler.bind(this), stdin: this.stdinHandler.bind(this), stderr: this.stderrHandler.bind(this), args: args}; - // reset the exit code - this.setExitCode(0); + // if an iohandler is given, the list of args is discarded + executeCommand(command: string, args: string[], io: IOHandler = null) { + const iohandler = io ? io : { + stdout: this.stdoutHandler.bind(this), + stdin: this.stdinHandler, + stderr: this.stderrHandler.bind(this), + args: args + }; + command = command.toLowerCase(); + // command not completed + this.setExitCode(-1); if (this.commands.hasOwnProperty(command)) { this.commands[command].executor(iohandler); } else if (command !== '') { @@ -40,33 +47,102 @@ export abstract class CommandTerminalState implements TerminalState { } } + // wait until the command is completed => the exitCode is !== -1 + waitForCompletion() { + const poll = (resolve: () => void) => { + if (this.getExitCode() !== -1) { + resolve(); + } else { + setTimeout(_ => poll(resolve), 10); + } + }; + return new Promise(poll); + } + + + executeCommandChain(commands: string[], previousStdout: string = null) { + let stdoutText = ''; + + const pipedStdout = (output: Stdout) => { + switch (output.outputType) { + case OutputType.NODE: + stdoutText = stdoutText + output.dataNode.toString() + '\n'; + break; + case OutputType.RAW: + stdoutText = stdoutText + output.data; + break; + case OutputType.HTML: + stdoutText = stdoutText + output.data + '\n'; + break; + case OutputType.TEXT: + stdoutText = stdoutText + output.data + '\n'; + break; + } + }; + + const pipedStdin = (callback: (input: string) => void) => { + callback(previousStdout); + }; + + let command = commands[0].trim().split(' '); + if (command.length === 0) { + this.executeCommandChain(commands.slice(1)); + return; + } + // replace variables with their values + command = command.map((arg) => { + if (arg.startsWith('$')) { + const name = arg.slice(1); + if (this.variables.has(name)) { + return this.variables.get(name); + } + return ''; + } + return arg; + }); + + const stdout = commands.length > 1 ? pipedStdout : this.stdoutHandler.bind(this); + const stdin = previousStdout ? pipedStdin : this.stdinHandler.bind(this); + const iohandler: IOHandler = {stdout: stdout, stdin: stdin, stderr: this.stderrHandler.bind(this), args: command.slice(1)}; + // args are in inclued in the iohandler, we don't have to give them twice + this.executeCommand(command[0], [], iohandler); + this.waitForCompletion().then(() => { + if (commands.length > 1) { + this.executeCommandChain(commands.slice(1), stdoutText); + } + }); + } + + execute(cmd: string) { + let commands = cmd.trim().split(';'); + commands = [].concat(...commands.map((command) => command.split('\n'))); + commands.forEach((command) => { + const pipedCommands = command.trim().split('|'); + this.executeCommandChain(pipedCommands); + }); + if (cmd) { + this.protocol.unshift(cmd); + } + + } + reportError(error) { console.warn(new Error(error.message)); this.setExitCode(1); } + /** default implementaion for stdin: reading from console */ + stdinHandler(callback: (input: string) => void) { + return new DefaultStdin(this.terminal).read(callback); + } - stdinHandler(_: Stdin) {} - /** default implementaion for stderr: printing to console**/ - stderrHandler(stderr: Stderr) { - switch (stderr.outputType) { - case OutputType.HTML: - this.terminal.output(stderr.data); - break; - case OutputType.RAW: - this.terminal.outputRaw(stderr.data); - break; - case OutputType.TEXT: - this.terminal.outputText(stderr.data); - break; - case OutputType.NODE: - this.terminal.outputNode(stderr.dataNode); - break; - } + /** default implementaion for stderr: printing to console */ + stderrHandler(stderr: string) { + this.terminal.output(stderr); } - /** default implementaion for stdout: printing to console**/ + /** default implementaion for stdout: printing to console */ stdoutHandler(stdout: Stdout) { switch (stdout.outputType) { case OutputType.HTML: @@ -88,30 +164,8 @@ export abstract class CommandTerminalState implements TerminalState { this.variables.set('?', String(exitCode)); } - execute(command: string) { - let commands = command.trim().split(';'); - commands = [].concat(...commands.map((command_) => command_.split("\n"))); - commands.forEach((command__) => { - let command_ = command__.trim().split(' '); - if (command_.length !== 0) { - // replace variables with their values - command_ = command_.map((arg) => { - if (arg.startsWith('$')) { - const name = arg.slice(1); - if (this.variables.has(name)) { - return this.variables.get(name); - } - return ''; - } - return arg; - }) - - this.executeCommand(command_[0], command_.slice(1)); - if (command) { - this.protocol.unshift(command); - } - } - }); + getExitCode(): number { + return Number(this.variables.get('?')); } abstract commandNotFound(command: string, iohandler: IOHandler): void; @@ -254,6 +308,10 @@ export class DefaultTerminalState extends CommandTerminalState { executor: this.echo.bind(this), description: 'display a line of text' }, + 'read': { + executor: this.read.bind(this), + description: 'read input of user' + }, // easter egg 'chaozz': { @@ -267,8 +325,8 @@ export class DefaultTerminalState extends CommandTerminalState { working_dir: string = Path.ROOT; // UUID of the working directory constructor(protected websocket: WebsocketService, private settings: SettingsService, private fileService: FileService, - private domSanitizer: DomSanitizer, protected windowDelegate: WindowDelegate, protected activeDevice: Device, - protected terminal: TerminalAPI, public promptColor: string = null) { + private domSanitizer: DomSanitizer, protected windowDelegate: WindowDelegate, protected activeDevice: Device, + protected terminal: TerminalAPI, public promptColor: string = null) { super(); } @@ -304,8 +362,8 @@ export class DefaultTerminalState extends CommandTerminalState { } commandNotFound(_: string, iohandler: IOHandler) { - iohandler.stderr(Stderr.html('Command could not be found.
    Type `help` for a list of commands.')); - this.setExitCode(1); + iohandler.stderr('Command could not be found.\nType `help` for a list of commands.'); + this.setExitCode(127); } refreshPrompt() { @@ -343,7 +401,7 @@ export class DefaultTerminalState extends CommandTerminalState { let text; const args = iohandler.args; if (args.length === 0) { - iohandler.stderr(Stderr.text('usage: miner look|wallet|power|start')); + iohandler.stderr('usage: miner look|wallet|power|start'); this.setExitCode(1); return; } @@ -373,7 +431,7 @@ export class DefaultTerminalState extends CommandTerminalState { break; case 'wallet': if (args.length !== 2) { - iohandler.stderr(Stderr.text('usage: miner wallet ')); + iohandler.stderr('usage: miner wallet '); this.setExitCode(1); return; } @@ -392,7 +450,7 @@ export class DefaultTerminalState extends CommandTerminalState { iohandler.stdout(Stdout.text(`Set wallet to ${args[1]}`)); this.setExitCode(0); }, () => { - iohandler.stderr(Stderr.text('Wallet is invalid.')); + iohandler.stderr('Wallet is invalid.'); this.setExitCode(1); }); } @@ -401,7 +459,7 @@ export class DefaultTerminalState extends CommandTerminalState { break; case 'power': if (args.length !== 2 || isNaN(Number(args[1])) || 0 > Number(args[1]) || Number(args[1]) > 100) { - iohandler.stderr(Stderr.text('usage: miner power <0-100>')); + iohandler.stderr('usage: miner power <0-100>'); this.setExitCode(1); } this.websocket.ms('service', ['list'], { @@ -423,7 +481,7 @@ export class DefaultTerminalState extends CommandTerminalState { break; case 'start': if (args.length !== 2) { - iohandler.stderr(Stderr.text('usage: miner start ')); + iohandler.stderr('usage: miner start '); this.setExitCode(1); return; } @@ -433,13 +491,14 @@ export class DefaultTerminalState extends CommandTerminalState { 'wallet_uuid': args[1], }).subscribe((service) => { miner = service; + this.setExitCode(0); }, () => { - iohandler.stderr(Stderr.text('Invalid wallet')); + iohandler.stderr('Invalid wallet'); this.setExitCode(1); }); break; default: - iohandler.stderr(Stderr.text('usage: miner look|wallet|power|start')); + iohandler.stderr('usage: miner look|wallet|power|start'); this.setExitCode(1); } } @@ -467,8 +526,9 @@ export class DefaultTerminalState extends CommandTerminalState { if (this.activeDevice.uuid === this.windowDelegate.device.uuid) { Object.assign(this.windowDelegate.device, newDevice); } + this.setExitCode(0); }, () => { - iohandler.stderr(Stderr.text('The hostname couldn\'t be changed')); + iohandler.stderr('The hostname couldn\'t be changed'); this.setExitCode(1); }); } else { @@ -493,7 +553,7 @@ export class DefaultTerminalState extends CommandTerminalState { try { path = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } @@ -501,13 +561,14 @@ export class DefaultTerminalState extends CommandTerminalState { if (file.is_directory) { this.working_dir = file.uuid; this.refreshPrompt(); + this.setExitCode(0); } else { - iohandler.stderr(Stderr.text('That is not a directory')); + iohandler.stderr('That is not a directory'); this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That directory does not exist')); + iohandler.stderr('That directory does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -516,55 +577,55 @@ export class DefaultTerminalState extends CommandTerminalState { } } - list_files(files: File[], stdout: (_: Stdout) => void) { + list_files(files: File[], iohandler: IOHandler) { files.filter((file) => { return file.is_directory; }).sort().forEach(folder => { - stdout(Stdout.html(`${(this.settings.getLSPrefix()) ? '[Folder] ' : ''}${folder.filename}`)); + iohandler.stdout(Stdout.html(`${(this.settings.getLSPrefix()) ? '[Folder] ' : ''}${folder.filename}`)); }); files.filter((file) => { return !file.is_directory; }).sort().forEach(file => { - this.terminal.outputText(`${(this.settings.getLSPrefix() ? '[File] ' : '')}${file.filename}`); + iohandler.stdout(Stdout.text(`${(this.settings.getLSPrefix() ? '[File] ' : '')}${file.filename}`)); }); + this.setExitCode(0); } ls(iohandler: IOHandler) { const args = iohandler.args; if (args.length === 0) { this.fileService.getFiles(this.activeDevice['uuid'], this.working_dir).subscribe(files => { - this.list_files(files, iohandler.stdout); + this.list_files(files, iohandler); }); } else if (args.length === 1) { let path: Path; try { path = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(target => { if (target.is_directory) { - this.fileService.getFiles(this.activeDevice['uuid'], target.uuid).subscribe(files => - this.list_files(files, iohandler.stdout) - ); + this.fileService.getFiles(this.activeDevice['uuid'], target.uuid).subscribe(files => { + this.list_files(files, iohandler); + }); } else { - iohandler.stderr(Stderr.text('That is not a directory')); - this.setExitCode(1); + this.list_files([target], iohandler); } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That directory does not exist')); - this.setExitCode(1); + iohandler.stderr('That directory does not exist'); + this.setExitCode(2); } else { this.reportError(error); } }); } else { - iohandler.stderr(Stderr.text('usage: ls [directory]')); + iohandler.stderr('usage: ls [directory]'); this.setExitCode(1); } } @@ -576,13 +637,13 @@ export class DefaultTerminalState extends CommandTerminalState { let content = ''; if (!filename.match(/^[a-zA-Z0-9.\-_]+$/)) { - iohandler.stderr(Stderr.text('That filename is not valid')); + iohandler.stderr('That filename is not valid'); this.setExitCode(1); return; } if (filename.length > 64) { - iohandler.stderr(Stderr.text('That filename is too long')); + iohandler.stderr('That filename is too long'); this.setExitCode(1); return; } @@ -591,18 +652,18 @@ export class DefaultTerminalState extends CommandTerminalState { content = args.slice(1).join(' '); } - this.fileService.createFile(this.activeDevice['uuid'], filename, content, this.working_dir).subscribe({ - error: err => { + this.fileService.createFile(this.activeDevice['uuid'], filename, content, this.working_dir).subscribe( + _ => this.setExitCode(0), + err => { if (err.message === 'file_already_exists') { - iohandler.stderr(Stderr.text('That file already exists')); + iohandler.stderr('That file already exists'); this.setExitCode(1); } else { this.reportError(err); } - } - }); + }); } else { - iohandler.stderr(Stderr.text('usage: touch [content]')); + iohandler.stderr('usage: touch [content]'); this.setExitCode(1); } } @@ -614,28 +675,32 @@ export class DefaultTerminalState extends CommandTerminalState { try { path = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { if (file.is_directory) { - iohandler.stderr(Stderr.text('That is not a file')); + iohandler.stderr('That is not a file'); this.setExitCode(1); } else { - iohandler.stdout(Stdout.text(file.content)); + const lines = file.content.split('\n'); + lines.forEach((line) => + iohandler.stdout(Stdout.text(line)) + ); + this.setExitCode(0); } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); } }); } else { - iohandler.stderr(Stderr.text('usage: cat ')); + iohandler.stderr('usage: cat '); this.setExitCode(1); } } @@ -647,7 +712,7 @@ export class DefaultTerminalState extends CommandTerminalState { try { path = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } @@ -658,6 +723,7 @@ export class DefaultTerminalState extends CommandTerminalState { device_uuid: this.activeDevice['uuid'], file_uuid: file.uuid }); + this.setExitCode(0); }; if (file.content.trim().length === 47) { const walletCred = file.content.split(' '); @@ -676,9 +742,10 @@ export class DefaultTerminalState extends CommandTerminalState { device_uuid: this.activeDevice['uuid'], file_uuid: file.uuid }); + this.setExitCode(0); }, error => { - iohandler.stderr(Stderr.text('The wallet couldn\'t be deleted successfully. ' + - 'Please report this bug.')); + iohandler.stderr('The wallet couldn\'t be deleted successfully. ' + + 'Please report this bug.'); this.setExitCode(1); this.reportError(error); }); @@ -695,14 +762,14 @@ export class DefaultTerminalState extends CommandTerminalState { } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); } }); } else { - iohandler.stderr(Stderr.text('usage: rm ')); + iohandler.stderr('usage: rm '); this.setExitCode(1); } } @@ -716,36 +783,36 @@ export class DefaultTerminalState extends CommandTerminalState { srcPath = Path.fromString(args[0], this.working_dir); destPath = Path.fromString(args[1], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } const deviceUUID = this.activeDevice['uuid']; this.fileService.getFromPath(deviceUUID, srcPath).subscribe(source => { - this.fileService.copyFile(source, destPath).subscribe({ - error: error => { + this.fileService.copyFile(source, destPath).subscribe( + _ => this.setExitCode(0), + error => { if (error.message === 'file_already_exists') { - iohandler.stderr(Stderr.text('That file already exists')); + iohandler.stderr('That file already exists'); } else if (error.message === 'cannot_copy_directory') { - iohandler.stderr(Stderr.text('Cannot copy directories')); + iohandler.stderr('Cannot copy directories'); } else if (error.message === 'destination_not_found') { - iohandler.stderr(Stderr.text('The destination folder was not found')); + iohandler.stderr('The destination folder was not found'); } else { this.reportError(error); } this.setExitCode(1); - } - }); + }); }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } }); } else { - iohandler.stderr(Stderr.text('usage: cp ')); + iohandler.stderr('usage: cp '); this.setExitCode(1); } } @@ -759,36 +826,36 @@ export class DefaultTerminalState extends CommandTerminalState { srcPath = Path.fromString(args[0], this.working_dir); destPath = Path.fromString(args[1], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], srcPath).subscribe(source => { if (source.is_directory) { - iohandler.stderr(Stderr.text('You cannot move directories')); + iohandler.stderr('You cannot move directories'); this.setExitCode(1); return; } - this.fileService.moveToPath(source, destPath).subscribe({ - error: err => { + this.fileService.moveToPath(source, destPath).subscribe( + _ => this.setExitCode(0), + err => { if (err.message === 'destination_is_file') { - iohandler.stderr(Stderr.text('The destination must be a directory')); + iohandler.stderr('The destination must be a directory'); this.setExitCode(1); } else if (err.message === 'file_already_exists') { - iohandler.stderr(Stderr.text('A file with the specified name already exists in the destination directory')); + iohandler.stderr('A file with the specified name already exists in the destination directory'); this.setExitCode(1); } else if (err.message === 'file_not_found') { - iohandler.stderr(Stderr.text('The destination directory does not exist')); + iohandler.stderr('The destination directory does not exist'); this.setExitCode(1); } else { this.reportError(err); } - } - }); + }); }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -796,7 +863,7 @@ export class DefaultTerminalState extends CommandTerminalState { }); } else { - iohandler.stderr(Stderr.text('usage: mv ')); + iohandler.stderr('usage: mv '); this.setExitCode(1); } } @@ -808,7 +875,7 @@ export class DefaultTerminalState extends CommandTerminalState { try { filePath = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } @@ -816,31 +883,31 @@ export class DefaultTerminalState extends CommandTerminalState { const name = args[1]; if (!name.match(/^[a-zA-Z0-9.\-_]+$/)) { - iohandler.stderr(Stderr.text('That name is not valid')); + iohandler.stderr('That name is not valid'); this.setExitCode(1); return; } if (name.length > 64) { - iohandler.stderr(Stderr.text('That name is too long')); + iohandler.stderr('That name is too long'); this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], filePath).subscribe(file => { - this.fileService.rename(file, name).subscribe({ - error: err => { + this.fileService.rename(file, name).subscribe( + _ => this.setExitCode(0), + err => { if (err.message === 'file_already_exists') { - iohandler.stderr(Stderr.text('A file with the specified name already exists')); + iohandler.stderr('A file with the specified name already exists'); this.setExitCode(1); } else { this.reportError(err); } - } - }); + }); }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -848,7 +915,7 @@ export class DefaultTerminalState extends CommandTerminalState { }); } else { - iohandler.stderr(Stderr.text('usage: rename ')); + iohandler.stderr('usage: rename '); this.setExitCode(1); } } @@ -858,49 +925,52 @@ export class DefaultTerminalState extends CommandTerminalState { if (args.length === 1) { const dirname = args[0]; if (!dirname.match(/^[a-zA-Z0-9.\-_]+$/)) { - iohandler.stderr(Stderr.text('That directory name is not valid')); + iohandler.stderr('That directory name is not valid'); this.setExitCode(1); return; } if (dirname.length > 64) { - iohandler.stderr(Stderr.text('That directory name is too long')); + iohandler.stderr('That directory name is too long'); this.setExitCode(1); return; } - this.fileService.createDirectory(this.activeDevice['uuid'], dirname, this.working_dir).subscribe({ - error: err => { + this.fileService.createDirectory(this.activeDevice['uuid'], dirname, this.working_dir).subscribe( + _ => this.setExitCode(0), + err => { if (err.message === 'file_already_exists') { - iohandler.stderr(Stderr.text('A file with the specified name already exists')); + iohandler.stderr('A file with the specified name already exists'); this.setExitCode(1); } else { this.reportError(err); } - } - }); + }); } else { - iohandler.stderr(Stderr.text('usage: mkdir ')); + iohandler.stderr('usage: mkdir '); this.setExitCode(1); } } exit() { this.terminal.popState(); + this.setExitCode(0); } clear() { this.terminal.clear(); + this.setExitCode(0); } - history() { + history(iohandler: IOHandler) { const l = this.getHistory(); l.reverse(); l.forEach(e => { - this.terminal.outputText(e); + iohandler.stdout(Stdout.text(e)); }); + this.setExitCode(0); } morphcoin(iohandler: IOHandler) { @@ -910,12 +980,13 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('currency', ['reset'], {source_uuid: args[1]}).subscribe( () => { iohandler.stdout(Stdout.text('Wallet has been deleted successfully.')); + this.setExitCode(0); }, error => { if (error.message === 'permission_denied') { - iohandler.stderr(Stderr.text('Permission denied.')); + iohandler.stderr('Permission denied.'); } else { - iohandler.stderr(Stderr.text('Wallet does not exist.')); + iohandler.stderr('Wallet does not exist.'); } this.setExitCode(1); } @@ -927,7 +998,7 @@ export class DefaultTerminalState extends CommandTerminalState { try { path = Path.fromString(args[1], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } @@ -935,7 +1006,7 @@ export class DefaultTerminalState extends CommandTerminalState { if (args[0] === 'look') { this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { if (file.is_directory) { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); return; } @@ -947,21 +1018,22 @@ export class DefaultTerminalState extends CommandTerminalState { if (uuid.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/) && key.match(/^[a-f0-9]{10}$/)) { this.websocket.ms('currency', ['get'], {source_uuid: uuid, key: key}).subscribe(wallet => { iohandler.stdout(Stdout.text(new Intl.NumberFormat().format(wallet.amount / 1000) + ' morphcoin')); + this.setExitCode(0); }, () => { - iohandler.stderr(Stderr.text('That file is not connected with a wallet')); + iohandler.stderr('That file is not connected with a wallet'); this.setExitCode(1); }); } else { - iohandler.stderr(Stderr.text('That file is not a wallet file')); + iohandler.stderr('That file is not a wallet file'); this.setExitCode(1); } } else { - iohandler.stderr(Stderr.text('That file is not a wallet file')); + iohandler.stderr('That file is not a wallet file'); this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -974,7 +1046,7 @@ export class DefaultTerminalState extends CommandTerminalState { : of({uuid: path.parentUUID}) ).subscribe(dest => { this.fileService.getFromPath(this.activeDevice['uuid'], new Path(path.path.slice(-1), dest.uuid)).subscribe(() => { - iohandler.stderr(Stderr.text('That file already exists')); + iohandler.stderr('That file already exists'); this.setExitCode(1); }, error => { if (error.message === 'file_not_found') { @@ -982,26 +1054,31 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('currency', ['create'], {}).subscribe(wallet => { const credentials = wallet.source_uuid + ' ' + wallet.key; - this.fileService.createFile(this.activeDevice['uuid'], path.path[path.path.length - 1], credentials, this.working_dir) - .subscribe({ - error: err => { - iohandler.stderr(Stderr.text('That file touldn\'t be created. Please note your wallet credentials ' + - 'and put them in a new file with \'touch\' or contact the support: \'' + credentials + '\'')); + this.fileService.createFile( + this.activeDevice['uuid'], + path.path[path.path.length - 1], + credentials, + this.working_dir + ) + .subscribe( + _ => this.setExitCode(0), + err => { + iohandler.stderr('That file touldn\'t be created. Please note your wallet credentials ' + + 'and put them in a new file with \'touch\' or contact the support: \'' + credentials + '\''); this.setExitCode(1); this.reportError(err); - } - }); + }); }, error1 => { if (error1.message === 'already_own_a_wallet') { - iohandler.stderr(Stderr.text('You already own a wallet')); + iohandler.stderr('You already own a wallet'); } else { - iohandler.stderr(Stderr.text(error1.message)); + iohandler.stderr(error1.message); this.reportError(error1); } this.setExitCode(1); }); } else { - iohandler.stderr(Stderr.text('Filename too long. Only 64 chars allowed')); + iohandler.stderr('Filename too long. Only 64 chars allowed'); this.setExitCode(1); } } else { @@ -1010,7 +1087,7 @@ export class DefaultTerminalState extends CommandTerminalState { }); }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That path does not exist')); + iohandler.stderr('That path does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -1020,7 +1097,7 @@ export class DefaultTerminalState extends CommandTerminalState { } else if (args.length === 1 && args[0] === 'list') { this.websocket.ms('currency', ['list'], {}).subscribe(data => { if (data.wallets.length === 0) { - iohandler.stderr(Stderr.text('You don\'t own any wallet.')); + iohandler.stderr('You don\'t own any wallet.'); this.setExitCode(1); } else { iohandler.stdout(Stdout.text('Your wallets:')); @@ -1032,10 +1109,11 @@ export class DefaultTerminalState extends CommandTerminalState { iohandler.stdout(Stdout.node(el)); DefaultTerminalState.registerPromptAppenders(el); + this.setExitCode(0); } }); } else { - iohandler.stderr(Stderr.text('usage: morphcoin look|create|list|reset [|]')); + iohandler.stderr('usage: morphcoin look|create|list|reset [|]'); this.setExitCode(1); } } @@ -1047,7 +1125,7 @@ export class DefaultTerminalState extends CommandTerminalState { try { walletPath = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } @@ -1060,12 +1138,12 @@ export class DefaultTerminalState extends CommandTerminalState { } if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) { - iohandler.stderr(Stderr.html('amount is not a valid number')); + iohandler.stderr('amount is not a valid number'); this.setExitCode(1); } else { this.fileService.getFromPath(this.activeDevice['uuid'], walletPath).subscribe(walletFile => { if (walletFile.is_directory) { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); return; } @@ -1084,25 +1162,26 @@ export class DefaultTerminalState extends CommandTerminalState { usage: usage }).subscribe(() => { iohandler.stdout(Stdout.text('Successfully sent ' + amount + ' to ' + receiver)); + this.setExitCode(0); }, error => { - iohandler.stderr(Stderr.text(error.message)); + iohandler.stderr(error.message); this.reportError(error); }); }, () => { - iohandler.stderr(Stderr.text('That file is not connected with a wallet')); + iohandler.stderr('That file is not connected with a wallet'); this.setExitCode(1); }); } else { - iohandler.stderr(Stderr.text('That file is not a wallet file')); + iohandler.stderr('That file is not a wallet file'); this.setExitCode(1); } } else { - iohandler.stderr(Stderr.text('That file is not a wallet file')); + iohandler.stderr('That file is not a wallet file'); this.setExitCode(1); } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -1110,7 +1189,7 @@ export class DefaultTerminalState extends CommandTerminalState { }); } } else { - iohandler.stderr(Stderr.text('usage: pay [usage]')); + iohandler.stderr('usage: pay [usage]'); this.setExitCode(1); } } @@ -1133,7 +1212,7 @@ export class DefaultTerminalState extends CommandTerminalState { if (args.length >= 1 && args[0].toLowerCase() === 'create') { if (args.length !== 2) { - iohandler.stderr(Stderr.text('usage: service create ')); + iohandler.stderr('usage: service create '); this.setExitCode(1); return; } @@ -1141,15 +1220,16 @@ export class DefaultTerminalState extends CommandTerminalState { const service = args[1]; const services = ['bruteforce', 'portscan', 'telnet', 'ssh']; if (!services.includes(service)) { - iohandler.stderr(Stderr.text('Unknown service. Available services: ' + services.join(', '))); + iohandler.stderr('Unknown service. Available services: ' + services.join(', ')); this.setExitCode(1); return; } this.websocket.ms('service', ['create'], {name: service, device_uuid: activeDevice}).subscribe(() => { iohandler.stdout(Stdout.text('Service was created')); + this.setExitCode(0); }, error => { if (error === 'already_own_this_service') { - iohandler.stderr(Stderr.text('You already created this service')); + iohandler.stderr('You already created this service'); this.setExitCode(1); } else { this.reportError(error); @@ -1157,18 +1237,19 @@ export class DefaultTerminalState extends CommandTerminalState { }); } else if (args.length >= 1 && args[0] === 'list') { if (args.length !== 1) { - iohandler.stderr(Stderr.text('usage: service list')); + iohandler.stderr('usage: service list'); this.setExitCode(1); return; } getServices().subscribe(services => { if (services.length === 0) { - iohandler.stderr(Stderr.text('There is no service on this device')); + iohandler.stderr('There is no service on this device'); this.setExitCode(1); } else { const dev = document.createElement('span'); - dev.innerHTML = '\'' + this.activeDevice['name'] + '\' (' + DefaultTerminalState.promptAppender(this.activeDevice['uuid']) + '):'; + dev.innerHTML = '\'' + this.activeDevice['name'] + '\' (' + + DefaultTerminalState.promptAppender(this.activeDevice['uuid']) + '):'; const el = document.createElement('ul'); el.innerHTML = services @@ -1183,11 +1264,12 @@ export class DefaultTerminalState extends CommandTerminalState { iohandler.stdout(Stdout.node(el)); DefaultTerminalState.registerPromptAppenders(dev); DefaultTerminalState.registerPromptAppenders(el); + this.setExitCode(0); } }); } else if (args.length >= 1 && args[0] === 'bruteforce') { if (args.length !== 3) { - iohandler.stderr(Stderr.text('usage: service bruteforce ')); + iohandler.stderr('usage: service bruteforce '); this.setExitCode(1); return; } @@ -1195,7 +1277,7 @@ export class DefaultTerminalState extends CommandTerminalState { const [targetDevice, targetService] = args.slice(1); getService('bruteforce').subscribe(bruteforceService => { if (bruteforceService == null || bruteforceService['uuid'] == null) { - iohandler.stderr(Stderr.text('You have to create a bruteforce service before you use it')); + iohandler.stderr('You have to create a bruteforce service before you use it'); this.setExitCode(1); return; } @@ -1209,13 +1291,14 @@ export class DefaultTerminalState extends CommandTerminalState { this.terminal.pushState(new BruteforceTerminalState(this.terminal, this.domSanitizer, stop => { if (stop) { this.executeCommand('service', ['bruteforce', targetDevice, targetService]); + this.setExitCode(0); } })); }, error1 => { if (error1.message === 'could_not_start_service') { - iohandler.stderr(Stderr.text('There was an error while starting the bruteforce attack')); + iohandler.stderr('There was an error while starting the bruteforce attack'); } else if (error1.message === 'invalid_input_data') { - iohandler.stderr(Stderr.text('The specified UUID is not valid')); + iohandler.stderr('The specified UUID is not valid'); } else { this.reportError(error1); } @@ -1234,6 +1317,7 @@ export class DefaultTerminalState extends CommandTerminalState { '. Stopping...'; iohandler.stdout(Stdout.node(div)); DefaultTerminalState.registerPromptAppenders(div); + this.setExitCode(255); } this.websocket.ms('service', ['bruteforce', 'stop'], { @@ -1241,8 +1325,9 @@ export class DefaultTerminalState extends CommandTerminalState { }).subscribe(stopData => { if (stopData['access'] === true) { iohandler.stdout(Stdout.text('Access granted - use `connect `')); + this.setExitCode(0); } else { - iohandler.stderr(Stderr.text('Access denied. The bruteforce attack was not successful')); + iohandler.stderr('Access denied. The bruteforce attack was not successful'); this.setExitCode(255); } @@ -1251,7 +1336,7 @@ export class DefaultTerminalState extends CommandTerminalState { } }, (err) => { if (err.message === 'service_not_running') { - iohandler.stderr(Stderr.text('Target service is unreachable.')); + iohandler.stderr('Target service is unreachable.'); this.setExitCode(255); } }); @@ -1265,7 +1350,7 @@ export class DefaultTerminalState extends CommandTerminalState { }); } else if (args.length >= 1 && args[0] === 'portscan') { if (args.length !== 2) { - iohandler.stderr(Stderr.text('usage: service portscan ')); + iohandler.stderr('usage: service portscan '); this.setExitCode(1); return; } @@ -1273,7 +1358,7 @@ export class DefaultTerminalState extends CommandTerminalState { const targetDevice = args[1]; getService('portscan').subscribe(portscanService => { if (portscanService == null || portscanService['uuid'] == null) { - iohandler.stderr(Stderr.text('You have to create a portscan service before you use it')); + iohandler.stderr('You have to create a portscan service before you use it'); this.setExitCode(1); return; } @@ -1284,7 +1369,7 @@ export class DefaultTerminalState extends CommandTerminalState { }).subscribe(data => { const runningServices = data['services']; if (runningServices == null || !(runningServices instanceof Array) || (runningServices as any[]).length === 0) { - iohandler.stderr(Stderr.text('That device doesn\'t have any running services')); + iohandler.stderr('That device doesn\'t have any running services'); this.setExitCode(1); return; } @@ -1303,10 +1388,11 @@ export class DefaultTerminalState extends CommandTerminalState { iohandler.stdout(Stdout.node(list)); DefaultTerminalState.registerPromptAppenders(list); + this.setExitCode(0); }); }); } else { - iohandler.stderr(Stderr.text('usage: service create|list|bruteforce|portscan')); + iohandler.stderr('usage: service create|list|bruteforce|portscan'); this.setExitCode(1); } } @@ -1316,11 +1402,11 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('service', ['list'], {'device_uuid': this.activeDevice['uuid']}).subscribe(localServices => { const portScanner = (localServices['services'] || []).filter(service => service.name === 'portscan')[0]; if (portScanner == null || portScanner['uuid'] == null) { - iohandler.stderr(Stderr.text('\'' + random_device['name'] + '\':')); - iohandler.stderr(Stderr.raw('
      ' + + iohandler.stderr('\'' + random_device['name'] + '\':'); + iohandler.stderr('
        ' + '
      • UUID: ' + random_device['uuid'] + '
      • ' + '
      • Services: portscan failed
      • ' + - '
      ')); + '
    '); this.setExitCode(1); return; } @@ -1335,13 +1421,15 @@ export class DefaultTerminalState extends CommandTerminalState { '
  • Services:
  • ' + '
      ' + remoteServices['services'] - .map(service => '
    • ' + escapeHtml(service['name']) + ' (' + DefaultTerminalState.promptAppender(service['uuid']) + ')
    • ') + .map(service => '
    • ' + escapeHtml(service['name']) + ' (' + + DefaultTerminalState.promptAppender(service['uuid']) + ')
    • ') .join('\n') + '
    '; iohandler.stdout(Stdout.node(list)); DefaultTerminalState.registerPromptAppenders(list); + this.setExitCode(0); }, error => { - iohandler.stderr(Stderr.html('An error occurred')); + iohandler.stderr('An error occurred'); this.reportError(error); return; }); @@ -1352,7 +1440,7 @@ export class DefaultTerminalState extends CommandTerminalState { connect(iohandler: IOHandler) { const args = iohandler.args; if (args.length !== 1) { - iohandler.stderr(Stderr.text('usage: connect ')); + iohandler.stderr('usage: connect '); this.setExitCode(1); return; } @@ -1362,16 +1450,17 @@ export class DefaultTerminalState extends CommandTerminalState { if (infoData['owner'] === this.websocket.account.uuid || partOwnerData['ok'] === true) { this.terminal.pushState(new DefaultTerminalState(this.websocket, this.settings, this.fileService, this.domSanitizer, this.windowDelegate, infoData, this.terminal, '#DD2C00')); + this.setExitCode(0); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); } }, error => { - iohandler.stderr(Stderr.text(error.message)); + iohandler.stderr(error.message); this.reportError(error); }); }, error => { - iohandler.stderr(Stderr.text(error.message)); + iohandler.stderr(error.message); this.reportError(error); }); } @@ -1395,10 +1484,11 @@ export class DefaultTerminalState extends CommandTerminalState { }); iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); DefaultTerminalState.registerPromptAppenders(element); } else { - iohandler.stderr(Stderr.text('No public networks found')); + iohandler.stderr('No public networks found'); this.setExitCode(1); } }); @@ -1430,10 +1520,11 @@ export class DefaultTerminalState extends CommandTerminalState { }); iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); DefaultTerminalState.registerPromptAppenders(element); } else { - iohandler.stderr(Stderr.text('This device is not part of a network')); + iohandler.stderr('This device is not part of a network'); this.setExitCode(1); } }); @@ -1448,7 +1539,7 @@ export class DefaultTerminalState extends CommandTerminalState { const invitations = invitationsData['invitations']; if (invitations.length === 0) { - iohandler.stderr(Stderr.text('No invitations found')); + iohandler.stderr('No invitations found'); this.setExitCode(1); } else { iohandler.stdout(Stdout.text('Found ' + invitations.length + ' invitations: ')); @@ -1467,10 +1558,11 @@ export class DefaultTerminalState extends CommandTerminalState { }); iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); } }, error => { if (error.message === 'no_permissions') { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); } else { this.reportError(error); } @@ -1488,8 +1580,9 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', ['delete'], data).subscribe(() => { iohandler.stdout(Stdout.text('Network deleted')); + this.setExitCode(0); }, () => { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); }); @@ -1503,15 +1596,16 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', ['request'], data).subscribe(requestData => { iohandler.stdout(Stdout.text('Request sent:')); iohandler.stdout(Stdout.text(this.activeDevice['name'] + ' -> ' + requestData['network'])); + this.setExitCode(0); }, error => { if (error.message === 'network_not_found') { - iohandler.stderr(Stderr.text('Network not found: ' + args[1])); + iohandler.stderr('Network not found: ' + args[1]); } else if (error.message === 'already_member_of_network') { - iohandler.stderr(Stderr.text('You are already a member of this network')); + iohandler.stderr('You are already a member of this network'); } else if (error.message === 'invitation_already_exists') { - iohandler.stderr(Stderr.text('You already requested to enter this network')); + iohandler.stderr('You already requested to enter this network'); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); } this.setExitCode(1); }); @@ -1526,7 +1620,7 @@ export class DefaultTerminalState extends CommandTerminalState { const requests = requestsData['requests']; if (requests.length === 0) { - iohandler.stderr(Stderr.text('No requests found')); + iohandler.stderr('No requests found'); this.setExitCode(1); } else { iohandler.stdout(Stdout.text('Found ' + requests.length + ' requests: ')); @@ -1542,11 +1636,12 @@ export class DefaultTerminalState extends CommandTerminalState { }); iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); DefaultTerminalState.registerPromptAppenders(element); } }, () => { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); }); @@ -1558,12 +1653,13 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', [args[0]], data).subscribe(() => { iohandler.stdout(Stdout.text(args[1] + ' -> ' + args[0])); + this.setExitCode(0); }, error => { if (error.message === 'invitation_not_found') { - iohandler.stderr(Stderr.text('Invitation not found')); + iohandler.stderr('Invitation not found'); this.setExitCode(1); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); } }); @@ -1577,12 +1673,13 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', ['leave'], data).subscribe(() => { iohandler.stdout(Stdout.text('You left the network: ' + args[1])); + this.setExitCode(0); }, error => { if (error.message === 'cannot_leave_own_network') { - iohandler.stderr(Stderr.text('You cannot leave your own network')); + iohandler.stderr('You cannot leave your own network'); this.setExitCode(1); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); } }); @@ -1600,10 +1697,11 @@ export class DefaultTerminalState extends CommandTerminalState { element.innerHTML += 'Owner: ' + DefaultTerminalState.promptAppender(getData['owner']) + ''; iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); DefaultTerminalState.registerPromptAppenders(element); }, () => { - iohandler.stderr(Stderr.text('Network not found: ' + args[1])); + iohandler.stderr('Network not found: ' + args[1]); this.setExitCode(1); }); @@ -1631,14 +1729,15 @@ export class DefaultTerminalState extends CommandTerminalState { }); iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); DefaultTerminalState.registerPromptAppenders(element); } else { - iohandler.stderr(Stderr.text('This network has no members')); + iohandler.stderr('This network has no members'); this.setExitCode(1); } }, () => { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); }); @@ -1659,20 +1758,21 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', ['create'], data).subscribe(createData => { iohandler.stdout(Stdout.text('Name: ' + createData['name'])); iohandler.stdout(Stdout.text('Visibility: ' + (createData['hidden'] ? 'private' : 'public'))); + this.setExitCode(0); }, error => { if (error.message === 'invalid_name') { - iohandler.stderr(Stderr.text('Name is invalid: Use 5 - 20 characters')); + iohandler.stderr('Name is invalid: Use 5 - 20 characters'); this.setExitCode(1); } else if (error.message === 'name_already_in_use') { - iohandler.stderr(Stderr.text('Name already in use')); + iohandler.stderr('Name already in use'); this.setExitCode(1); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); } }); } else { - iohandler.stderr(Stderr.text('Please use public or private as mode')); + iohandler.stderr('Please use public or private as mode'); this.setExitCode(1); } @@ -1685,18 +1785,19 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', ['invite'], data).subscribe(() => { iohandler.stdout(Stdout.text(args[2] + ' invited to ' + args[1])); + this.setExitCode(0); }, error => { if (error.message === 'network_not_found') { - iohandler.stderr(Stderr.text('Network not found: ' + args[1])); + iohandler.stderr('Network not found: ' + args[1]); this.setExitCode(1); } else if (error.message === 'already_member_of_network') { - iohandler.stderr(Stderr.text('This device is already a member of this network')); + iohandler.stderr('This device is already a member of this network'); this.setExitCode(1); } else if (error.message === 'invitation_already_exists') { - iohandler.stderr(Stderr.text('You already invited this device')); + iohandler.stderr('You already invited this device'); this.setExitCode(1); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); } }); @@ -1709,7 +1810,7 @@ export class DefaultTerminalState extends CommandTerminalState { }; if (data['device'] === this.activeDevice['uuid']) { - iohandler.stderr(Stderr.text('You cannot kick yourself')); + iohandler.stderr('You cannot kick yourself'); this.setExitCode(1); return; } @@ -1717,16 +1818,17 @@ export class DefaultTerminalState extends CommandTerminalState { this.websocket.ms('network', ['kick'], data).subscribe(kickData => { if (kickData['result']) { iohandler.stdout(Stdout.text('Kicked successfully')); + this.setExitCode(0); } else { - iohandler.stderr(Stderr.text('The device is not a member of the network')); + iohandler.stderr('The device is not a member of the network'); this.setExitCode(1); } }, error => { if (error.message === 'cannot_kick_owner') { - iohandler.stderr(Stderr.text('You cannot kick the owner of the network')); + iohandler.stderr('You cannot kick the owner of the network'); this.setExitCode(1); } else { - iohandler.stderr(Stderr.text('Access denied')); + iohandler.stderr('Access denied'); this.setExitCode(255); } }); @@ -1734,22 +1836,24 @@ export class DefaultTerminalState extends CommandTerminalState { return; } } - iohandler.stdout(Stdout.text('network list # show all networks of this device')); - iohandler.stdout(Stdout.text('network public # show all public networks')); - iohandler.stdout(Stdout.text('network invitations # show invitations of a this device')); - iohandler.stdout(Stdout.text('network info # show info of network')); - iohandler.stdout(Stdout.text('network get # show info of network')); - iohandler.stdout(Stdout.text('network members # show members of network')); - iohandler.stdout(Stdout.text('network leave # leave a network')); - iohandler.stdout(Stdout.text('network delete # delete a network')); - iohandler.stdout(Stdout.text('network request # create a join request to a network')); - iohandler.stdout(Stdout.text('network requests # show requests of a network')); - iohandler.stdout(Stdout.text('network accept # accept an invitation or request')); - iohandler.stdout(Stdout.text('network deny # accept an invitation or request')); - iohandler.stdout(Stdout.text('network invite # invite to network')); - iohandler.stdout(Stdout.text('network revoke # revoke an invitation')); - iohandler.stdout(Stdout.text('network kick # kick device out of network')); - iohandler.stdout(Stdout.text('network create # create a network')); + iohandler.stderr('usage: '); + iohandler.stderr('network list # show all networks of this device'); + iohandler.stderr('network public # show all public networks'); + iohandler.stderr('network invitations # show invitations of a this device'); + iohandler.stderr('network info # show info of network'); + iohandler.stderr('network get # show info of network'); + iohandler.stderr('network members # show members of network'); + iohandler.stderr('network leave # leave a network'); + iohandler.stderr('network delete # delete a network'); + iohandler.stderr('network request # create a join request to a network'); + iohandler.stderr('network requests # show requests of a network'); + iohandler.stderr('network accept # accept an invitation or request'); + iohandler.stderr('network deny # accept an invitation or request'); + iohandler.stderr('network invite # invite to network'); + iohandler.stderr('network revoke # revoke an invitation'); + iohandler.stderr('network kick # kick device out of network'); + iohandler.stderr('network create # create a network'); + this.setExitCode(1); } info(iohandler: IOHandler) { @@ -1757,8 +1861,10 @@ export class DefaultTerminalState extends CommandTerminalState { iohandler.stdout(Stdout.text('Host: ' + this.activeDevice['name'])); const element = document.createElement('div'); - element.innerHTML = `Address: ${DefaultTerminalState.promptAppender(this.activeDevice['uuid'])}`; + element.innerHTML = 'Address: ' + + DefaultTerminalState.promptAppender(this.activeDevice['uuid']) + ''; iohandler.stdout(Stdout.node(element)); + this.setExitCode(0); DefaultTerminalState.registerPromptAppenders(element); } @@ -1766,7 +1872,7 @@ export class DefaultTerminalState extends CommandTerminalState { run(iohandler: IOHandler) { const args = iohandler.args; if (args.length === 0) { - iohandler.stderr(Stderr.text('usage: run ')); + iohandler.stderr('usage: run '); this.setExitCode(1); return; } @@ -1774,13 +1880,13 @@ export class DefaultTerminalState extends CommandTerminalState { try { path = Path.fromString(args[0], this.working_dir); } catch { - iohandler.stderr(Stderr.text('The specified path is not valid')); + iohandler.stderr('The specified path is not valid'); this.setExitCode(1); return; } this.fileService.getFromPath(this.activeDevice['uuid'], path).subscribe(file => { if (file.is_directory) { - iohandler.stderr(Stderr.text('That is not a file')); + iohandler.stderr('That is not a file'); this.setExitCode(1); } else { // set special variables @@ -1797,14 +1903,15 @@ export class DefaultTerminalState extends CommandTerminalState { // reset special variables '#0*@'.split('').forEach((variable: string) => { this.variables.delete(variable); - }) + }); for (let i = 0; i <= numberOfArgs; i++) { this.variables.delete(String(i)); } + this.setExitCode(0); } }, error => { if (error.message === 'file_not_found') { - iohandler.stderr(Stderr.text('That file does not exist')); + iohandler.stderr('That file does not exist'); this.setExitCode(1); } else { this.reportError(error); @@ -1815,15 +1922,30 @@ export class DefaultTerminalState extends CommandTerminalState { setVariable(iohandler: IOHandler) { const args = iohandler.args; if (args.length !== 2) { - iohandler.stderr(Stderr.text('usage: set ')); + iohandler.stderr('usage: set '); this.setExitCode(1); return; } this.variables.set(args[0], args[1]); + this.setExitCode(0); } echo(iohandler: IOHandler) { iohandler.stdout(Stdout.text(iohandler.args.join(' '))); + this.setExitCode(0); + } + + read(iohandler: IOHandler) { + const args = iohandler.args; + if (args.length !== 1) { + iohandler.stderr('usage: read '); + this.setExitCode(1); + return; + } + iohandler.stdin((input) => { + this.variables.set(args[0], input); + this.setExitCode(0); + }); } } @@ -1903,9 +2025,9 @@ export class BruteforceTerminalState extends ChoiceTerminalState { }; constructor(terminal: TerminalAPI, - private domSanitizer: DomSanitizer, - private callback: (response: boolean) => void, - private startSeconds: number = 0) { + private domSanitizer: DomSanitizer, + private callback: (response: boolean) => void, + private startSeconds: number = 0) { super(terminal); this.intervalHandle = setInterval(() => { @@ -1922,14 +2044,43 @@ export class BruteforceTerminalState extends ChoiceTerminalState { } } +class DefaultStdin implements TerminalState { + private callback: (stdin: string) => void; + + constructor(private terminal: TerminalAPI) {} + + read(callback: (stdin: string) => void) { + this.callback = callback; + this.terminal.pushState(this); + } + + execute(command: string) { + const input = command ? command : ''; + this.terminal.popState(); + this.callback(input); + } + + autocomplete(_: string): string { + return ''; + } + + getHistory(): string[] { + return []; + } + + refreshPrompt() { + this.terminal.changePrompt('>'); + } +} + + class IOHandler { stdout: (stdout: Stdout) => void; - stdin: (stdin: Stdin) => void; - stderr: (stderr: Stderr) => void; + stdin: (callback: (stdin: string) => void) => void; + stderr: (stderr: string) => void; args: string[]; } -class Stdin {} class Stderr { outputType: OutputType; data: string;