mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-08 19:42:12 +00:00
docpath: hold a non-Unicode document's root to the namespace rules
A relative link next to a document whose path is not Unicode was joined to the document's folder natively, skipping the parsing that refuses device and object-manager namespaces. On Windows a document path with an unpaired surrogate under \\.\, \\?\GLOBALROOT or \??\ therefore let the link resolve inside that namespace. The native join now refuses those roots as resolve does for a Unicode path; drives and shares are kept.
This commit is contained in:
2 files changed
+53
-2
No files matched your search
@@ -75,8 +75,9 @@
|
||||
//! assert_eq!(resolve_absolute("img/logo.png", None), Err(Refused::Invalid));
|
||||
//! ```
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::path::{MAIN_SEPARATOR, Path, PathBuf};
|
||||
use std::path::{Component, MAIN_SEPARATOR, Path, PathBuf, Prefix};
|
||||
|
||||
/// What a stored path refers to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -605,8 +606,11 @@ fn render(root: &Root, comps: &[String]) -> PathBuf {
|
||||
PathBuf::from(prefix + &comps.join(&sep.to_string()))
|
||||
}
|
||||
|
||||
/// A relative link next to a document whose path is not UTF-8 (possible on Unix only).
|
||||
/// A relative link next to a document whose path is not Unicode (bytes that are not UTF-8 on
|
||||
/// Unix, an unpaired surrogate on Windows). The link inherits the document's root, so that root
|
||||
/// is held to the rules [`resolve`] applies to a Unicode document path.
|
||||
fn join_native(doc: &Path, comps: &[String]) -> Result<PathBuf, Refused> {
|
||||
check_native_root(doc)?;
|
||||
let mut p = doc.parent().map(Path::to_path_buf).unwrap_or_default();
|
||||
for c in comps {
|
||||
match c.as_str() {
|
||||
@@ -621,3 +625,19 @@ fn join_native(doc: &Path, comps: &[String]) -> Result<PathBuf, Refused> {
|
||||
}
|
||||
Ok(p)
|
||||
}
|
||||
|
||||
/// Refuses a native document path rooted in a device or object-manager namespace (`\\.\`,
|
||||
/// `\\?\` other than a drive or `UNC`, `\??\`), as [`parse`] does for a Unicode one. Drives and
|
||||
/// shares are kept; a share is then the link's boundary as for a Unicode path.
|
||||
fn check_native_root(doc: &Path) -> Result<(), Refused> {
|
||||
let mut comps = doc.components();
|
||||
match comps.next() {
|
||||
Some(Component::Prefix(p)) => match p.kind() {
|
||||
Prefix::Disk(_) | Prefix::VerbatimDisk(_) => Ok(()),
|
||||
Prefix::UNC(host, _) | Prefix::VerbatimUNC(host, _) if host != "." && host != "?" => Ok(()),
|
||||
_ => Err(Refused::Device),
|
||||
},
|
||||
Some(Component::RootDir) if comps.next() == Some(Component::Normal(OsStr::new("??"))) => Err(Refused::Device),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -401,3 +401,34 @@ fn refusals_have_messages() {
|
||||
assert!(!r.to_string().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn a_non_unicode_document_path_keeps_the_namespace_rules() {
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
let doc = Path::new(OsStr::from_bytes(b"/home/u/\xff/a.doc"));
|
||||
assert_eq!(resolve("img/x.png", Some(doc)).unwrap(), Path::new(OsStr::from_bytes(b"/home/u/\xff/img/x.png")));
|
||||
let device = Path::new(OsStr::from_bytes(b"/??/\xff/a.doc"));
|
||||
assert_eq!(resolve("x.png", Some(device)), Err(Refused::Device));
|
||||
assert_eq!(resolve("x.png", Some(Path::new("/??/u/a.doc"))), Err(Refused::Device));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn a_non_unicode_document_path_keeps_the_namespace_rules() {
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
let native = |s: &str| {
|
||||
// An unpaired surrogate in the folder name makes the path non-Unicode.
|
||||
let mut w: Vec<u16> = s.encode_utf16().collect();
|
||||
let at = w.iter().rposition(|c| *c == u16::from(b'\\')).unwrap_or(0);
|
||||
w.insert(at, 0xD800);
|
||||
std::path::PathBuf::from(OsString::from_wide(&w))
|
||||
};
|
||||
assert!(resolve("x.png", Some(native(r"C:\docs\a.doc").as_path())).is_ok());
|
||||
assert!(resolve("x.png", Some(native(r"\\server\share\docs\a.doc").as_path())).is_ok());
|
||||
for doc in [r"\\.\COM1\docs\a.doc", r"\\?\GLOBALROOT\Device\docs\a.doc", r"\??\C:\docs\a.doc"] {
|
||||
assert_eq!(resolve("x.png", Some(native(doc).as_path())), Err(Refused::Device), "{doc}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user