use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
use crate::ascii::Char;
pub fn escape<T: Into<OsString>>(s: T) -> Vec<u8> {
let sin = s.into().into_vec();
if let Some(esc) = escape_prepare(&sin) {
let size: usize = esc.iter().map(escape_size).sum();
let mut sout = Vec::with_capacity(size + 2);
escape_chars(esc, &mut sout); sout
} else {
sin
}
}
pub fn quote<T: Into<OsString>>(s: T) -> OsString {
OsString::from_vec(escape(s))
}
pub fn escape_into<T: Into<OsString>>(s: T, sout: &mut Vec<u8>) {
let sin = s.into().into_vec();
if let Some(esc) = escape_prepare(&sin) {
let size: usize = esc.iter().map(escape_size).sum();
sout.reserve(size + 2);
escape_chars(esc, sout); } else {
sout.extend(sin);
}
}
fn escape_prepare(sin: &[u8]) -> Option<Vec<Char>> {
let esc: Vec<_> = sin.iter().map(Char::from).collect();
if esc.is_empty() {
Some(esc)
} else if esc.iter().all(Char::is_inert) {
None
} else {
Some(esc)
}
}
fn escape_chars(esc: Vec<Char>, sout: &mut Vec<u8>) {
sout.extend(b"'");
for mode in esc {
use Char::*;
match mode {
Bell => sout.extend(b"\\a"),
Backspace => sout.extend(b"\\b"),
Escape => sout.extend(b"\\033"),
FormFeed => sout.extend(b"\\f"),
NewLine => sout.extend(b"\\n"),
CarriageReturn => sout.extend(b"\\r"),
HorizontalTab => sout.extend(b"\\t"),
VerticalTab => sout.extend(b"\\v"),
Control(ch) => sout.extend(format!("\\{:03o}", ch).bytes()),
Backslash => sout.extend(b"\\\\"),
SingleQuote => sout.extend(b"\\047"),
DoubleQuote => sout.extend(b"\""),
Delete => sout.push(0x7F),
PrintableInert(ch) => sout.push(ch),
Printable(ch) => sout.push(ch),
Extended(ch) => sout.push(ch),
}
}
sout.push(b'\'');
}
fn escape_size(char: &Char) -> usize {
use Char::*;
match char {
Bell => 2,
Backspace => 2,
Escape => 4,
FormFeed => 2,
NewLine => 2,
CarriageReturn => 2,
HorizontalTab => 2,
VerticalTab => 2,
Control(_) => 4,
Backslash => 2,
SingleQuote => 4,
DoubleQuote => 1,
Delete => 4,
PrintableInert(_) => 1,
Printable(_) => 1,
Extended(_) => 4,
}
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use std::os::unix::prelude::OsStringExt;
use crate::find_bins;
use super::{escape, escape_into, quote};
#[test]
fn test_lowercase_ascii() {
assert_eq!(
escape("abcdefghijklmnopqrstuvwxyz"),
b"abcdefghijklmnopqrstuvwxyz"
);
}
#[test]
fn test_uppercase_ascii() {
assert_eq!(
escape("ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
);
}
#[test]
fn test_numbers() {
assert_eq!(escape("0123456789"), b"0123456789");
}
#[test]
fn test_punctuation() {
assert_eq!(escape("-_=/,.+"), b"'-_=/,.+'");
}
#[test]
fn test_empty_string() {
assert_eq!(escape(""), b"''");
}
#[test]
fn test_basic_escapes() {
assert_eq!(escape(r#"woo'wah""#), br#"'woo\047wah"'"#);
}
#[test]
#[allow(non_snake_case)]
fn test_control_characters() {
assert_eq!(escape(&"\x07"), b"'\\a'");
assert_eq!(escape(&"\x00"), b"'\\000'");
assert_eq!(escape(&"\x06"), b"'\\006'");
assert_eq!(escape(&"\x7F"), b"'\x7F'");
assert_eq!(escape(&"\x1B"), b"'\\033'");
}
#[test]
fn test_escape_into_plain() {
let mut buffer = Vec::new();
escape_into("hello", &mut buffer);
assert_eq!(buffer, b"hello");
}
#[test]
fn test_escape_into_with_escapes() {
let mut buffer = Vec::new();
escape_into("-_=/,.+", &mut buffer);
assert_eq!(buffer, b"'-_=/,.+'");
}
#[test]
fn test_roundtrip() {
use std::process::Command;
let string: OsString = OsString::from_vec((u8::MIN..=u8::MAX).collect());
let mut script = b"echo ".to_vec();
escape_into(&string, &mut script);
let script = OsString::from_vec(script);
for bin in find_bins("sh") {
let output = Command::new(bin).arg("-c").arg(&script).output().unwrap();
let mut result = output.stdout;
result.resize(result.len() - 1, 0); let result = OsString::from_vec(result);
assert_eq!(result, string);
}
}
#[test]
fn test_quote() {
assert_eq!(
quote("abcdefghijklmnopqrstuvwxyz"),
OsString::from("abcdefghijklmnopqrstuvwxyz"),
);
}
}