1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Copyright 2018-2020 Sebastian Wiesner <sebastian@swsnr.de>

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! Access to resources referenced from markdown documents.

use std::fmt::Debug;
use std::io::{Error, ErrorKind, Result};

use mime::Mime;
use url::Url;

mod file;
pub(crate) mod image;

pub(crate) mod svg;

pub(crate) use self::image::InlineImageProtocol;
pub use file::FileResourceHandler;

/// Data of a resource with associated mime type.
#[derive(Debug, Clone)]
pub struct MimeData {
    /// The mime type if known.
    pub mime_type: Option<Mime>,
    /// The data.
    pub data: Vec<u8>,
}

impl MimeData {
    /// Get the essence of the mime type, if any.
    ///
    /// The essence is roughly the mime type without parameters.
    pub fn mime_type_essence(&self) -> Option<&str> {
        self.mime_type.as_ref().map(|m| m.essence_str())
    }
}

/// Handle resource URLs.
pub trait ResourceUrlHandler: Send + Sync + Debug {
    /// Read a resource.
    ///
    /// Read data from the given `url`, and return the data and its associated mime type if known,
    /// or any IO error which occurred while reading from the resource.
    ///
    /// Alternatively, return an IO error with [`ErrorKind::Unsupported`] to indicate that the
    /// given `url` is not supported by this resource handler.  In this case a higher level
    /// resource handler may try a different handler.
    fn read_resource(&self, url: &Url) -> Result<MimeData>;
}

impl<'a, R: ResourceUrlHandler + ?Sized> ResourceUrlHandler for &'a R {
    fn read_resource(&self, url: &Url) -> Result<MimeData> {
        (*self).read_resource(url)
    }
}

/// Filter by URL scheme.
///
/// Return `Ok(url)` if `url` has the given `scheme`, otherwise return an IO error with error kind
/// [`ErrorKind::Unsupported`].
pub fn filter_schemes<'a>(schemes: &[&str], url: &'a Url) -> Result<&'a Url> {
    if schemes.contains(&url.scheme()) {
        Ok(url)
    } else {
        Err(Error::new(
            ErrorKind::Unsupported,
            format!("Unsupported scheme in {url}, expected one of {schemes:?}"),
        ))
    }
}

/// A resource handler which dispatches reading among a list of inner handlers.
#[derive(Debug)]
pub struct DispatchingResourceHandler {
    /// Inner handlers.
    handlers: Vec<Box<dyn ResourceUrlHandler>>,
}

impl DispatchingResourceHandler {
    /// Create a new handler wrapping all given `handlers`.
    pub fn new(handlers: Vec<Box<dyn ResourceUrlHandler>>) -> Self {
        Self { handlers }
    }
}

impl ResourceUrlHandler for DispatchingResourceHandler {
    /// Read from the given resource `url`.
    ///
    /// Try every inner handler one after another, while handlers return an
    /// [`ErrorKind::Unsupported`] IO error.  For any other error abort and return the error.
    ///
    /// Return the first different result, i.e. either data read or another error.
    fn read_resource(&self, url: &Url) -> Result<MimeData> {
        for handler in &self.handlers {
            match handler.read_resource(url) {
                Ok(data) => return Ok(data),
                Err(error) if error.kind() == ErrorKind::Unsupported => continue,
                Err(error) => return Err(error),
            }
        }
        Err(Error::new(
            ErrorKind::Unsupported,
            format!("No handler supported reading from {url}"),
        ))
    }
}

/// A resource handler which doesn't read anything.
#[derive(Debug, Clone, Copy)]
pub struct NoopResourceHandler;

impl ResourceUrlHandler for NoopResourceHandler {
    /// Always return an [`ErrorKind::Unsupported`] error.
    fn read_resource(&self, url: &Url) -> Result<MimeData> {
        Err(Error::new(
            ErrorKind::Unsupported,
            format!("Reading from resource {url} is not supported"),
        ))
    }
}