Skip to main content

mdcat/
resources.rs

1// Copyright 2018-2020 Sebastian Wiesner <sebastian@swsnr.de>
2
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Access to resources referenced from markdown documents.
8
9use std::fmt::Debug;
10use std::io::{Error, ErrorKind, Result};
11
12use mime::Mime;
13use url::Url;
14
15mod curl;
16mod file;
17pub(crate) mod image;
18pub(crate) mod prefetch;
19
20pub(crate) mod svg;
21
22pub(crate) use self::image::InlineImageProtocol;
23pub use curl::CurlResourceHandler;
24pub use file::FileResourceHandler;
25pub use prefetch::{prefetch_and_wrap, CachingResourceHandler};
26
27/// Data of a resource with associated mime type.
28#[derive(Debug, Clone)]
29pub struct MimeData {
30    /// The mime type if known.
31    pub mime_type: Option<Mime>,
32    /// The data.
33    pub data: Vec<u8>,
34}
35
36impl MimeData {
37    /// Get the essence of the mime type, if any.
38    ///
39    /// The essence is roughly the mime type without parameters.
40    pub fn mime_type_essence(&self) -> Option<&str> {
41        self.mime_type.as_ref().map(|m| m.essence_str())
42    }
43}
44
45/// Handle resource URLs.
46///
47/// See [`DispatchingResourceHandler`] for a resource handler which dispatches
48/// to a list of handlers, and [`FileResourceHandler`] for a resource handler for
49/// local files.
50///
51/// For remote URLs implement this handler on top of a suitable crate for network
52/// requests, e.q. [`reqwest`](https://docs.rs/reqwest) or [`curl`](https://docs.rs/curl).
53pub trait ResourceUrlHandler {
54    /// Read a resource.
55    ///
56    /// Read data from the given `url`, and return the data and its associated mime type if known,
57    /// or any IO error which occurred while reading from the resource.
58    ///
59    /// Alternatively, return an IO error with [`ErrorKind::Unsupported`] to indicate that the
60    /// given `url` is not supported by this resource handler.  In this case a higher level
61    /// resource handler may try a different handler.
62    fn read_resource(&self, url: &Url) -> Result<MimeData>;
63}
64
65impl<R: ResourceUrlHandler + ?Sized> ResourceUrlHandler for &'_ R {
66    fn read_resource(&self, url: &Url) -> Result<MimeData> {
67        (*self).read_resource(url)
68    }
69}
70
71/// Filter by URL scheme.
72///
73/// Return `Ok(url)` if `url` has the given `scheme`, otherwise return an IO error with error kind
74/// [`ErrorKind::Unsupported`].
75pub fn filter_schemes<'a>(schemes: &[&str], url: &'a Url) -> Result<&'a Url> {
76    if schemes.contains(&url.scheme()) {
77        Ok(url)
78    } else {
79        Err(Error::new(
80            ErrorKind::Unsupported,
81            format!("Unsupported scheme in {url}, expected one of {schemes:?}"),
82        ))
83    }
84}
85
86/// A resource handler which dispatches reading among a list of inner handlers.
87pub struct DispatchingResourceHandler {
88    /// Inner handlers.
89    handlers: Vec<Box<dyn ResourceUrlHandler>>,
90}
91
92impl DispatchingResourceHandler {
93    /// Create a new handler wrapping all given `handlers`.
94    pub fn new(handlers: Vec<Box<dyn ResourceUrlHandler>>) -> Self {
95        Self { handlers }
96    }
97}
98
99impl ResourceUrlHandler for DispatchingResourceHandler {
100    /// Read from the given resource `url`.
101    ///
102    /// Try every inner handler one after another, while handlers return an
103    /// [`ErrorKind::Unsupported`] IO error.  For any other error abort and return the error.
104    ///
105    /// Return the first different result, i.e. either data read or another error.
106    fn read_resource(&self, url: &Url) -> Result<MimeData> {
107        for handler in &self.handlers {
108            match handler.read_resource(url) {
109                Ok(data) => return Ok(data),
110                Err(error) if error.kind() == ErrorKind::Unsupported => {}
111                Err(error) => return Err(error),
112            }
113        }
114        Err(Error::new(
115            ErrorKind::Unsupported,
116            format!("No handler supported reading from {url}"),
117        ))
118    }
119}
120
121/// A resource handler which doesn't read anything.
122#[derive(Debug, Clone, Copy)]
123pub struct NoopResourceHandler;
124
125impl ResourceUrlHandler for NoopResourceHandler {
126    /// Always return an [`ErrorKind::Unsupported`] error.
127    fn read_resource(&self, url: &Url) -> Result<MimeData> {
128        Err(Error::new(
129            ErrorKind::Unsupported,
130            format!("Reading from resource {url} is not supported"),
131        ))
132    }
133}