From d7cf39c1bce72fc093406d548fb2700ad3154a90 Mon Sep 17 00:00:00 2001 From: Graham Dumpleton <Graham.Dumpleton@gmail.com> Date: Mon, 1 May 2023 13:56:08 +1000 Subject: [PATCH] Explicitly disable bracketed paste mode when executing commands in terminal. --- workshop-images/base-environment/Dockerfile | 2 +- .../gateway/src/frontend/scripts/educates.ts | 92 ++++++++++++------- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/workshop-images/base-environment/Dockerfile b/workshop-images/base-environment/Dockerfile index 3c882f8e..10e0a2d7 100644 --- a/workshop-images/base-environment/Dockerfile +++ b/workshop-images/base-environment/Dockerfile @@ -58,7 +58,7 @@ RUN HOME=/root && \ dnf clean -y --enablerepo='*' all && \ sed -i.bak -e '1i auth requisite pam_deny.so' /etc/pam.d/su && \ sed -i.bak -e 's/^%wheel/# %wheel/' /etc/sudoers && \ - echo "set enable-bracketed-paste off" >> /etc/inputrc && \ + echo "# set enable-bracketed-paste off" >> /etc/inputrc && \ useradd -u 1001 -g 0 -M -d /home/eduk8s eduk8s && \ mkdir -p /home/eduk8s && \ chown -R 1001:0 /home/eduk8s && \ diff --git a/workshop-images/base-environment/opt/gateway/src/frontend/scripts/educates.ts b/workshop-images/base-environment/opt/gateway/src/frontend/scripts/educates.ts index 2b1e17e9..79b37b55 100644 --- a/workshop-images/base-environment/opt/gateway/src/frontend/scripts/educates.ts +++ b/workshop-images/base-environment/opt/gateway/src/frontend/scripts/educates.ts @@ -87,6 +87,11 @@ interface ErrorPacketArgs { reason: string } +interface BufferedDataBlock { + text: string + bracketed: boolean +} + class TerminalSession { private id: string private element: HTMLElement @@ -98,7 +103,7 @@ class TerminalSession { private started: Date private sequence: number private blocked: boolean - private buffer: string[] + private buffer: BufferedDataBlock[] private reconnecting: boolean private reconnectTimer: any private shutdown: boolean @@ -265,7 +270,7 @@ class TerminalSession { // any initial prompt. If a reconnect occurs while we were // waiting, discard the data. - setTimeout(() => { + setTimeout(async () => { if (socket !== this.socket) { this.buffer = [] return @@ -276,8 +281,12 @@ class TerminalSession { let buffer = this.buffer this.buffer = [] - for (let text of buffer) - this.paste(text) + if (buffer.length) { + console.log("Flushing data to terminal", this.id) + + for (let block of buffer) + await this.paste(block.text, block.bracketed) + } }, 1000) // Generate analytics event to track terminal connect. @@ -351,7 +360,7 @@ class TerminalSession { } } - this.socket.onmessage = (evt) => { + this.socket.onmessage = async (evt) => { // If the socket isn't the one currently associated with the // terminal then bail out straight away as some sort of mixup has // occurred. Close the socket for good measure. @@ -375,7 +384,7 @@ class TerminalSession { case (PacketType.DATA): { let args: InboundDataPacketArgs = packet.args - this.terminal.write(args.data) + await this.write(args.data) // Update the sequence number to that received on the // DATA message from the server side. This affects @@ -396,7 +405,7 @@ class TerminalSession { $("#refresh-button").addClass("terminal-" + this.id + "-refresh-required") this.scrollToBottom() - this.write("\r\nExited\r\n") + await this.write("\r\nExited\r\n") this.socket.close() @@ -520,7 +529,7 @@ class TerminalSession { setTimeout(connect, 100) - function terminate() { + async function terminate() { self.reconnectTimer = null if (!self.reconnecting) @@ -538,7 +547,7 @@ class TerminalSession { $("#refresh-button").addClass("terminal-" + self.id + "-refresh-required") self.scrollToBottom() - self.write("\r\nClosed\r\n") + await self.write("\r\nClosed\r\n") // Generate analytics event to track terminal close. @@ -652,8 +661,14 @@ class TerminalSession { return false } - write(text: string) { - this.terminal.write(text) + async write(text: string) { + let self = this + + function writeSync(text: string | Uint8Array): Promise<void> { + return new Promise<void>(resolve => self.terminal.write(text, resolve)); + } + + await writeSync(text) } focus() { @@ -668,11 +683,20 @@ class TerminalSession { this.terminal.scrollToBottom() } - paste(text: string) { - if (!this.blocked) - this.terminal.paste(text) - else - this.buffer.push(text) + async paste(text: string, bracketed: boolean=true) { + if (!this.blocked) { + if (this.terminal.modes.bracketedPasteMode && !bracketed) { + await this.write("\x1b[?2004l").then(_ => { + this.terminal.paste(text) + }).then(async _ => { + await this.write("\x1b[?2004h") + }) + } + else { + await this.terminal.paste(text) + } + } else + this.buffer.push({text: text, bracketed: bracketed}) } close() { @@ -780,25 +804,25 @@ class Terminals { // The following are the only APIs which separate frontend application // code should use to interact with terminals. - paste_to_terminal(text: string, id: string = "1") { + async paste_to_terminal(text: string, id: string = "1") { let terminal = this.sessions[id] if (terminal) - terminal.paste(text) + await terminal.paste(text) } - paste_to_all_terminals(text: string) { - this.paste_to_terminal(text, "1") - this.paste_to_terminal(text, "2") - this.paste_to_terminal(text, "3") + async paste_to_all_terminals(text: string) { + await this.paste_to_terminal(text, "1") + await this.paste_to_terminal(text, "2") + await this.paste_to_terminal(text, "3") } - interrupt_terminal(id: string = "1") { + async interrupt_terminal(id: string = "1") { let terminal = this.sessions[id] if (terminal) { terminal.scrollToBottom() - terminal.paste(String.fromCharCode(0x03)) + await terminal.paste(String.fromCharCode(0x03)) } } @@ -823,7 +847,7 @@ class Terminals { this.clear_terminal("3") } - execute_in_terminal(command: string, id: string = "1", clear: boolean = false) { + async execute_in_terminal(command: string, id: string = "1", clear: boolean = false) { if (command == "<ctrl-c>" || command == "<ctrl+c>") return this.interrupt_terminal(id) @@ -835,17 +859,17 @@ class Terminals { if (clear) terminal.clear() - terminal.paste(command + "\r") + await terminal.paste(command + "\r", false) } } - execute_in_all_terminals(command: string, clear: boolean = false) { + async execute_in_all_terminals(command: string, clear: boolean = false) { if (command == "<ctrl-c>" || command == "<ctrl+c>") return this.interrupt_all_terminals() - this.execute_in_terminal(command, "1") - this.execute_in_terminal(command, "2") - this.execute_in_terminal(command, "3") + await this.execute_in_terminal(command, "1") + await this.execute_in_terminal(command, "2") + await this.execute_in_terminal(command, "3") } disconnect_terminal(id: string = "1") { @@ -1623,7 +1647,7 @@ interface DashboardCreateOptions { } const action_table = { - "terminal:execute": function (args: TerminalExecuteOptions) { + "terminal:execute": async function (args: TerminalExecuteOptions) { let id = args.session || "1" if (id == "*") { dashboard.expose_dashboard("terminal") @@ -1631,12 +1655,12 @@ const action_table = { } else { dashboard.expose_terminal(id) - terminals.execute_in_terminal(args.command, id, args.clear) + await terminals.execute_in_terminal(args.command, id, args.clear) } }, - "terminal:execute-all": function (args: TerminalExecuteAllOptions) { + "terminal:execute-all": async function (args: TerminalExecuteAllOptions) { dashboard.expose_dashboard("terminal") - terminals.execute_in_all_terminals(args.command, args.clear) + await terminals.execute_in_all_terminals(args.command, args.clear) }, "terminal:clear": function (args: TerminalSelectOptions) { let id = args.session || "1"