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
124
125
126
127
128
129
130
// 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.
///
/// See [`DispatchingResourceHandler`] for a resource handler which dispatches
/// to a list of handlers, and [`FileResourceHandler`] for a resource handler for
/// local files.
///
/// For remote URLs, see e.g. [mdcat-http-reqwest](https://docs.rs/mdcat-http-reqwest)
/// for an implementation which fetches HTTP resources with the `reqwest` library.
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"),
        ))
    }
}