about summary refs log tree commit diff
path: root/src/libstd
diff options
context:
space:
mode:
authorVadim Chugunov <vadimcn@gmail.com>2014-09-14 01:33:21 -0700
committerVadim Chugunov <vadimcn@gmail.com>2014-09-17 00:56:13 -0700
commitffa8b2917c5d85222e7308ae577e590f6ef04d1e (patch)
tree6bde3721045e02fed3ad2a91990c2fe1ab7a3abe /src/libstd
parent88cb454b91b16fdf8395bc4859b65aff8303acb5 (diff)
downloadrust-ffa8b2917c5d85222e7308ae577e590f6ef04d1e.tar.gz
rust-ffa8b2917c5d85222e7308ae577e590f6ef04d1e.zip
Case-insensitive environment keys.
Diffstat (limited to 'src/libstd')
-rw-r--r--src/libstd/io/process.rs82
1 files changed, 75 insertions, 7 deletions
diff --git a/src/libstd/io/process.rs b/src/libstd/io/process.rs
index 1225dcf1f4a..2ebafc30718 100644
--- a/src/libstd/io/process.rs
+++ b/src/libstd/io/process.rs
@@ -24,6 +24,9 @@ use rt::rtio::{RtioProcess, ProcessConfig, IoFactory, LocalIo};
 use rt::rtio;
 use c_str::CString;
 use collections::HashMap;
+use std::hash::Hash;
+use std::hash::sip::SipState;
+use clone::Clone;
 
 /// Signal a process to exit, without forcibly killing it. Corresponds to
 /// SIGTERM on unix platforms.
@@ -78,8 +81,56 @@ pub struct Process {
     pub extra_io: Vec<Option<io::PipeStream>>,
 }
 
+/// A representation of environment variable name
+/// It compares case-insensitive on Windows and case-sensitive everywhere else.
+#[cfg(not(windows))]
+#[deriving(PartialEq, Eq, Hash, Clone, Show)]
+struct EnvKey(CString);
+
+#[doc(hidden)]
+#[cfg(windows)]
+#[deriving(Eq, Clone, Show)]
+struct EnvKey(CString);
+
+#[cfg(windows)]
+impl Hash for EnvKey {
+    fn hash(&self, state: &mut SipState) {
+        let &EnvKey(ref x) = self;
+        match x.as_str() {
+            Some(s) => for ch in s.chars() {
+                (ch as u8 as char).to_lowercase().hash(state);
+            },
+            None => x.hash(state)
+        }
+    }
+}
+
+#[cfg(windows)]
+impl PartialEq for EnvKey {
+    fn eq(&self, other: &EnvKey) -> bool {
+        let &EnvKey(ref x) = self;
+        let &EnvKey(ref y) = other;
+        match (x.as_str(), y.as_str()) {
+            (Some(xs), Some(ys)) => {
+                if xs.len() != ys.len() {
+                    return false
+                } else {
+                    for (xch, ych) in xs.chars().zip(ys.chars()) {
+                        if xch.to_lowercase() != ych.to_lowercase() {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            },
+            // If either is not a valid utf8 string, just compare them byte-wise
+            _ => return x.eq(y)
+        }
+    }
+}
+
 /// A HashMap representation of environment variables.
-pub type EnvMap = HashMap<CString, CString>;
+pub type EnvMap = HashMap<EnvKey, CString>;
 
 /// The `Command` type acts as a process builder, providing fine-grained control
 /// over how a new process should be spawned. A default configuration can be
@@ -161,14 +212,14 @@ impl Command {
         self
     }
     // Get a mutable borrow of the environment variable map for this `Command`.
-    fn get_env_map<'a>(&'a mut self) -> &'a mut EnvMap {
+    fn get_env_map<'a>(&'a mut self) -> &'a mut  EnvMap {
         match self.env {
             Some(ref mut map) => map,
             None => {
                 // if the env is currently just inheriting from the parent's,
                 // materialize the parent's env into a hashtable.
                 self.env = Some(os::env_as_bytes().into_iter()
-                                   .map(|(k, v)| (k.as_slice().to_c_str(),
+                                   .map(|(k, v)| (EnvKey(k.as_slice().to_c_str()),
                                                   v.as_slice().to_c_str()))
                                    .collect());
                 self.env.as_mut().unwrap()
@@ -177,15 +228,18 @@ impl Command {
     }
 
     /// Inserts or updates an environment variable mapping.
+    ///
+    /// Note that environment variable names are case-insensitive (but case-preserving) on Windows,
+    /// and case-sensitive on all other platforms.
     pub fn env<'a, T: ToCStr, U: ToCStr>(&'a mut self, key: T, val: U)
                                          -> &'a mut Command {
-        self.get_env_map().insert(key.to_c_str(), val.to_c_str());
+        self.get_env_map().insert(EnvKey(key.to_c_str()), val.to_c_str());
         self
     }
 
     /// Removes an environment variable mapping.
     pub fn env_remove<'a, T: ToCStr>(&'a mut self, key: T) -> &'a mut Command {
-        self.get_env_map().remove(&key.to_c_str());
+        self.get_env_map().remove(&EnvKey(key.to_c_str()));
         self
     }
 
@@ -195,7 +249,7 @@ impl Command {
     /// variable, the *rightmost* instance will determine the value.
     pub fn env_set_all<'a, T: ToCStr, U: ToCStr>(&'a mut self, env: &[(T,U)])
                                                  -> &'a mut Command {
-        self.env = Some(env.iter().map(|&(ref k, ref v)| (k.to_c_str(), v.to_c_str()))
+        self.env = Some(env.iter().map(|&(ref k, ref v)| (EnvKey(k.to_c_str()), v.to_c_str()))
                                   .collect());
         self
     }
@@ -273,7 +327,9 @@ impl Command {
             let env = match self.env {
                 None => None,
                 Some(ref env_map) =>
-                    Some(env_map.iter().collect::<Vec<_>>())
+                    Some(env_map.iter()
+                                .map(|(&EnvKey(ref key), val)| (key, val))
+                                .collect::<Vec<_>>())
             };
             let cfg = ProcessConfig {
                 program: &self.program,
@@ -1039,4 +1095,16 @@ mod tests {
         assert!(cmd.status().unwrap().success());
         assert!(fdes.inner_write("extra write\n".as_bytes()).is_ok());
     })
+
+    #[test]
+    #[cfg(windows)]
+    fn env_map_keys_ci() {
+        use super::EnvKey;
+        let mut cmd = Command::new("");
+        cmd.env("path", "foo");
+        cmd.env("Path", "bar");
+        let env = &cmd.env.unwrap();
+        let val = env.find(&EnvKey("PATH".to_c_str()));
+        assert!(val.unwrap() == &"bar".to_c_str());
+    }
 }