1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#![doc = include_str!("../README.md")]

use std::ffi::{OsStr, OsString};

mod ascii;
mod bash;
mod sh;

pub use bash::Bash;
pub use sh::Sh;

/// Extension trait for shell quoting byte slices (e.g. `&[u8]`, `&str`) into
/// byte container types like [`Vec<u8>`], [`String`] (and [`OsString`] on
/// Unix).
pub trait QuoteExt {
    fn push_quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(&mut self, q: Q, s: &S);
    fn quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(q: Q, s: &S) -> Self;
}

impl QuoteExt for Vec<u8> {
    fn push_quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(&mut self, _q: Q, s: &S) {
        Q::quote_into(s, self);
    }

    fn quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(_q: Q, s: &S) -> Self {
        Q::quote(s)
    }
}

#[cfg(unix)]
impl QuoteExt for OsString {
    fn push_quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(&mut self, _q: Q, s: &S) {
        use std::os::unix::ffi::OsStrExt;
        let quoted = Q::quote(s);
        self.push(OsStr::from_bytes(&quoted))
    }

    fn quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(_q: Q, s: &S) -> Self {
        use std::os::unix::ffi::OsStringExt;
        let quoted = Q::quote(s);
        OsString::from_vec(quoted)
    }
}

impl QuoteExt for String {
    fn push_quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(&mut self, _q: Q, s: &S) {
        let quoted = Q::quote(s);
        // SAFETY: `quoted` is valid UTF-8 (ASCII, in truth) because it was
        // generated by a `quote` implementation from this crate –
        // enforced because `Quoter` is sealed.
        let quoted = unsafe { std::str::from_utf8_unchecked(&quoted) };
        self.push_str(quoted);
    }

    fn quoted<Q: Quoter, S: ?Sized + AsRef<[u8]>>(_q: Q, s: &S) -> Self {
        let quoted = Q::quote(s);
        // SAFETY: `quoted` is valid UTF-8 (ASCII, in truth) because it was
        // generated by a `quote` implementation from this crate –
        // enforced because `Quoter` is sealed.
        unsafe { String::from_utf8_unchecked(quoted) }
    }
}

pub(crate) mod quoter {
    pub trait QuoterSealed {
        /// Quote/escape a string of bytes into a new `Vec<u8>`.
        fn quote<S: ?Sized + AsRef<[u8]>>(s: &S) -> Vec<u8>;

        /// Quote/escape a string of bytes into an existing `Vec<u8>`.
        fn quote_into<S: ?Sized + AsRef<[u8]>>(s: &S, sout: &mut Vec<u8>);
    }
}

/// A trait for quoting/escaping a string of bytes into a shell-safe form.
///
/// **This trait is sealed** and cannot be implemented outside of this crate.
/// This is because the [`QuoteExt`] implementation for [`String`] must be sure
/// that quoted bytes are valid UTF-8, and that is only possible if the
/// implementation is known and tested in advance.
pub trait Quoter: quoter::QuoterSealed {}