mini-docs 0.4.5

A minimal, secure build-time Markdown to HTML generator for the mini-* family.
Documentation
use std::path::{Component, Path, PathBuf};

use crate::error::DocError;

/// Confirms `relative` cannot escape `output_dir` before it is joined and written.
///
/// `relative` is expected to come from stripping `input_dir` off a path produced by
/// [`crate::walk`], which — because real directory entries can never be named `..`
/// and `PathBuf::join` never crosses a real filesystem boundary — cannot currently
/// contain a `ParentDir` or absolute component. Trusting "produced by our own walk"
/// as sufficient is exactly the kind of ambient assumption this function exists to
/// remove: it is the one place `output_dir.join(relative)` happens, so it is the one
/// place that must not assume its input is well-formed.
pub(crate) fn guard_output_path(output_dir: &Path, relative: &Path) -> Result<PathBuf, DocError> {
    for component in relative.components() {
        match component {
            Component::ParentDir | Component::RootDir | Component::Prefix(_) => {
                return Err(DocError::Escape(relative.display().to_string()));
            }
            Component::CurDir | Component::Normal(_) => {}
        }
    }

    let joined = output_dir.join(relative);
    if !joined.starts_with(output_dir) {
        return Err(DocError::Escape(relative.display().to_string()));
    }

    Ok(joined)
}

#[cfg(test)]
#[path = "../tests/unit/escape.rs"]
mod tests;