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