ironpress 1.4.4

Pure Rust HTML/CSS/Markdown to PDF converter with layout engine, LaTeX math, tables, images, custom fonts, and streaming output. No browser, no system dependencies.
Documentation
use super::*;
use std::fs;

fn test_root() -> (tempfile::TempDir, DocumentResources) {
    let directory = tempfile::tempdir().expect("temporary resource root");
    fs::create_dir(directory.path().join("images")).expect("image directory");
    fs::write(directory.path().join("images/ok.png"), b"png").expect("image fixture");
    let resources = DocumentResources::new(ResourceAccess::Sanitized, Some(directory.path()), None);
    (directory, resources)
}

#[test]
fn sanitized_local_reference_requires_an_authorized_root() {
    let resources = DocumentResources::new(ResourceAccess::Sanitized, None, None);
    assert_eq!(resources.resolve("../../private.png", None), None);
    assert_eq!(
        resources.rewrite_css_urls("a{background:url(../../private.png)}", None),
        "a{background:url(\"\")}"
    );
}

#[test]
fn authorized_root_rewrites_descendants_and_rejects_traversal() {
    let (directory, resources) = test_root();
    let resolved = resources
        .resolve("images/ok.png", Some(directory.path()))
        .expect("authorized descendant");
    assert!(Path::new(&resolved).is_absolute());
    assert!(resolved.ends_with("images/ok.png"));
    assert_eq!(
        resources.resolve("../outside.png", Some(directory.path())),
        None
    );
}

#[test]
fn distinct_base_and_root_allow_shared_ancestor_assets_only() {
    let root = tempfile::tempdir().expect("authorized root");
    let base = root.path().join("cases/fonts");
    fs::create_dir_all(&base).expect("document base");
    fs::create_dir(root.path().join("fonts")).expect("shared font directory");
    fs::write(root.path().join("fonts/Parity.ttf"), b"font").expect("shared font");
    let outside = tempfile::tempdir().expect("outside directory");
    fs::write(outside.path().join("secret.ttf"), b"secret").expect("outside font");

    let resources =
        DocumentResources::new(ResourceAccess::Sanitized, Some(&base), Some(root.path()));
    let resolved = resources
        .resolve("../../fonts/Parity.ttf", resources.base_path())
        .expect("shared asset inside the explicit root");
    assert!(resolved.ends_with("fonts/Parity.ttf"));
    assert_eq!(
        resources.resolve(
            outside
                .path()
                .join("secret.ttf")
                .to_str()
                .expect("UTF-8 fixture path"),
            None
        ),
        None
    );
}

#[test]
fn base_outside_explicit_root_denies_relative_resources() {
    let root = tempfile::tempdir().expect("authorized root");
    let outside = tempfile::tempdir().expect("outside base");
    fs::write(outside.path().join("secret.png"), b"secret").expect("outside resource");
    let resources = DocumentResources::new(
        ResourceAccess::Sanitized,
        Some(outside.path()),
        Some(root.path()),
    );

    assert_eq!(resources.base_path(), None);
    assert_eq!(resources.resolve("secret.png", None), None);
}

#[cfg(unix)]
#[test]
fn authorized_root_rejects_symlink_escape() {
    use std::os::unix::fs::symlink;

    let (directory, resources) = test_root();
    let outside = tempfile::tempdir().expect("outside directory");
    fs::write(outside.path().join("secret.png"), b"secret").expect("outside fixture");
    symlink(outside.path(), directory.path().join("linked")).expect("symlink fixture");

    assert_eq!(
        resources.resolve("linked/secret.png", Some(directory.path())),
        None
    );
}

#[test]
fn css_rewriter_ignores_comments_and_strings() {
    let resources = DocumentResources::new(ResourceAccess::Sanitized, None, None);
    let css =
        r#"/* url(secret.png) */ a::before{content:"url(secret.png)";background:url(secret.png)}"#;
    assert_eq!(
        resources.rewrite_css_urls(css, None),
        r#"/* url(secret.png) */ a::before{content:"url(secret.png)";background:url("")}"#
    );
}

#[test]
fn sanitized_css_preserves_inline_and_fragment_urls() {
    let resources = DocumentResources::new(ResourceAccess::Sanitized, None, None);
    let css = "a{filter:url(#fx);background:url(DATA:image/png;base64,AA==)}";
    assert_eq!(
        resources.rewrite_css_urls(css, None),
        "a{filter:url(\"#fx\");background:url(\"DATA:image/png;base64,AA==\")}"
    );
}