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}