waveterm/pkg/util/envutil/envutil.go
Mike Sawka 01a26d59e6
Persistent Terminal Sessions (+ improvements and bug fixes) (#2806)
Lots of updates across all parts of the system to get this working. Big
changes to routing, streaming, connection management, etc.

* Persistent sessions behind a metadata flag for now
* New backlog queue in the router to prevent hanging
* Fix connection Close() issues that caused hangs when network was down
* Fix issue with random routeids (need to be generated fresh each time
the JWT is used and not fixed) so you can run multiple-wsh commands at
once
* Fix issue with domain sockets changing names across wave restarts
(added a symlink mechanism to resolve new names)
* ClientId caching in main server
* Quick reorder queue for input to prevent out of order delivery across
multiple hops
* Fix out-of-order event delivery in router (remove unnecessary go
routine creation)
* Environment testing and fix environment variables for remote jobs (get
from connserver, add to remote job starts)
* Add new ConnServerInit() remote method to call before marking
connection up
* TODO -- remote file transfer needs to be fixed to not create OOM
issues when transferring large files or directories
2026-01-28 13:30:48 -08:00

129 lines
2.8 KiB
Go

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package envutil
import (
"fmt"
"strings"
)
const MaxEnvSize = 1024 * 1024
// env format:
// KEY=VALUE\0
// keys cannot have '=' or '\0' in them
// values can have '=' but not '\0'
func EnvToMap(envStr string) map[string]string {
rtn := make(map[string]string)
envLines := strings.Split(envStr, "\x00")
for _, line := range envLines {
if len(line) == 0 {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
rtn[parts[0]] = parts[1]
}
}
return rtn
}
func MapToEnv(envMap map[string]string) string {
var sb strings.Builder
for key, val := range envMap {
sb.WriteString(key)
sb.WriteByte('=')
sb.WriteString(val)
sb.WriteByte('\x00')
}
return sb.String()
}
func GetEnv(envStr string, key string) string {
envMap := EnvToMap(envStr)
return envMap[key]
}
func SetEnv(envStr string, key string, val string) (string, error) {
if strings.ContainsAny(key, "=\x00") {
return "", fmt.Errorf("key cannot contain '=' or '\\x00'")
}
if strings.Contains(val, "\x00") {
return "", fmt.Errorf("value cannot contain '\\x00'")
}
if len(key)+len(val)+2+len(envStr) > MaxEnvSize {
return "", fmt.Errorf("env string too large (max %d bytes)", MaxEnvSize)
}
envMap := EnvToMap(envStr)
envMap[key] = val
rtnStr := MapToEnv(envMap)
return rtnStr, nil
}
func RmEnv(envStr string, key string) string {
envMap := EnvToMap(envStr)
delete(envMap, key)
return MapToEnv(envMap)
}
func SliceToEnv(env []string) string {
var sb strings.Builder
for _, envVar := range env {
if len(envVar) == 0 {
continue
}
sb.WriteString(envVar)
sb.WriteByte('\x00')
}
return sb.String()
}
func EnvToSlice(envStr string) []string {
envLines := strings.Split(envStr, "\x00")
result := make([]string, 0, len(envLines))
for _, line := range envLines {
if len(line) == 0 {
continue
}
result = append(result, line)
}
return result
}
func SliceToMap(env []string) map[string]string {
envMap := make(map[string]string)
for _, envVar := range env {
parts := strings.SplitN(envVar, "=", 2)
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
return envMap
}
func CopyAndAddToEnvMap(envMap map[string]string, key string, val string) map[string]string {
newMap := make(map[string]string, len(envMap)+1)
for k, v := range envMap {
newMap[k] = v
}
newMap[key] = val
return newMap
}
func PruneInitialEnv(envMap map[string]string) map[string]string {
pruned := make(map[string]string)
for key, value := range envMap {
if strings.HasPrefix(key, "WAVETERM_") || strings.HasPrefix(key, "BASH_FUNC_") {
continue
}
if key == "XDG_SESSION_ID" || key == "SHLVL" || key == "S_COLORS" ||
key == "SSH_CONNECTION" || key == "SSH_CLIENT" || key == "LESSOPEN" ||
key == "which_declare" {
continue
}
pruned[key] = value
}
return pruned
}