shell_escape/
lib.rs

1// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10//! Escape characters that may have special meaning in a shell.
11#![doc(html_root_url="https://docs.rs/shell-escape/0.1")]
12
13use std::borrow::Cow;
14use std::env;
15
16/// Escape characters that may have special meaning in a shell.
17pub fn escape(s: Cow<str>) -> Cow<str> {
18    if cfg!(unix) || env::var("MSYSTEM").is_ok() {
19        unix::escape(s)
20    } else {
21        windows::escape(s)
22    }
23}
24
25/// Windows-specific escaping.
26pub mod windows {
27    use std::borrow::Cow;
28    use std::iter::repeat;
29
30    /// Escape for the windows cmd.exe shell.
31    ///
32    /// See [here][msdn] for more information.
33    ///
34    /// [msdn]: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
35    pub fn escape(s: Cow<str>) -> Cow<str> {
36        let mut needs_escape = s.is_empty();
37        for ch in s.chars() {
38            match ch {
39                '"' | '\t' | '\n' | ' ' => needs_escape = true,
40                _ => {}
41            }
42        }
43        if !needs_escape {
44            return s
45        }
46        let mut es = String::with_capacity(s.len());
47        es.push('"');
48        let mut chars = s.chars().peekable();
49        loop {
50            let mut nslashes = 0;
51            while let Some(&'\\') = chars.peek() {
52                chars.next();
53                nslashes += 1;
54            }
55
56            match chars.next() {
57                Some('"') => {
58                    es.extend(repeat('\\').take(nslashes * 2 + 1));
59                    es.push('"');
60                }
61                Some(c) => {
62                    es.extend(repeat('\\').take(nslashes));
63                    es.push(c);
64                }
65                None => {
66                    es.extend(repeat('\\').take(nslashes * 2));
67                    break;
68                }
69            }
70
71        }
72        es.push('"');
73        es.into()
74    }
75
76    #[test]
77    fn test_escape() {
78        assert_eq!(escape("--aaa=bbb-ccc".into()), "--aaa=bbb-ccc");
79        assert_eq!(escape("linker=gcc -L/foo -Wl,bar".into()),
80        r#""linker=gcc -L/foo -Wl,bar""#);
81        assert_eq!(escape(r#"--features="default""#.into()),
82        r#""--features=\"default\"""#);
83        assert_eq!(escape(r#"\path\to\my documents\"#.into()),
84        r#""\path\to\my documents\\""#);
85        assert_eq!(escape("".into()), r#""""#);
86    }
87}
88
89/// Unix-specific escaping.
90pub mod unix {
91    use std::borrow::Cow;
92
93    fn non_whitelisted(ch: char) -> bool {
94        match ch {
95            'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' | '=' | '/' | ',' | '.' | '+' => false,
96            _ => true,
97        }
98    }
99
100    /// Escape characters that may have special meaning in a shell, including spaces.
101    pub fn escape(s: Cow<str>) -> Cow<str> {
102        if !s.is_empty() && !s.contains(non_whitelisted) {
103            return s;
104        }
105
106        let mut es = String::with_capacity(s.len() + 2);
107        es.push('\'');
108        for ch in s.chars() {
109            match ch {
110                '\'' | '!' => {
111                    es.push_str("'\\");
112                    es.push(ch);
113                    es.push('\'');
114                }
115                _ => es.push(ch),
116            }
117        }
118        es.push('\'');
119        es.into()
120    }
121
122    #[test]
123    fn test_escape() {
124        assert_eq!(
125            escape("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+".into()),
126            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+"
127        );
128        assert_eq!(escape("--aaa=bbb-ccc".into()), "--aaa=bbb-ccc");
129        assert_eq!(escape("linker=gcc -L/foo -Wl,bar".into()), r#"'linker=gcc -L/foo -Wl,bar'"#);
130        assert_eq!(escape(r#"--features="default""#.into()), r#"'--features="default"'"#);
131        assert_eq!(escape(r#"'!\$`\\\n "#.into()), r#"''\'''\!'\$`\\\n '"#);
132        assert_eq!(escape("".into()), r#"''"#);
133    }
134}
135