mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Handle win32 separator & prefixes for cygwin paths
This commit is contained in:
+16
-1
@@ -1316,8 +1316,17 @@ fn _push(&mut self, path: &Path) {
|
||||
need_sep = false
|
||||
}
|
||||
|
||||
let need_clear = if cfg!(target_os = "cygwin") {
|
||||
// If path is absolute and its prefix is none, it is like `/foo`,
|
||||
// and will be handled below.
|
||||
path.prefix().is_some()
|
||||
} else {
|
||||
// On Unix: prefix is always None.
|
||||
path.is_absolute() || path.prefix().is_some()
|
||||
};
|
||||
|
||||
// absolute `path` replaces `self`
|
||||
if path.is_absolute() || path.prefix().is_some() {
|
||||
if need_clear {
|
||||
self.inner.truncate(0);
|
||||
|
||||
// verbatim paths need . and .. removed
|
||||
@@ -3616,6 +3625,11 @@ impl Error for NormalizeError {}
|
||||
/// paths, this is currently equivalent to calling
|
||||
/// [`GetFullPathNameW`][windows-path].
|
||||
///
|
||||
/// On Cygwin, this is currently equivalent to calling [`cygwin_conv_path`][cygwin-path]
|
||||
/// with mode `CCP_WIN_A_TO_POSIX`, and then being processed like other POSIX platforms.
|
||||
/// If a Windows path is given, it will be converted to an absolute POSIX path without
|
||||
/// keeping `..`.
|
||||
///
|
||||
/// Note that these [may change in the future][changes].
|
||||
///
|
||||
/// # Errors
|
||||
@@ -3673,6 +3687,7 @@ impl Error for NormalizeError {}
|
||||
/// [changes]: io#platform-specific-behavior
|
||||
/// [posix-semantics]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13
|
||||
/// [windows-path]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
|
||||
/// [cygwin-path]: https://cygwin.com/cygwin-api/func-cygwin-conv-path.html
|
||||
#[stable(feature = "absolute_path", since = "1.79.0")]
|
||||
pub fn absolute<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
use crate::ffi::OsString;
|
||||
use crate::os::unix::ffi::OsStringExt;
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::sys::common::small_c_string::run_path_with_cstr;
|
||||
use crate::sys::cvt;
|
||||
use crate::{io, ptr};
|
||||
|
||||
#[inline]
|
||||
pub fn is_sep_byte(b: u8) -> bool {
|
||||
b == b'/' || b == b'\\'
|
||||
}
|
||||
|
||||
/// Cygwin allways prefers `/` over `\`, and it always converts all `/` to `\`
|
||||
/// internally when calling Win32 APIs. Therefore, the server component of path
|
||||
/// `\\?\UNC\localhost/share` is `localhost/share` on Win32, but `localhost`
|
||||
/// on Cygwin.
|
||||
#[inline]
|
||||
pub fn is_verbatim_sep(b: u8) -> bool {
|
||||
b == b'/' || b == b'\\'
|
||||
}
|
||||
|
||||
pub use super::windows_prefix::parse_prefix;
|
||||
|
||||
pub const MAIN_SEP_STR: &str = "/";
|
||||
pub const MAIN_SEP: char = '/';
|
||||
|
||||
unsafe extern "C" {
|
||||
// Doc: https://cygwin.com/cygwin-api/func-cygwin-conv-path.html
|
||||
// Src: https://github.com/cygwin/cygwin/blob/718a15ba50e0d01c79800bd658c2477f9a603540/winsup/cygwin/path.cc#L3902
|
||||
// Safety:
|
||||
// * `what` should be `CCP_WIN_A_TO_POSIX` here
|
||||
// * `from` is null-terminated UTF-8 path
|
||||
// * `to` is buffer, the buffer size is `size`.
|
||||
//
|
||||
// Converts a path to an absolute POSIX path, no matter the input is Win32 path or POSIX path.
|
||||
fn cygwin_conv_path(
|
||||
what: libc::c_uint,
|
||||
from: *const libc::c_char,
|
||||
to: *mut u8,
|
||||
size: libc::size_t,
|
||||
) -> libc::ssize_t;
|
||||
}
|
||||
|
||||
const CCP_WIN_A_TO_POSIX: libc::c_uint = 2;
|
||||
|
||||
/// Make a POSIX path absolute.
|
||||
pub(crate) fn absolute(path: &Path) -> io::Result<PathBuf> {
|
||||
run_path_with_cstr(path, &|path| {
|
||||
let conv = CCP_WIN_A_TO_POSIX;
|
||||
let size = cvt(unsafe { cygwin_conv_path(conv, path.as_ptr(), ptr::null_mut(), 0) })?;
|
||||
// If success, size should not be 0.
|
||||
debug_assert!(size >= 1);
|
||||
let size = size as usize;
|
||||
let mut buffer = Vec::with_capacity(size);
|
||||
cvt(unsafe { cygwin_conv_path(conv, path.as_ptr(), buffer.as_mut_ptr(), size) })?;
|
||||
unsafe {
|
||||
buffer.set_len(size - 1);
|
||||
}
|
||||
Ok(PathBuf::from(OsString::from_vec(buffer)))
|
||||
})
|
||||
.map(|path| {
|
||||
if path.prefix().is_some() {
|
||||
return path;
|
||||
}
|
||||
|
||||
// From unix.rs
|
||||
let mut components = path.components();
|
||||
let path_os = path.as_os_str().as_encoded_bytes();
|
||||
|
||||
let mut normalized = if path_os.starts_with(b"//") && !path_os.starts_with(b"///") {
|
||||
components.next();
|
||||
PathBuf::from("//")
|
||||
} else {
|
||||
PathBuf::new()
|
||||
};
|
||||
normalized.extend(components);
|
||||
|
||||
if path_os.ends_with(b"/") {
|
||||
normalized.push("");
|
||||
}
|
||||
|
||||
normalized
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn is_absolute(path: &Path) -> bool {
|
||||
if path.as_os_str().as_encoded_bytes().starts_with(b"\\") {
|
||||
path.has_root() && path.prefix().is_some()
|
||||
} else {
|
||||
path.has_root()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "windows")] {
|
||||
mod windows;
|
||||
mod windows_prefix;
|
||||
pub use windows::*;
|
||||
} else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
|
||||
mod sgx;
|
||||
@@ -11,6 +12,10 @@
|
||||
} else if #[cfg(target_os = "uefi")] {
|
||||
mod uefi;
|
||||
pub use uefi::*;
|
||||
} else if #[cfg(target_os = "cygwin")] {
|
||||
mod cygwin;
|
||||
mod windows_prefix;
|
||||
pub use cygwin::*;
|
||||
} else {
|
||||
mod unix;
|
||||
pub use unix::*;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::ffi::{OsStr, OsString};
|
||||
use crate::path::{Path, PathBuf, Prefix};
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::sys::api::utf16;
|
||||
use crate::sys::pal::{c, fill_utf16_buf, os2path, to_u16s};
|
||||
use crate::{io, ptr};
|
||||
@@ -7,6 +7,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use super::windows_prefix::parse_prefix;
|
||||
|
||||
pub const MAIN_SEP_STR: &str = "\\";
|
||||
pub const MAIN_SEP: char = '\\';
|
||||
|
||||
@@ -77,177 +79,6 @@ pub(crate) fn append_suffix(path: PathBuf, suffix: &OsStr) -> PathBuf {
|
||||
path.into()
|
||||
}
|
||||
|
||||
struct PrefixParser<'a, const LEN: usize> {
|
||||
path: &'a OsStr,
|
||||
prefix: [u8; LEN],
|
||||
}
|
||||
|
||||
impl<'a, const LEN: usize> PrefixParser<'a, LEN> {
|
||||
#[inline]
|
||||
fn get_prefix(path: &OsStr) -> [u8; LEN] {
|
||||
let mut prefix = [0; LEN];
|
||||
// SAFETY: Only ASCII characters are modified.
|
||||
for (i, &ch) in path.as_encoded_bytes().iter().take(LEN).enumerate() {
|
||||
prefix[i] = if ch == b'/' { b'\\' } else { ch };
|
||||
}
|
||||
prefix
|
||||
}
|
||||
|
||||
fn new(path: &'a OsStr) -> Self {
|
||||
Self { path, prefix: Self::get_prefix(path) }
|
||||
}
|
||||
|
||||
fn as_slice(&self) -> PrefixParserSlice<'a, '_> {
|
||||
PrefixParserSlice {
|
||||
path: self.path,
|
||||
prefix: &self.prefix[..LEN.min(self.path.len())],
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PrefixParserSlice<'a, 'b> {
|
||||
path: &'a OsStr,
|
||||
prefix: &'b [u8],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> PrefixParserSlice<'a, '_> {
|
||||
fn strip_prefix(&self, prefix: &str) -> Option<Self> {
|
||||
self.prefix[self.index..]
|
||||
.starts_with(prefix.as_bytes())
|
||||
.then_some(Self { index: self.index + prefix.len(), ..*self })
|
||||
}
|
||||
|
||||
fn prefix_bytes(&self) -> &'a [u8] {
|
||||
&self.path.as_encoded_bytes()[..self.index]
|
||||
}
|
||||
|
||||
fn finish(self) -> &'a OsStr {
|
||||
// SAFETY: The unsafety here stems from converting between &OsStr and
|
||||
// &[u8] and back. This is safe to do because (1) we only look at ASCII
|
||||
// contents of the encoding and (2) new &OsStr values are produced only
|
||||
// from ASCII-bounded slices of existing &OsStr values.
|
||||
unsafe { OsStr::from_encoded_bytes_unchecked(&self.path.as_encoded_bytes()[self.index..]) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
|
||||
use Prefix::{DeviceNS, Disk, UNC, Verbatim, VerbatimDisk, VerbatimUNC};
|
||||
|
||||
let parser = PrefixParser::<8>::new(path);
|
||||
let parser = parser.as_slice();
|
||||
if let Some(parser) = parser.strip_prefix(r"\\") {
|
||||
// \\
|
||||
|
||||
// The meaning of verbatim paths can change when they use a different
|
||||
// separator.
|
||||
if let Some(parser) = parser.strip_prefix(r"?\")
|
||||
&& !parser.prefix_bytes().iter().any(|&x| x == b'/')
|
||||
{
|
||||
// \\?\
|
||||
if let Some(parser) = parser.strip_prefix(r"UNC\") {
|
||||
// \\?\UNC\server\share
|
||||
|
||||
let path = parser.finish();
|
||||
let (server, path) = parse_next_component(path, true);
|
||||
let (share, _) = parse_next_component(path, true);
|
||||
|
||||
Some(VerbatimUNC(server, share))
|
||||
} else {
|
||||
let path = parser.finish();
|
||||
|
||||
// in verbatim paths only recognize an exact drive prefix
|
||||
if let Some(drive) = parse_drive_exact(path) {
|
||||
// \\?\C:
|
||||
Some(VerbatimDisk(drive))
|
||||
} else {
|
||||
// \\?\prefix
|
||||
let (prefix, _) = parse_next_component(path, true);
|
||||
Some(Verbatim(prefix))
|
||||
}
|
||||
}
|
||||
} else if let Some(parser) = parser.strip_prefix(r".\") {
|
||||
// \\.\COM42
|
||||
let path = parser.finish();
|
||||
let (prefix, _) = parse_next_component(path, false);
|
||||
Some(DeviceNS(prefix))
|
||||
} else {
|
||||
let path = parser.finish();
|
||||
let (server, path) = parse_next_component(path, false);
|
||||
let (share, _) = parse_next_component(path, false);
|
||||
|
||||
if !server.is_empty() && !share.is_empty() {
|
||||
// \\server\share
|
||||
Some(UNC(server, share))
|
||||
} else {
|
||||
// no valid prefix beginning with "\\" recognized
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If it has a drive like `C:` then it's a disk.
|
||||
// Otherwise there is no prefix.
|
||||
parse_drive(path).map(Disk)
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a drive prefix, e.g. "C:" and "C:\whatever"
|
||||
fn parse_drive(path: &OsStr) -> Option<u8> {
|
||||
// In most DOS systems, it is not possible to have more than 26 drive letters.
|
||||
// See <https://en.wikipedia.org/wiki/Drive_letter_assignment#Common_assignments>.
|
||||
fn is_valid_drive_letter(drive: &u8) -> bool {
|
||||
drive.is_ascii_alphabetic()
|
||||
}
|
||||
|
||||
match path.as_encoded_bytes() {
|
||||
[drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a drive prefix exactly, e.g. "C:"
|
||||
fn parse_drive_exact(path: &OsStr) -> Option<u8> {
|
||||
// only parse two bytes: the drive letter and the drive separator
|
||||
if path.as_encoded_bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) {
|
||||
parse_drive(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the next path component.
|
||||
//
|
||||
// Returns the next component and the rest of the path excluding the component and separator.
|
||||
// Does not recognize `/` as a separator character if `verbatim` is true.
|
||||
fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
|
||||
let separator = if verbatim { is_verbatim_sep } else { is_sep_byte };
|
||||
|
||||
match path.as_encoded_bytes().iter().position(|&x| separator(x)) {
|
||||
Some(separator_start) => {
|
||||
let separator_end = separator_start + 1;
|
||||
|
||||
let component = &path.as_encoded_bytes()[..separator_start];
|
||||
|
||||
// Panic safe
|
||||
// The max `separator_end` is `bytes.len()` and `bytes[bytes.len()..]` is a valid index.
|
||||
let path = &path.as_encoded_bytes()[separator_end..];
|
||||
|
||||
// SAFETY: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\')
|
||||
// is encoded in a single byte, therefore `bytes[separator_start]` and
|
||||
// `bytes[separator_end]` must be code point boundaries and thus
|
||||
// `bytes[..separator_start]` and `bytes[separator_end..]` are valid wtf8 slices.
|
||||
unsafe {
|
||||
(
|
||||
OsStr::from_encoded_bytes_unchecked(component),
|
||||
OsStr::from_encoded_bytes_unchecked(path),
|
||||
)
|
||||
}
|
||||
}
|
||||
None => (path, OsStr::new("")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a UTF-16 encoded path capable of bypassing the legacy `MAX_PATH` limits.
|
||||
///
|
||||
/// This path may or may not have a verbatim prefix.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::super::windows_prefix::*;
|
||||
use super::*;
|
||||
use crate::path::Prefix;
|
||||
|
||||
#[test]
|
||||
fn test_parse_next_component() {
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
//! Parse Windows prefixes, for both Windows and Cygwin.
|
||||
|
||||
use super::{is_sep_byte, is_verbatim_sep};
|
||||
use crate::ffi::OsStr;
|
||||
use crate::path::Prefix;
|
||||
|
||||
struct PrefixParser<'a, const LEN: usize> {
|
||||
path: &'a OsStr,
|
||||
prefix: [u8; LEN],
|
||||
}
|
||||
|
||||
impl<'a, const LEN: usize> PrefixParser<'a, LEN> {
|
||||
#[inline]
|
||||
fn get_prefix(path: &OsStr) -> [u8; LEN] {
|
||||
let mut prefix = [0; LEN];
|
||||
// SAFETY: Only ASCII characters are modified.
|
||||
for (i, &ch) in path.as_encoded_bytes().iter().take(LEN).enumerate() {
|
||||
prefix[i] = if ch == b'/' { b'\\' } else { ch };
|
||||
}
|
||||
prefix
|
||||
}
|
||||
|
||||
fn new(path: &'a OsStr) -> Self {
|
||||
Self { path, prefix: Self::get_prefix(path) }
|
||||
}
|
||||
|
||||
fn as_slice(&self) -> PrefixParserSlice<'a, '_> {
|
||||
PrefixParserSlice {
|
||||
path: self.path,
|
||||
prefix: &self.prefix[..LEN.min(self.path.len())],
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PrefixParserSlice<'a, 'b> {
|
||||
path: &'a OsStr,
|
||||
prefix: &'b [u8],
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<'a> PrefixParserSlice<'a, '_> {
|
||||
fn strip_prefix(&self, prefix: &str) -> Option<Self> {
|
||||
self.prefix[self.index..]
|
||||
.starts_with(prefix.as_bytes())
|
||||
.then_some(Self { index: self.index + prefix.len(), ..*self })
|
||||
}
|
||||
|
||||
fn prefix_bytes(&self) -> &'a [u8] {
|
||||
&self.path.as_encoded_bytes()[..self.index]
|
||||
}
|
||||
|
||||
fn finish(self) -> &'a OsStr {
|
||||
// SAFETY: The unsafety here stems from converting between &OsStr and
|
||||
// &[u8] and back. This is safe to do because (1) we only look at ASCII
|
||||
// contents of the encoding and (2) new &OsStr values are produced only
|
||||
// from ASCII-bounded slices of existing &OsStr values.
|
||||
unsafe { OsStr::from_encoded_bytes_unchecked(&self.path.as_encoded_bytes()[self.index..]) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_prefix(path: &OsStr) -> Option<Prefix<'_>> {
|
||||
use Prefix::{DeviceNS, Disk, UNC, Verbatim, VerbatimDisk, VerbatimUNC};
|
||||
|
||||
let parser = PrefixParser::<8>::new(path);
|
||||
let parser = parser.as_slice();
|
||||
if let Some(parser) = parser.strip_prefix(r"\\") {
|
||||
// \\
|
||||
|
||||
// It's a POSIX path.
|
||||
if cfg!(target_os = "cygwin") && !path.as_encoded_bytes().iter().any(|&x| x == b'\\') {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The meaning of verbatim paths can change when they use a different
|
||||
// separator.
|
||||
if let Some(parser) = parser.strip_prefix(r"?\")
|
||||
// Cygwin allows `/` in verbatim paths.
|
||||
&& (cfg!(target_os = "cygwin") || !parser.prefix_bytes().iter().any(|&x| x == b'/'))
|
||||
{
|
||||
// \\?\
|
||||
if let Some(parser) = parser.strip_prefix(r"UNC\") {
|
||||
// \\?\UNC\server\share
|
||||
|
||||
let path = parser.finish();
|
||||
let (server, path) = parse_next_component(path, true);
|
||||
let (share, _) = parse_next_component(path, true);
|
||||
|
||||
Some(VerbatimUNC(server, share))
|
||||
} else {
|
||||
let path = parser.finish();
|
||||
|
||||
// in verbatim paths only recognize an exact drive prefix
|
||||
if let Some(drive) = parse_drive_exact(path) {
|
||||
// \\?\C:
|
||||
Some(VerbatimDisk(drive))
|
||||
} else {
|
||||
// \\?\prefix
|
||||
let (prefix, _) = parse_next_component(path, true);
|
||||
Some(Verbatim(prefix))
|
||||
}
|
||||
}
|
||||
} else if let Some(parser) = parser.strip_prefix(r".\") {
|
||||
// \\.\COM42
|
||||
let path = parser.finish();
|
||||
let (prefix, _) = parse_next_component(path, false);
|
||||
Some(DeviceNS(prefix))
|
||||
} else {
|
||||
let path = parser.finish();
|
||||
let (server, path) = parse_next_component(path, false);
|
||||
let (share, _) = parse_next_component(path, false);
|
||||
|
||||
if !server.is_empty() && !share.is_empty() {
|
||||
// \\server\share
|
||||
Some(UNC(server, share))
|
||||
} else {
|
||||
// no valid prefix beginning with "\\" recognized
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If it has a drive like `C:` then it's a disk.
|
||||
// Otherwise there is no prefix.
|
||||
Some(Disk(parse_drive(path)?))
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a drive prefix, e.g. "C:" and "C:\whatever"
|
||||
fn parse_drive(path: &OsStr) -> Option<u8> {
|
||||
// In most DOS systems, it is not possible to have more than 26 drive letters.
|
||||
// See <https://en.wikipedia.org/wiki/Drive_letter_assignment#Common_assignments>.
|
||||
fn is_valid_drive_letter(drive: &u8) -> bool {
|
||||
drive.is_ascii_alphabetic()
|
||||
}
|
||||
|
||||
match path.as_encoded_bytes() {
|
||||
[drive, b':', ..] if is_valid_drive_letter(drive) => Some(drive.to_ascii_uppercase()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Parses a drive prefix exactly, e.g. "C:"
|
||||
fn parse_drive_exact(path: &OsStr) -> Option<u8> {
|
||||
// only parse two bytes: the drive letter and the drive separator
|
||||
if path.as_encoded_bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) {
|
||||
parse_drive(path)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the next path component.
|
||||
//
|
||||
// Returns the next component and the rest of the path excluding the component and separator.
|
||||
// Does not recognize `/` as a separator character on Windows if `verbatim` is true.
|
||||
pub(crate) fn parse_next_component(path: &OsStr, verbatim: bool) -> (&OsStr, &OsStr) {
|
||||
let separator = if verbatim { is_verbatim_sep } else { is_sep_byte };
|
||||
|
||||
match path.as_encoded_bytes().iter().position(|&x| separator(x)) {
|
||||
Some(separator_start) => {
|
||||
let separator_end = separator_start + 1;
|
||||
|
||||
let component = &path.as_encoded_bytes()[..separator_start];
|
||||
|
||||
// Panic safe
|
||||
// The max `separator_end` is `bytes.len()` and `bytes[bytes.len()..]` is a valid index.
|
||||
let path = &path.as_encoded_bytes()[separator_end..];
|
||||
|
||||
// SAFETY: `path` is a valid wtf8 encoded slice and each of the separators ('/', '\')
|
||||
// is encoded in a single byte, therefore `bytes[separator_start]` and
|
||||
// `bytes[separator_end]` must be code point boundaries and thus
|
||||
// `bytes[..separator_start]` and `bytes[separator_end..]` are valid wtf8 slices.
|
||||
unsafe {
|
||||
(
|
||||
OsStr::from_encoded_bytes_unchecked(component),
|
||||
OsStr::from_encoded_bytes_unchecked(path),
|
||||
)
|
||||
}
|
||||
}
|
||||
None => (path, OsStr::new("")),
|
||||
}
|
||||
}
|
||||
@@ -1112,6 +1112,473 @@ pub fn test_decompositions_windows() {
|
||||
);
|
||||
}
|
||||
|
||||
// Unix paths are tested in `test_decompositions_unix` above.
|
||||
#[test]
|
||||
#[cfg(target_os = "cygwin")]
|
||||
pub fn test_decompositions_cygwin() {
|
||||
t!("\\",
|
||||
iter: ["/"],
|
||||
has_root: true,
|
||||
is_absolute: false,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("c:",
|
||||
iter: ["c:"],
|
||||
has_root: false,
|
||||
is_absolute: false,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("c:\\",
|
||||
iter: ["c:", "/"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("c:/",
|
||||
iter: ["c:", "/"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("a\\b\\c",
|
||||
iter: ["a", "b", "c"],
|
||||
has_root: false,
|
||||
is_absolute: false,
|
||||
parent: Some("a\\b"),
|
||||
file_name: Some("c"),
|
||||
file_stem: Some("c"),
|
||||
extension: None,
|
||||
file_prefix: Some("c")
|
||||
);
|
||||
|
||||
t!("\\a",
|
||||
iter: ["/", "a"],
|
||||
has_root: true,
|
||||
is_absolute: false,
|
||||
parent: Some("\\"),
|
||||
file_name: Some("a"),
|
||||
file_stem: Some("a"),
|
||||
extension: None,
|
||||
file_prefix: Some("a")
|
||||
);
|
||||
|
||||
t!("c:\\foo.txt",
|
||||
iter: ["c:", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("c:\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\server\\share\\foo.txt",
|
||||
iter: ["\\\\server\\share", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\server\\share\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("//server/share\\foo.txt",
|
||||
iter: ["//server/share", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//server/share\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("//server/share/foo.txt",
|
||||
iter: ["/", "server", "share", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//server/share"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\server\\share",
|
||||
iter: ["\\\\server\\share", "/"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\server",
|
||||
iter: ["/", "server"],
|
||||
has_root: true,
|
||||
is_absolute: false,
|
||||
parent: Some("\\"),
|
||||
file_name: Some("server"),
|
||||
file_stem: Some("server"),
|
||||
extension: None,
|
||||
file_prefix: Some("server")
|
||||
);
|
||||
|
||||
t!("\\\\?\\bar\\foo.txt",
|
||||
iter: ["\\\\?\\bar", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\bar\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\?\\bar",
|
||||
iter: ["\\\\?\\bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\?\\",
|
||||
iter: ["\\\\?\\"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\?\\UNC\\server\\share\\foo.txt",
|
||||
iter: ["\\\\?\\UNC\\server\\share", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\UNC\\server\\share\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\?\\UNC\\server/share\\foo.txt",
|
||||
iter: ["\\\\?\\UNC\\server/share", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\UNC\\server/share\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("//?/UNC/server\\share/foo.txt",
|
||||
iter: ["//?/UNC/server\\share", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//?/UNC/server\\share/"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("//?/UNC/server/share/foo.txt",
|
||||
iter: ["/", "?", "UNC", "server", "share", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//?/UNC/server/share"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\?\\UNC\\server",
|
||||
iter: ["\\\\?\\UNC\\server"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\?\\UNC\\",
|
||||
iter: ["\\\\?\\UNC\\"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:\\foo.txt",
|
||||
iter: ["\\\\?\\C:", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\C:\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("//?/C:\\foo.txt",
|
||||
iter: ["//?/C:", "/", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//?/C:\\"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("//?/C:/foo.txt",
|
||||
iter: ["/", "?", "C:", "foo.txt"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//?/C:"),
|
||||
file_name: Some("foo.txt"),
|
||||
file_stem: Some("foo"),
|
||||
extension: Some("txt"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:\\",
|
||||
iter: ["\\\\?\\C:", "/"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:",
|
||||
iter: ["\\\\?\\C:"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\?\\foo/bar",
|
||||
iter: ["\\\\?\\foo", "/", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\foo/"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:/foo/bar",
|
||||
iter: ["\\\\?\\C:", "/", "foo", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\C:/foo"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("\\\\.\\foo\\bar",
|
||||
iter: ["\\\\.\\foo", "/", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\.\\foo\\"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("\\\\.\\foo",
|
||||
iter: ["\\\\.\\foo", "/"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("\\\\.\\foo/bar",
|
||||
iter: ["\\\\.\\foo", "/", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\.\\foo/"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("\\\\.\\foo\\bar/baz",
|
||||
iter: ["\\\\.\\foo", "/", "bar", "baz"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\.\\foo\\bar"),
|
||||
file_name: Some("baz"),
|
||||
file_stem: Some("baz"),
|
||||
extension: None,
|
||||
file_prefix: Some("baz")
|
||||
);
|
||||
|
||||
t!("\\\\.\\",
|
||||
iter: ["\\\\.\\", "/"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: None,
|
||||
file_name: None,
|
||||
file_stem: None,
|
||||
extension: None,
|
||||
file_prefix: None
|
||||
);
|
||||
|
||||
t!("//.\\foo/bar",
|
||||
iter: ["//.\\foo", "/", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//.\\foo/"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("\\\\./foo/bar",
|
||||
iter: ["\\\\./foo", "/", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\./foo/"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("//./foo\\bar",
|
||||
iter: ["//./foo", "/", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//./foo\\"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("//./?/C:/foo/bar",
|
||||
iter: ["/", "?", "C:", "foo", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//./?/C:/foo"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("//././../././../?/C:/foo/bar",
|
||||
iter: ["/", "..", "..", "?", "C:", "foo", "bar"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("//././../././../?/C:/foo"),
|
||||
file_name: Some("bar"),
|
||||
file_stem: Some("bar"),
|
||||
extension: None,
|
||||
file_prefix: Some("bar")
|
||||
);
|
||||
|
||||
t!("\\\\?\\a\\b\\",
|
||||
iter: ["\\\\?\\a", "/", "b"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\a\\"),
|
||||
file_name: Some("b"),
|
||||
file_stem: Some("b"),
|
||||
extension: None,
|
||||
file_prefix: Some("b")
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:\\foo.txt.zip",
|
||||
iter: ["\\\\?\\C:", "/", "foo.txt.zip"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\C:\\"),
|
||||
file_name: Some("foo.txt.zip"),
|
||||
file_stem: Some("foo.txt"),
|
||||
extension: Some("zip"),
|
||||
file_prefix: Some("foo")
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:\\.foo.txt.zip",
|
||||
iter: ["\\\\?\\C:", "/", ".foo.txt.zip"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\C:\\"),
|
||||
file_name: Some(".foo.txt.zip"),
|
||||
file_stem: Some(".foo.txt"),
|
||||
extension: Some("zip"),
|
||||
file_prefix: Some(".foo")
|
||||
);
|
||||
|
||||
t!("\\\\?\\C:\\.foo",
|
||||
iter: ["\\\\?\\C:", "/", ".foo"],
|
||||
has_root: true,
|
||||
is_absolute: true,
|
||||
parent: Some("\\\\?\\C:\\"),
|
||||
file_name: Some(".foo"),
|
||||
file_stem: Some(".foo"),
|
||||
extension: None,
|
||||
file_prefix: Some(".foo")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_stem_ext() {
|
||||
t!("foo",
|
||||
@@ -1227,6 +1694,11 @@ macro_rules! tp (
|
||||
tp!("/foo/bar", "/", "/");
|
||||
tp!("/foo/bar", "/baz", "/baz");
|
||||
tp!("/foo/bar", "./baz", "/foo/bar/./baz");
|
||||
|
||||
if cfg!(target_os = "cygwin") {
|
||||
tp!("c:\\", "windows", "c:\\windows");
|
||||
tp!("c:", "windows", "c:windows");
|
||||
}
|
||||
} else {
|
||||
tp!("", "foo", "foo");
|
||||
tp!("foo", "bar", r"foo\bar");
|
||||
|
||||
Reference in New Issue
Block a user