// Copyright 2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Escape characters that may have special meaning in a shell. #![doc(html_root_url="https://docs.rs/shell-escape/0.1")] use std::borrow::Cow; use std::env; /// Escape characters that may have special meaning in a shell. pub fn escape(s: Cow) -> Cow { if cfg!(unix) || env::var("MSYSTEM").is_ok() { unix::escape(s) } else { windows::escape(s) } } /// Windows-specific escaping. pub mod windows { use std::borrow::Cow; use std::iter::repeat; /// Escape for the windows cmd.exe shell. /// /// See [here][msdn] for more information. /// /// [msdn]: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx pub fn escape(s: Cow) -> Cow { let mut needs_escape = s.is_empty(); for ch in s.chars() { match ch { '"' | '\t' | '\n' | ' ' => needs_escape = true, _ => {} } } if !needs_escape { return s } let mut es = String::with_capacity(s.len()); es.push('"'); let mut chars = s.chars().peekable(); loop { let mut nslashes = 0; while let Some(&'\\') = chars.peek() { chars.next(); nslashes += 1; } match chars.next() { Some('"') => { es.extend(repeat('\\').take(nslashes * 2 + 1)); es.push('"'); } Some(c) => { es.extend(repeat('\\').take(nslashes)); es.push(c); } None => { es.extend(repeat('\\').take(nslashes * 2)); break; } } } es.push('"'); es.into() } #[test] fn test_escape() { assert_eq!(escape("--aaa=bbb-ccc".into()), "--aaa=bbb-ccc"); assert_eq!(escape("linker=gcc -L/foo -Wl,bar".into()), r#""linker=gcc -L/foo -Wl,bar""#); assert_eq!(escape(r#"--features="default""#.into()), r#""--features=\"default\"""#); assert_eq!(escape(r#"\path\to\my documents\"#.into()), r#""\path\to\my documents\\""#); assert_eq!(escape("".into()), r#""""#); } } /// Unix-specific escaping. pub mod unix { use std::borrow::Cow; fn non_whitelisted(ch: char) -> bool { match ch { 'a'...'z' | 'A'...'Z' | '0'...'9' | '-' | '_' | '=' | '/' | ',' | '.' | '+' => false, _ => true, } } /// Escape characters that may have special meaning in a shell, including spaces. pub fn escape(s: Cow) -> Cow { if !s.is_empty() && !s.contains(non_whitelisted) { return s; } let mut es = String::with_capacity(s.len() + 2); es.push('\''); for ch in s.chars() { match ch { '\'' | '!' => { es.push_str("'\\"); es.push(ch); es.push('\''); } _ => es.push(ch), } } es.push('\''); es.into() } #[test] fn test_escape() { assert_eq!( escape("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+".into()), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=/,.+" ); assert_eq!(escape("--aaa=bbb-ccc".into()), "--aaa=bbb-ccc"); assert_eq!(escape("linker=gcc -L/foo -Wl,bar".into()), r#"'linker=gcc -L/foo -Wl,bar'"#); assert_eq!(escape(r#"--features="default""#.into()), r#"'--features="default"'"#); assert_eq!(escape(r#"'!\$`\\\n "#.into()), r#"''\'''\!'\$`\\\n '"#); assert_eq!(escape("".into()), r#"''"#); } }