Repository: typst/typst
File: crates/typst-library/src/visualize/image/svg.rs (Rust)

Code before:

    /// Load a linked image or return None if a previous image caused an error,
    /// or if the linked image failed to load.
    /// Only the first error message is retained.
    fn load(&mut self, href: &str) -> Option<usvg::ImageKind> {
        if self.error.is_none() {
            match self.load_or_error(href) {
                Ok(image) => Some(image),
                Err(err) => {
                    self.error = Some(LoadError::new(
                        ReportPos::None,
                        eco_format!("failed to load linked image {} in SVG", href),
                        err,
                    ));
                    None
                }
            }
        } else {
            None
        }
    }

    /// Load a linked image or return an error message string.
    fn load_or_error(&mut self, href: &str) -> Result<usvg::ImageKind, EcoString> {
        // If the href starts with "file://", strip this prefix to construct an ordinary path.
        let href = if let Some(stripped) = href.strip_prefix("file://") {
            stripped
        } else {
            href
        };

        // Do not accept absolute hrefs. They would be parsed in typst in a way
        // that is not compatible with their interpretation in the SVG standard.
        if href.starts_with("/") {
            return Err(EcoString::from("absolute paths are not allowed"));
        }

        // Exit early if the href is an URL.
        if let Some(pos) = href.find("://") {
            let scheme = &href[..pos];
            if scheme
                .chars()
                .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
            {
                return Err(EcoString::from("URLs are not allowed"));
            }
        }

        // Resolve the path to the linked image.
        if self.svg_parent.is_none() {
            return Err(EcoString::from("cannot access file system from here"));

Review comment (suggestion):
this could use `.strip_prefix(..).unwrap_or(href)`

Code after:
    /// Only the first error message is retained.
    fn load(&mut self, href: &str) -> Option<usvg::ImageKind> {
        if self.error.is_some() {
            return None;
        }
        match self.load_or_error(href) {
            Ok(image) => Some(image),
            Err(err) => {
                self.error = Some(LoadError::new(
                    ReportPos::None,
                    eco_format!("failed to load linked image {} in SVG", href),
                    err,
                ));
                None
            }
        }
    }

    /// Load a linked image or return an error message string.
    fn load_or_error(&mut self, href: &str) -> Result<usvg::ImageKind, EcoString> {
        // If the href starts with "file://", strip this prefix to construct an ordinary path.
        let href = href.strip_prefix("file://").unwrap_or(href);

        // Do not accept absolute hrefs. They would be parsed in typst in a way
        // that is not compatible with their interpretation in the SVG standard.
        if href.starts_with("/") {
            return Err("absolute paths are not allowed".into());
        }

        // Exit early if the href is an URL.
        if let Some(pos) = href.find("://") {
            let scheme = &href[..pos];
            if scheme
                .chars()
                .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
            {
                return Err("URLs are not allowed".into());
            }
        }

        // Resolve the path to the linked image.
        if self.svg_file.is_none() {
            return Err("cannot access file system from here".into());
        }
        // Replace the file name in svg_file by href.
        let href_file = self.svg_file.unwrap().join(href);

        // Load image if file can be accessed.
        match self.world.file(href_file) {
            Ok(bytes) => {
                let arc_data = Arc::new(bytes.to_vec());