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(key:"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
136