use base64::{engine::general_purpose::STANDARD, Engine as _};
pub fn escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 || c == '\u{7f}' => {
out.push_str("\\x");
out.push(hex((c as u32 >> 4) & 0xf));
out.push(hex(c as u32 & 0xf));
}
c => out.push(c),
}
}
out
}
fn hex(n: u32) -> char {
char::from_digit(n, 16).unwrap_or('0')
}
pub fn b64(s: &str) -> String {
STANDARD.encode(s.as_bytes())
}
pub fn unb64(s: &str) -> Option<String> {
STANDARD
.decode(s.as_bytes())
.ok()
.map(|b| String::from_utf8_lossy(&b).into_owned())
}
const B64_PREFIX: &str = "b64:";
pub fn needs_b64(v: &str) -> bool {
v != v.trim()
|| v.starts_with(B64_PREFIX)
|| v.bytes().any(|b| b < 0x20 || b == 0x7f || b == b';' || b == b'#')
}
pub fn encode_value(v: &str) -> String {
if needs_b64(v) {
format!("{B64_PREFIX}{}", b64(v))
} else {
v.to_string()
}
}
pub fn decode_value(v: &str) -> String {
match v.strip_prefix(B64_PREFIX) {
Some(rest) => unb64(rest).unwrap_or_else(|| v.to_string()),
None => v.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_is_display_only_and_kills_framing_bytes() {
assert_eq!(escape("plain/path"), "plain/path");
assert_eq!(escape("a\nb\tc"), "a\\nb\\tc");
assert_eq!(escape("x\x01y\x7f"), "x\\x01y\\x7f");
assert_eq!(escape("a;b#c"), "a;b#c");
for bad in ['\n', '\t', '\r'] {
assert!(!escape("a\nb\tc\rd").contains(bad));
}
}
#[test]
fn base64_round_trips_any_string() {
for s in ["", "/usr/bin/curl", "café/π", "a\nb\t;#\\x", " sp ace "] {
assert_eq!(unb64(&b64(s)).as_deref(), Some(s));
}
}
#[test]
fn safe_values_stay_plain() {
for s in ["/opt/app/bin", "/home/u/My Documents/x", "a=b", "/p/π"] {
assert_eq!(encode_value(s), s, "{s:?} should stay plain");
assert_eq!(decode_value(&encode_value(s)), s);
}
}
#[test]
fn unsafe_values_go_base64_and_round_trip() {
for s in [
"a;b", "a#b", "x\ny", "x\ty", " leading", "trailing ", "b64:literal", "ctrl\x01x",
] {
let enc = encode_value(s);
assert!(enc.starts_with(B64_PREFIX), "{s:?} -> {enc:?}");
for bad in ['\n', '\t', '\r', ';', '#'] {
assert!(!enc.contains(bad), "{enc:?} still has {bad:?}");
}
assert_eq!(decode_value(&enc), s, "round-trip failed for {s:?}");
}
}
}