diff --git a/library/std/src/os/windows/ffi.rs b/library/std/src/os/windows/ffi.rs index c89b9ff1efa6b..5d0464a9ab00a 100644 --- a/library/std/src/os/windows/ffi.rs +++ b/library/std/src/os/windows/ffi.rs @@ -52,7 +52,9 @@ #![stable(feature = "rust1", since = "1.0.0")] +use crate::cmp::Ordering; use crate::ffi::{OsStr, OsString}; +use crate::io; use crate::sealed::Sealed; use crate::sys::os_str::Buf; use crate::sys_common::wtf8::Wtf8Buf; @@ -124,6 +126,78 @@ pub trait OsStrExt: Sealed { /// ``` #[stable(feature = "rust1", since = "1.0.0")] fn encode_wide(&self) -> EncodeWide<'_>; + + /// Compares two `OsStr` by using the Windows system implementation. + /// This performs a case-insensitive comparison of UTF-16 code units using the system case mappings. + /// The comparison is locale-independent, but exact results may depend on the Windows version, + /// file system, and system settings. + /// + /// This is the correct way to compare various strings on Windows: + /// environment variable keys, registry keys and resource handle names are all case-insensitive. + /// Note that this does not include file names or paths; those can be case-sensitive depending on + /// the system, file system or directory settings. + /// + /// Note that this operation requires encoding both strings to UTF-16 and potentially performing system calls. + /// This operation is thus more computationally expensive than a normal comparison using [`Ord`]. + /// + /// # Errors + /// + /// This function will return an error in the following situations, but is not limited to just these cases: + /// - If the string contains any null characters. + /// + /// # Examples + /// ``` + /// #![feature(windows_case_insensitive)] + /// + /// use std::ffi::OsString; + /// use std::os::windows::prelude::*; + /// + /// let list = [ OsString::from("A"), OsString::from("Z"), OsString::from("a") ]; + /// + /// let mut sorted = list.clone(); + /// sorted.sort(); + /// + /// let mut sorted_with_system_cmp = list.clone(); + /// sorted_with_system_cmp.sort_by(|a, b| a.system_cmp(b).unwrap()); + /// + /// assert_eq!(sorted, list); // unchanged, since `Z` < `a` + /// assert_eq!(sorted_with_system_cmp, [ OsString::from("A"), OsString::from("a"), OsString::from("Z") ]); + /// ``` + #[unstable(feature = "windows_case_insensitive", issue = "86007")] + fn system_cmp(&self, other: &Self) -> io::Result; + + /// Checks two `OsStr` for equality by using the Windows system implementation. + /// This performs a case-insensitive comparison of UTF-16 code units using the system case mappings. + /// The comparison is locale-independent, but exact results may depend on the Windows version, + /// file system, and system settings. + /// + /// This is the correct way to compare various strings on Windows: + /// environment variable keys, registry keys and resource handle names are all case-insensitive. + /// Note that this does not include file names or paths; those can be case-sensitive depending on + /// the system, file system or directory settings. + /// + /// Note that this operation requires encoding both strings to UTF-16 and potentially performing system calls. + /// This operation is thus more computationally expensive than a normal comparison using [`Eq`]. + /// + /// # Errors + /// + /// This function will return an error in the following situations, but is not limited to just these cases: + /// - If the string contains any null characters. + /// + /// # Examples + /// ``` + /// #![feature(windows_case_insensitive)] + /// + /// use std::ffi::OsString; + /// use std::os::windows::prelude::*; + /// + /// let a = OsString::from("Path"); + /// let b = OsString::from("PATH"); + /// + /// assert!(a.eq(&b) == false); + /// assert!(a.system_eq(&b).unwrap() == true); + #[unstable(feature = "windows_case_insensitive", issue = "86007")] + fn system_eq(&self, other: &Self) -> io::Result; } #[stable(feature = "rust1", since = "1.0.0")] @@ -131,4 +205,20 @@ impl OsStrExt for OsStr { fn encode_wide(&self) -> EncodeWide<'_> { self.as_inner().inner.encode_wide() } + + fn system_cmp(&self, other: &Self) -> io::Result { + crate::sys::compare_case_insensitive(self, other) + } + + fn system_eq(&self, other: &Self) -> io::Result { + if self.len() == other.len() { + Ok(crate::sys::compare_case_insensitive(self, other)? == Ordering::Equal) + } else { + // The system implementation performs an "ordinal" check, so directly comparing every + // code unit in the same position in the two strings. As a consequence, two strings + // with different lengths can never be equal, even if they contain characters that + // change length when changing case according to Unicode. + Ok(false) + } + } } diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs index b7efc884473b4..0a95b194d7a99 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; @@ -996,6 +1000,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; } #[link(name = "ws2_32")] diff --git a/library/std/src/sys/windows/mod.rs b/library/std/src/sys/windows/mod.rs index f23e874f24905..1d1f213585021 100644 --- a/library/std/src/sys/windows/mod.rs +++ b/library/std/src/sys/windows/mod.rs @@ -151,6 +151,25 @@ pub fn to_u16s>(s: S) -> crate::io::Result> { inner(s.as_ref()) } +pub fn compare_case_insensitive, B: AsRef>( + a: A, + b: B, +) -> crate::io::Result { + let a = crate::sys::to_u16s(a.as_ref())?; + let b = crate::sys::to_u16s(b.as_ref())?; + + let result = unsafe { + c::CompareStringOrdinal(a.as_ptr(), a.len() as _, b.as_ptr(), b.len() as _, c::TRUE) + }; + + match result { + c::CSTR_LESS_THAN => Ok(crate::cmp::Ordering::Less), + c::CSTR_EQUAL => Ok(crate::cmp::Ordering::Equal), + c::CSTR_GREATER_THAN => Ok(crate::cmp::Ordering::Greater), + _ => Err(crate::io::Error::last_os_error()), + } +} + // Many Windows APIs follow a pattern of where we hand a buffer and then they // will report back to us how large the buffer should be or how many bytes // currently reside in the buffer. This function is an abstraction over these