bootc_internal_utils/
path.rs1use std::fmt::Display;
2use std::os::unix::ffi::OsStrExt;
3use std::path::Path;
4
5#[derive(Debug)]
7pub struct PathQuotedDisplay<'a> {
8 path: &'a Path,
9}
10
11fn is_shellsafe(c: char) -> bool {
19 matches!(c, '/' | '.' | '-' | '_' | ',' | '=' | ':') || c.is_alphanumeric()
20}
21
22impl<'a> Display for PathQuotedDisplay<'a> {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 if let Some(s) = self.path.to_str()
25 && !s.is_empty()
26 && s.chars().all(is_shellsafe)
27 {
28 return f.write_str(s);
29 }
30 if let Ok(r) = shlex::bytes::try_quote(self.path.as_os_str().as_bytes()) {
31 let s = String::from_utf8_lossy(&r);
32 return f.write_str(&s);
33 }
34 Err(std::fmt::Error)
36 }
37}
38
39impl<'a> PathQuotedDisplay<'a> {
40 pub fn new<P: AsRef<Path>>(path: &'a P) -> PathQuotedDisplay<'a> {
44 PathQuotedDisplay {
45 path: path.as_ref(),
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use std::ffi::OsStr;
53
54 use super::*;
55
56 #[test]
57 fn test_unquoted() {
58 for v in [
59 "foo",
60 "/foo/bar",
61 "/foo/bar/../baz",
62 "/foo9/bar10",
63 "--foo",
64 "--virtiofs=/foo,/bar",
65 "/foo:/bar",
66 "--label=type=unconfined_t",
67 ] {
68 assert_eq!(v, format!("{}", PathQuotedDisplay::new(&v)));
69 }
70 }
71
72 #[test]
73 fn test_bash_metachars() {
74 let bash_metachars = "|&;()<>";
76 for c in bash_metachars.chars() {
77 assert!(!is_shellsafe(c));
78 }
79 }
80
81 #[test]
82 fn test_quoted() {
83 let cases = [
84 ("", "''"),
85 (" ", "' '"),
86 ("/some/path with spaces/", "'/some/path with spaces/'"),
87 ("/foo/!/bar&", "'/foo/!/bar&'"),
88 (r#"/path/"withquotes'"#, r#""/path/\"withquotes'""#),
89 ];
90 for (v, quoted) in cases {
91 let q = PathQuotedDisplay::new(&v).to_string();
92 assert_eq!(quoted, q.as_str());
93 let token = shlex::split(&q).unwrap();
95 assert_eq!(1, token.len());
96 assert_eq!(v, token[0]);
97 }
98 }
99
100 #[test]
101 fn test_nonutf8() {
102 let p = Path::new(OsStr::from_bytes(b"/foo/somenonutf8\xEE/bar"));
103 assert!(p.to_str().is_none());
104 let q = PathQuotedDisplay::new(&p).to_string();
105 assert_eq!(q, r#"'/foo/somenonutf8�/bar'"#);
106 }
107}