diff options
| author | Chris Denton <christophersdenton@gmail.com> | 2021-05-19 23:34:15 +0100 |
|---|---|---|
| committer | Chris Denton <christophersdenton@gmail.com> | 2021-05-19 23:34:15 +0100 |
| commit | 8345538fec7aa45fabeb7283707386ac7ed51f5b (patch) | |
| tree | c95a3c1091b3e21fdc74934d6be7fa75aabd9b5f | |
| parent | f94942d8421dc4b1da86d07069571ddb43127235 (diff) | |
| download | rust-8345538fec7aa45fabeb7283707386ac7ed51f5b.tar.gz rust-8345538fec7aa45fabeb7283707386ac7ed51f5b.zip | |
Windows `Command` environment variables are case-preserving
But comparing is case-insensitive.
| -rw-r--r-- | library/std/src/sys/windows/c.rs | 12 | ||||
| -rw-r--r-- | library/std/src/sys/windows/process.rs | 64 | ||||
| -rw-r--r-- | library/std/src/sys/windows/process/tests.rs | 61 |
3 files changed, 128 insertions, 9 deletions
diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs index e91c489361e..919415039ca 100644 --- a/library/std/src/sys/windows/c.rs +++ b/library/std/src/sys/windows/c.rs @@ -68,6 +68,10 @@ pub type ADDRESS_FAMILY = USHORT; pub const TRUE: BOOL = 1; pub const FALSE: BOOL = 0; +pub const CSTR_LESS_THAN: c_int = 1; +pub const CSTR_EQUAL: c_int = 2; +pub const CSTR_GREATER_THAN: c_int = 3; + pub const FILE_ATTRIBUTE_READONLY: DWORD = 0x1; pub const FILE_ATTRIBUTE_DIRECTORY: DWORD = 0x10; pub const FILE_ATTRIBUTE_REPARSE_POINT: DWORD = 0x400; @@ -1072,6 +1076,14 @@ extern "system" { pub fn ReleaseSRWLockShared(SRWLock: PSRWLOCK); pub fn TryAcquireSRWLockExclusive(SRWLock: PSRWLOCK) -> BOOLEAN; pub fn TryAcquireSRWLockShared(SRWLock: PSRWLOCK) -> BOOLEAN; + + pub fn CompareStringOrdinal( + lpString1: LPCWSTR, + cchCount1: c_int, + lpString2: LPCWSTR, + cchCount2: c_int, + bIgnoreCase: BOOL, + ) -> c_int; } // Functions that aren't available on every version of Windows that we support, diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs index 81dbea4a067..909017bcc0f 100644 --- a/library/std/src/sys/windows/process.rs +++ b/library/std/src/sys/windows/process.rs @@ -4,6 +4,7 @@ mod tests; use crate::borrow::Borrow; +use crate::cmp; use crate::collections::BTreeMap; use crate::convert::{TryFrom, TryInto}; use crate::env; @@ -34,32 +35,76 @@ use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS}; // Command //////////////////////////////////////////////////////////////////////////////// -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Clone, Debug, Eq)] #[doc(hidden)] -pub struct EnvKey(OsString); +pub struct EnvKey { + os_string: OsString, + // This stores a UTF-16 encoded string to workaround the mismatch between + // Rust's OsString (WTF-8) and the Windows API string type (UTF-16). + // Normally converting on every API call is acceptable but here + // `c::CompareStringOrdinal` will be called for every use of `==`. + utf16: Vec<u16>, +} + +// Windows environment variables preserve their case but comparisons use +// simplified case folding. So we call `CompareStringOrdinal` to get the OS to +// perform the comparison. +impl Ord for EnvKey { + fn cmp(&self, other: &Self) -> cmp::Ordering { + unsafe { + let result = c::CompareStringOrdinal( + self.utf16.as_ptr(), + self.utf16.len() as _, + other.utf16.as_ptr(), + other.utf16.len() as _, + c::TRUE, + ); + match result { + c::CSTR_LESS_THAN => cmp::Ordering::Less, + c::CSTR_EQUAL => cmp::Ordering::Equal, + c::CSTR_GREATER_THAN => cmp::Ordering::Greater, + // `CompareStringOrdinal` should never fail so long as the parameters are correct. + _ => panic!("comparing environment keys failed: {}", Error::last_os_error()), + } + } + } +} +impl PartialOrd for EnvKey { + fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { + Some(self.cmp(other)) + } +} +impl PartialEq for EnvKey { + fn eq(&self, other: &Self) -> bool { + if self.utf16.len() != other.utf16.len() { + false + } else { + self.cmp(other) == cmp::Ordering::Equal + } + } +} impl From<OsString> for EnvKey { - fn from(mut k: OsString) -> Self { - k.make_ascii_uppercase(); - EnvKey(k) + fn from(k: OsString) -> Self { + EnvKey { utf16: k.encode_wide().collect(), os_string: k } } } impl From<EnvKey> for OsString { fn from(k: EnvKey) -> Self { - k.0 + k.os_string } } impl Borrow<OsStr> for EnvKey { fn borrow(&self) -> &OsStr { - &self.0 + &self.os_string } } impl AsRef<OsStr> for EnvKey { fn as_ref(&self) -> &OsStr { - &self.0 + &self.os_string } } @@ -531,7 +576,8 @@ fn make_envp(maybe_env: Option<BTreeMap<EnvKey, OsString>>) -> io::Result<(*mut let mut blk = Vec::new(); for (k, v) in env { - blk.extend(ensure_no_nuls(k.0)?.encode_wide()); + ensure_no_nuls(k.os_string)?; + blk.extend(k.utf16); blk.push('=' as u16); blk.extend(ensure_no_nuls(v)?.encode_wide()); blk.push(0); diff --git a/library/std/src/sys/windows/process/tests.rs b/library/std/src/sys/windows/process/tests.rs index 8830ae049c6..ff3f9131cc8 100644 --- a/library/std/src/sys/windows/process/tests.rs +++ b/library/std/src/sys/windows/process/tests.rs @@ -1,5 +1,7 @@ use super::make_command_line; +use crate::env; use crate::ffi::{OsStr, OsString}; +use crate::process::Command; #[test] fn test_make_command_line() { @@ -41,3 +43,62 @@ fn test_make_command_line() { "\"\u{03c0}\u{042f}\u{97f3}\u{00e6}\u{221e}\"" ); } + +// On Windows, environment args are case preserving but comparisons are case-insensitive. +// See: #85242 +#[test] +fn windows_env_unicode_case() { + let test_cases = [ + ("ä", "Ä"), + ("ß", "SS"), + ("Ä", "Ö"), + ("Ä", "Ö"), + ("I", "İ"), + ("I", "i"), + ("I", "ı"), + ("i", "I"), + ("i", "İ"), + ("i", "ı"), + ("İ", "I"), + ("İ", "i"), + ("İ", "ı"), + ("ı", "I"), + ("ı", "i"), + ("ı", "İ"), + ("ä", "Ä"), + ("ß", "SS"), + ("Ä", "Ö"), + ("Ä", "Ö"), + ("I", "İ"), + ("I", "i"), + ("I", "ı"), + ("i", "I"), + ("i", "İ"), + ("i", "ı"), + ("İ", "I"), + ("İ", "i"), + ("İ", "ı"), + ("ı", "I"), + ("ı", "i"), + ("ı", "İ"), + ]; + // Test that `cmd.env` matches `env::set_var` when setting two strings that + // may (or may not) be case-folded when compared. + for (a, b) in test_cases.iter() { + let mut cmd = Command::new("cmd"); + cmd.env(a, "1"); + cmd.env(b, "2"); + env::set_var(a, "1"); + env::set_var(b, "2"); + + for (key, value) in cmd.get_envs() { + assert_eq!( + env::var(key).ok(), + value.map(|s| s.to_string_lossy().into_owned()), + "command environment mismatch: {} {}", + a, + b + ); + } + } +} |
