diff --git a/package.json b/package.json index 7253204e9..66db33ea3 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@tanstack/react-table": "^8.10.3", "@types/semver": "^7.5.6", "autobind-decorator": "^2.4.0", + "base64-js": "^1.5.1", "classnames": "^2.3.1", "dayjs": "^1.11.3", "dompurify": "^3.0.2", diff --git a/src/model/model.ts b/src/model/model.ts index 4ba81bd19..ce2d49a88 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -9,6 +9,8 @@ import { boundMethod } from "autobind-decorator"; import { debounce } from "throttle-debounce"; import { handleJsonFetchResponse, + base64ToString, + stringToBase64, base64ToArray, genMergeData, genMergeDataMap, @@ -336,7 +338,7 @@ class Cmd { type: "feinput", ck: this.screenId + "/" + this.lineId, remote: this.remote, - inputdata64: btoa(data), + inputdata64: stringToBase64(data), }; GlobalModel.sendInputPacket(inputPacket); } @@ -2866,7 +2868,7 @@ class RemotesModalModel { let inputPacket: RemoteInputPacketType = { type: "remoteinput", remoteid: remoteId, - inputdata64: btoa(event.key), + inputdata64: stringToBase64(event.key), }; GlobalModel.sendInputPacket(inputPacket); } @@ -3045,7 +3047,7 @@ class RemotesModel { let inputPacket: RemoteInputPacketType = { type: "remoteinput", remoteid: remoteId, - inputdata64: btoa(event.key), + inputdata64: stringToBase64(event.key), }; GlobalModel.sendInputPacket(inputPacket); } @@ -4201,7 +4203,7 @@ class Model { return resp.text() as any; } contentType = resp.headers.get("Content-Type"); - fileInfo = JSON.parse(atob(resp.headers.get("X-FileInfo"))); + fileInfo = JSON.parse(base64ToString(resp.headers.get("X-FileInfo"))); return resp.blob(); }) .then((blobOrText: any) => { diff --git a/src/util/util.ts b/src/util/util.ts index b5d9ee62f..b4b80ef19 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -6,6 +6,7 @@ import { sprintf } from "sprintf-js"; import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import type { RemoteType, CommandRtnType } from "../types/types"; +import base64 from "base64-js"; type OV = mobx.IObservableValue; @@ -70,6 +71,16 @@ function handleJsonFetchResponse(url: URL, resp: any): Promise { return rtnData; } +function base64ToString(b64: string): string { + let stringBytes = base64.toByteArray(b64) + return new TextDecoder().decode(stringBytes) +} + +function stringToBase64(input: string): string { + let stringBytes = new TextEncoder().encode(input) + return base64.fromByteArray(stringBytes) +} + function base64ToArray(b64: string): Uint8Array { let rawStr = atob(b64); let rtnArr = new Uint8Array(new ArrayBuffer(rawStr.length)); @@ -229,26 +240,6 @@ function genMergeDataMap, DataType extends ID return rtn; } -function parseEnv0(envStr64: string): Map { - let envStr = atob(envStr64); - let parts = envStr.split("\x00"); - let rtn: Map = new Map(); - for (let i = 0; i < parts.length; i++) { - let part = parts[i]; - if (part == "") { - continue; - } - let eqIdx = part.indexOf("="); - if (eqIdx == -1) { - continue; - } - let varName = part.substr(0, eqIdx); - let varVal = part.substr(eqIdx + 1); - rtn.set(varName, varVal); - } - return rtn; -} - function boundInt(ival: number, minVal: number, maxVal: number): number { if (ival < minVal) { return minVal; @@ -414,11 +405,12 @@ function getRemoteName(remote: RemoteType): string { export { handleJsonFetchResponse, + base64ToString, + stringToBase64, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, - parseEnv0, boundInt, isModKeyPress, incObs, diff --git a/waveshell/pkg/packet/packet.go b/waveshell/pkg/packet/packet.go index c2dec0500..f75030558 100644 --- a/waveshell/pkg/packet/packet.go +++ b/waveshell/pkg/packet/packet.go @@ -927,14 +927,6 @@ func ParseJsonPacket(jsonBuf []byte) (PacketType, error) { return pk, nil } -func sanitizeBytes(buf []byte) { - for idx, b := range buf { - if b >= 127 || (b < 32 && b != 10 && b != 13) { - buf[idx] = '?' - } - } -} - type SendError struct { IsWriteError bool // fatal IsMarshalError bool // not fatal @@ -970,7 +962,6 @@ func MarshalPacket(packet PacketType) ([]byte, error) { outBuf.Write(jsonBytes) outBuf.WriteByte('\n') outBytes := outBuf.Bytes() - sanitizeBytes(outBytes) return outBytes, nil } diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index 145920962..786597db0 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -1126,6 +1126,9 @@ func addScVarsToState(state *packet.ShellState) *packet.ShellState { envMap := shexec.DeclMapFromState(&rtn) envMap["PROMPT"] = &shexec.DeclareDeclType{Name: "PROMPT", Value: "1", Args: "x"} envMap["PROMPT_VERSION"] = &shexec.DeclareDeclType{Name: "PROMPT_VERSION", Value: scbase.WaveVersion, Args: "x"} + if _, exists := envMap["LANG"]; !exists { + envMap["LANG"] = &shexec.DeclareDeclType{Name: "LANG", Value: scbase.DetermineLang(), Args: "x"} + } rtn.ShellVars = shexec.SerializeDeclMap(envMap) return &rtn } diff --git a/wavesrv/pkg/scbase/scbase.go b/wavesrv/pkg/scbase/scbase.go index 2b064cdff..84643ef72 100644 --- a/wavesrv/pkg/scbase/scbase.go +++ b/wavesrv/pkg/scbase/scbase.go @@ -377,3 +377,30 @@ func MacOSRelease() string { }) return osRelease } + +var osLangOnce = &sync.Once{} +var osLang string + +func determineLang() string { + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + if runtime.GOOS == "darwin" { + out, err := exec.CommandContext(ctx, "defaults", "read", "-g", "AppleLocale").CombinedOutput() + if err != nil { + log.Printf("error executing 'defaults read -g AppleLocale': %v\n", err) + return "" + } + return strings.TrimSpace(string(out)) + ".UTF-8" + } else { + // this is specifically to get the wavesrv LANG so waveshell + // on a remote uses the same LANG + return os.Getenv("LANG") + } +} + +func DetermineLang() string { + osLangOnce.Do(func() { + osLang = determineLang() + }) + return osLang +}