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}