mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-07 12:13:20 +00:00
docpath: fold . and .. in a non-Unicode document's folder with the platform's path rules
A non-Unicode document path holding '..' (/a/../\xff/d.doc) was refused, because the native join kept the '..' while the lexical result folded it. join_native now folds the folder's components as normalize does, so such a document resolves as a Unicode one would, and a '..' that leaves a Unix name holding a backslash is still caught by the comparison.
This commit is contained in:
+26
-11
@@ -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};
|
||||
|
||||
/// What a stored path refers to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
@@ -619,18 +620,32 @@ fn resolve_native(raw: &str, doc: &Path, comps: &[String]) -> Result<PathBuf, Re
|
||||
if parts(&native) == parts(&expected) { Ok(native) } else { Err(Refused::Invalid) }
|
||||
}
|
||||
|
||||
/// Join a relative link's components to a non-Unicode document's folder with the platform's own
|
||||
/// path rules, folding `.` and `..` as [`normalize`] does: a folder with a root (`/`, `C:\`, a
|
||||
/// share) may not be climbed above, and a relative one keeps leading `..`.
|
||||
fn join_native(doc: &Path, comps: &[String]) -> Result<PathBuf, Refused> {
|
||||
let mut p = doc.parent().map(Path::to_path_buf).unwrap_or_default();
|
||||
for c in comps {
|
||||
match c.as_str() {
|
||||
"." => {}
|
||||
".." => {
|
||||
if !p.pop() {
|
||||
return Err(Refused::Invalid);
|
||||
let folder = doc.parent().unwrap_or_else(|| Path::new(""));
|
||||
let link = comps.iter().map(|c| match c.as_str() {
|
||||
"." => Component::CurDir,
|
||||
".." => Component::ParentDir,
|
||||
name => Component::Normal(OsStr::new(name)),
|
||||
});
|
||||
let mut root = PathBuf::new();
|
||||
let mut names: Vec<&OsStr> = Vec::new();
|
||||
for c in folder.components().chain(link) {
|
||||
match c {
|
||||
Component::Prefix(_) | Component::RootDir => root.push(c),
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => match names.last() {
|
||||
Some(last) if *last != OsStr::new("..") => {
|
||||
names.pop();
|
||||
}
|
||||
}
|
||||
_ => p.push(c),
|
||||
_ if root.has_root() => return Err(Refused::Invalid),
|
||||
_ => names.push(c.as_os_str()),
|
||||
},
|
||||
Component::Normal(name) => names.push(name),
|
||||
}
|
||||
}
|
||||
Ok(p)
|
||||
root.extend(names);
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
@@ -425,6 +425,15 @@ fn a_non_unicode_document_path_keeps_the_namespace_rules() {
|
||||
assert_eq!(resolve("x.png", Some(folder)).unwrap(), Path::new(OsStr::from_bytes(b"/home/u/a\\b/x.png")));
|
||||
let name = Path::new(OsStr::from_bytes(b"/home/u/a\xff\\b.doc"));
|
||||
assert_eq!(resolve("x.png", Some(name)), Err(Refused::Invalid));
|
||||
// `.` and `..` in the document's folder are folded as for a Unicode document path.
|
||||
let dotted = Path::new(OsStr::from_bytes(b"/home/./x/../u/\xff/a.doc"));
|
||||
assert_eq!(resolve("../y/x.png", Some(dotted)).unwrap(), Path::new("/home/u/y/x.png"));
|
||||
assert_eq!(resolve("../../../../x.png", Some(dotted)), Err(Refused::Invalid));
|
||||
let relative = Path::new(OsStr::from_bytes(b"\xff/a.doc"));
|
||||
assert_eq!(resolve("../../x.png", Some(relative)).unwrap(), Path::new("../x.png"));
|
||||
// A `..` that climbs out of a name holding a `\` on Unix does not leave the share unnoticed.
|
||||
let climb = Path::new(OsStr::from_bytes(b"//server/share/a\\b/../../\xff/a.doc"));
|
||||
assert_eq!(resolve("x.png", Some(climb)), Err(Refused::Invalid));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -443,6 +452,10 @@ fn a_non_unicode_document_path_keeps_the_namespace_rules() {
|
||||
let share = native(r"\\server\share\docs\a.doc");
|
||||
assert_eq!(resolve("x.png", Some(share.as_path())).unwrap(), native(r"\\server\share\docs\x.png"));
|
||||
assert_eq!(resolve(r"..\..\x.png", Some(share.as_path())), Err(Refused::Invalid));
|
||||
let dotted = native(r"C:\a\..\.\docs\a.doc");
|
||||
assert_eq!(resolve("x.png", Some(dotted.as_path())).unwrap(), native(r"C:\docs\x.png"));
|
||||
assert_eq!(resolve(r"..\y\x.png", Some(dotted.as_path())).unwrap(), Path::new(r"C:\y\x.png"));
|
||||
assert_eq!(resolve(r"..\..\x.png", Some(dotted.as_path())), Err(Refused::Invalid));
|
||||
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