From 2ea3f52fd139b80c2994278cbd3a469e9e1c9ef0 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Wed, 17 Dec 2025 17:06:52 +0900 Subject: [PATCH] fixed the blind conversion of a byte array to a string when the array may not contain a valid and complete utf8 sequence. it should have been treated as a binary blob. conversion is now using base64 encoding --- client-pty.go | 12 ++++++++++-- server-pty.go | 23 +++++++++++++++++++---- server-pxy.go | 12 ++++++++++-- xterm.html | 26 +++++++++++++++++++++----- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/client-pty.go b/client-pty.go index 7d63e0c..a71831e 100644 --- a/client-pty.go +++ b/client-pty.go @@ -1,5 +1,6 @@ package hodu +import "encoding/base64" import "encoding/json" import "errors" import "io" @@ -94,7 +95,7 @@ func (pty *client_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) { n, err = out.Read(buf[:]) if n > 0 { var err2 error - err2 = send_ws_data_for_xterm(ws, "iov", string(buf[:n])) + err2 = send_ws_data_for_xterm(ws, "iov", base64.StdEncoding.EncodeToString(buf[:n])) if err2 != nil { c.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err2.Error()) break @@ -187,7 +188,14 @@ ws_recv_loop: if tty != nil { var i int for i, _ = range ev.Data { - in.Write([]byte(ev.Data[i])) + //in.Write([]byte(ev.Data[i])) + var bytes []byte + bytes, err = base64.StdEncoding.DecodeString(ev.Data[i]) + if err != nil { + c.log.Write(pty.Id, LOG_WARN, "[%s] Invalid iov data received - %s", req.RemoteAddr, ev.Data[i]) + } else { + in.Write(bytes) + } } } diff --git a/server-pty.go b/server-pty.go index 7fa21d0..6837aea 100644 --- a/server-pty.go +++ b/server-pty.go @@ -1,5 +1,6 @@ package hodu +import "encoding/base64" import "encoding/json" import "errors" import "fmt" @@ -102,7 +103,7 @@ func (pty *server_pty_ws) ServeWebsocket(ws *websocket.Conn) (int, error) { n, err = out.Read(buf[:]) if n > 0 { var err2 error - err2 = send_ws_data_for_xterm(ws, "iov", string(buf[:n])) + err2 = send_ws_data_for_xterm(ws, "iov", base64.StdEncoding.EncodeToString(buf[:n])) if err2 != nil { s.log.Write(pty.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err2.Error()) break @@ -195,7 +196,14 @@ ws_recv_loop: if tty != nil { var i int for i, _ = range ev.Data { - in.Write([]byte(ev.Data[i])) + //in.Write([]byte(ev.Data[i])) + var bytes []byte + bytes, err = base64.StdEncoding.DecodeString(ev.Data[i]) + if err != nil { + s.log.Write(pty.Id, LOG_WARN, "[%s] Invalid pty iov data received - %s", req.RemoteAddr, ev.Data[i]) + } else { + in.Write(bytes) + } } } @@ -310,8 +318,15 @@ ws_recv_loop: case "iov": var i int for i, _ = range ev.Data { - cts.WriteRpty(ws, []byte(ev.Data[i])) - // ignore error for now + //cts.WriteRpty(ws, []byte(ev.Data[i])) + var bytes []byte + bytes, err = base64.StdEncoding.DecodeString(ev.Data[i]) + if err != nil { + s.log.Write(rpty.Id, LOG_WARN, "[%s] Invalid rpty iov data received - %s", req.RemoteAddr, ev.Data[i]) + } else { + cts.WriteRpty(ws, bytes) + // ignore error for now + } } case "size": diff --git a/server-pxy.go b/server-pxy.go index bc2d544..88ace13 100644 --- a/server-pxy.go +++ b/server-pxy.go @@ -3,6 +3,7 @@ package hodu import "bufio" import "context" import "crypto/tls" +import "encoding/base64" import "encoding/json" import "errors" import "fmt" @@ -704,7 +705,7 @@ func (pxy *server_pxy_ssh_ws) ServeWebsocket(ws *websocket.Conn) (int, error) { n, err = out.Read(buf[:]) if n > 0 { var err2 error - err2 = send_ws_data_for_xterm(ws, "iov", string(buf[:n])) + err2 = send_ws_data_for_xterm(ws, "iov", base64.StdEncoding.EncodeToString(buf[:n])) if err2 != nil { s.log.Write(pxy.Id, LOG_ERROR, "[%s] Failed to send to websocket - %s", req.RemoteAddr, err2.Error()) break @@ -777,7 +778,14 @@ ws_recv_loop: if sess != nil { var i int for i, _ = range ev.Data { - in.Write([]byte(ev.Data[i])) + //in.Write([]byte(ev.Data[i])) + var bytes []byte + bytes, err = base64.StdEncoding.DecodeString(ev.Data[i]) + if err != nil { + s.log.Write(pxy.Id, LOG_WARN, "[%s] Invalid pxy iov data received - %s", req.RemoteAddr, ev.Data[i]) + } else { + in.Write(bytes) + } } } diff --git a/xterm.html b/xterm.html index 5c65766..2dd8915 100644 --- a/xterm.html +++ b/xterm.html @@ -94,6 +94,22 @@ const xt_mode = '{{ .Mode }}'; const conn_id = '{{ .ConnId }}'; const route_id = '{{ .RouteId }}'; +function utf8ToBase64(str) { + const encoder = new TextEncoder(); + const data = encoder.encode(str); + const binaryString = String.fromCharCode.apply(null, data); + return btoa(binaryString); +} + +function base64ToBinary(b64) { + const binaryString = atob(b64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; +} + window.onload = function(event) { const terminal_container = document.getElementById('terminal-container'); const terminal_target = document.getElementById('terminal-target'); @@ -235,9 +251,9 @@ window.onload = function(event) { let event_text; event_text = (typeof event.data === 'string')? event.data: text_decoder.decode(new Uint8Array(event.data)); const msg = JSON.parse(event_text); - if (msg.type == "iov") { - for (const data of msg.data) term.write(data); - } else if (msg.type == "status") { + if (msg.type == 'iov') { + for (const data of msg.data) term.write(base64ToBinary(data)); + } else if (msg.type == 'status') { if (msg.data.length >= 1) { if (msg.data[0] == 'opened') { set_terminal_status('Connected', ''); @@ -248,7 +264,7 @@ window.onload = function(event) { // socket.onclose() will be executed anyway } } - } else if (msg.type == "error") { + } else if (msg.type == 'error') { toggle_login_form(true); window.onresize = adjust_terminal_size_unconnected; set_terminal_status(null, msg.data.join(' ')) @@ -275,7 +291,7 @@ window.onload = function(event) { term.onData(function(data) { if (socket.readyState == 1) // if open - socket.send(JSON.stringify({ type: 'iov', data: [data] })); + socket.send(JSON.stringify({ type: 'iov', data: [utf8ToBase64(data)] })); }); window.onresize = adjust_terminal_size_connected;