mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 05:47:00 -06:00
#3736: HTML pictures load only from inside the automation read root
Finding 54. Opening an HTML file checks the file through its links, but its pictures load relative to the folder the given path names. Through a link outside the read root to a page inside it, pictures beside the link (outside the root) were embedded. Each picture's canonical path is now checked with check_read before it is read; unconfined opens are unchanged. Test: tests::html_pictures_load_only_from_inside_the_read_root. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8ce3eb58c7
commit
02fffea7c9
@@ -506,7 +506,7 @@ impl LocalImages {
|
||||
left.map(|_| ())
|
||||
}
|
||||
|
||||
/// The canonical path `src` names, if it's inside the folder.
|
||||
/// The canonical path `src` names, if it's inside the folder and may be read.
|
||||
fn resolve(&self, src: &str) -> Option<std::path::PathBuf> {
|
||||
let src = src.split(['#', '?']).next().unwrap_or("");
|
||||
if src.is_empty() || src.starts_with("//") || src.split_once(':').is_some_and(|(scheme, _)| !scheme.contains('/')) {
|
||||
@@ -520,6 +520,9 @@ impl LocalImages {
|
||||
let dir = self.dir.as_ref()?;
|
||||
// Canonical paths have `..` and symlinks resolved, so this keeps both inside the folder.
|
||||
let path = std::fs::canonicalize(dir.join(rel)).ok()?;
|
||||
// The folder is the one the HTML path names, which may be a link from outside the
|
||||
// automation read root; the picture itself must be inside it (#3736).
|
||||
crate::file_access::check_read(&path.to_string_lossy()).ok()?;
|
||||
path.starts_with(dir).then_some(path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4112,6 +4112,29 @@ fn html_pictures_through_a_symlink_out_of_the_folder_are_not_loaded() {
|
||||
assert!(doc.plain_text(StoryRef::Body).contains("Link"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn html_pictures_load_only_from_inside_the_read_root() {
|
||||
// #3736: the HTML file is checked through its link, but its pictures load from beside the
|
||||
// link. Opened through a link outside the read root, they must not come from there.
|
||||
let root = scratch_dir("html-img-root");
|
||||
for d in ["in", "outside"] {
|
||||
std::fs::create_dir_all(root.join(d)).unwrap();
|
||||
}
|
||||
std::fs::write(root.join("in/page.html"), r#"<p><img src="pic.png" alt="Pic"></p>"#).unwrap();
|
||||
std::fs::write(root.join("in/pic.png"), tiny_png()).unwrap();
|
||||
std::fs::write(root.join("outside/pic.png"), tiny_png()).unwrap();
|
||||
std::os::unix::fs::symlink(root.join("in/page.html"), root.join("outside/page.html")).unwrap();
|
||||
let roots = crate::file_access::AutomationRoots::new(Some(&root.join("in")), None).unwrap();
|
||||
let through_link = crate::file_access::confine(roots.as_ref(), || crate::io::open_path(&root.join("outside/page.html")));
|
||||
let direct = crate::file_access::confine(roots.as_ref(), || crate::io::open_path(&root.join("in/page.html")));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let through_link = through_link.unwrap();
|
||||
assert!(through_link.media.is_empty(), "no picture from outside the read root");
|
||||
assert!(through_link.plain_text(StoryRef::Body).contains("Pic"));
|
||||
assert_eq!(direct.unwrap().media.len(), 1, "pictures inside the root still load");
|
||||
}
|
||||
|
||||
/// `file.open` on a path reads only a regular file: a named pipe blocked until a writer came, and
|
||||
/// `/dev/zero` read until memory ran out.
|
||||
#[cfg(unix)]
|
||||
|
||||
Reference in New Issue
Block a user