markdown-compiler 0.1.0

Strict Markdown content validation, asset resolution, and revision identities for Maincopy
Documentation
use crate::LogicalTreePathError;

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PortableLogicalPath(pub(crate) String);

impl PortableLogicalPath {
    pub(crate) fn parse(value: &str, max_bytes: usize) -> Result<Self, LogicalTreePathError> {
        if value.is_empty() {
            return Err(LogicalTreePathError::Empty);
        }
        if value.len() > max_bytes {
            return Err(LogicalTreePathError::TooLong);
        }
        if value.starts_with('/') || value.starts_with('\\') {
            return Err(LogicalTreePathError::Absolute);
        }
        if value.contains(['%', '\\']) {
            return Err(LogicalTreePathError::EncodedTraversal);
        }
        if !value.split('/').all(is_portable_component) {
            return Err(LogicalTreePathError::UnsupportedComponent);
        }
        Ok(Self(value.to_owned()))
    }

    pub(crate) fn as_str(&self) -> &str {
        &self.0
    }

    pub(crate) fn case_collision_key(&self) -> String {
        self.0.to_ascii_lowercase()
    }
}

fn is_portable_component(component: &str) -> bool {
    !component.is_empty()
        && component != "."
        && component != ".."
        && component
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
}