pulldown_cmark_mdcat/resources/
file.rs1use std::fs::File;
10use std::io::prelude::*;
11use std::io::{Error, ErrorKind, Result};
12use std::path::Path;
13
14use mime::Mime;
15use tracing::{event, instrument, Level};
16use url::Url;
17
18use super::{filter_schemes, MimeData, ResourceUrlHandler};
19
20#[derive(Debug, Clone)]
22pub struct FileResourceHandler {
23 read_limit: u64,
24}
25
26impl FileResourceHandler {
27 pub fn new(read_limit: u64) -> Self {
31 Self { read_limit }
32 }
33}
34
35fn guess_mimetype<P: AsRef<Path>>(path: P) -> Option<Mime> {
46 path.as_ref()
47 .extension()
48 .map(|s| s.to_ascii_lowercase())
49 .and_then(|s| match s.to_str() {
50 Some("png") => Some(mime::IMAGE_PNG),
51 Some("svg") => Some(mime::IMAGE_SVG),
52 _ => None,
53 })
54}
55
56impl ResourceUrlHandler for FileResourceHandler {
57 #[instrument(level = "debug", skip(self))]
58 fn read_resource(&self, url: &Url) -> Result<MimeData> {
59 filter_schemes(&["file"], url).and_then(|url| {
60 match url.to_file_path() {
61 Ok(path) => {
62 event!(
63 Level::DEBUG,
64 "Reading from resource file {}",
65 path.display()
66 );
67 let mut buffer = Vec::new();
68 File::open(&path)?
69 .take(self.read_limit + 1)
71 .read_to_end(&mut buffer)?;
72
73 if self.read_limit < buffer.len() as u64 {
74 Err(Error::new(
75 ErrorKind::FileTooLarge,
76 format!("Contents of {url} exceeded {} bytes", self.read_limit),
77 ))
78 } else {
79 let mime_type = guess_mimetype(&path);
80 if mime_type.is_none() {
81 event!(
82 Level::DEBUG,
83 "Failed to guess mime type from {}",
84 path.display()
85 );
86 }
87 Ok(MimeData {
88 mime_type,
89 data: buffer,
90 })
91 }
92 }
93 Err(_) => Err(Error::new(
94 ErrorKind::InvalidInput,
95 format!("Cannot convert URL {url} to file path"),
96 )),
97 }
98 })
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use crate::resources::*;
105 use similar_asserts::assert_eq;
106 use url::Url;
107
108 #[test]
109 fn read_resource_returns_content_type() {
110 let cwd = Url::from_directory_path(std::env::current_dir().unwrap()).unwrap();
111 let client = FileResourceHandler::new(5_000_000);
112
113 let resource = cwd.join("../sample/rust-logo.svg").unwrap();
114 let mime_type = client.read_resource(&resource).unwrap().mime_type;
115 assert_eq!(mime_type, Some(mime::IMAGE_SVG));
116
117 let resource = cwd.join("../sample/rust-logo-128x128.png").unwrap();
118 let mime_type = client.read_resource(&resource).unwrap().mime_type;
119 assert_eq!(mime_type, Some(mime::IMAGE_PNG));
120 }
121
122 #[test]
123 fn read_resource_obeys_size_limit() {
124 let cwd = Url::from_directory_path(std::env::current_dir().unwrap()).unwrap();
125 let client = FileResourceHandler { read_limit: 10 };
126
127 let resource = cwd.join("../sample/rust-logo.svg").unwrap();
128 let error = client.read_resource(&resource).unwrap_err().to_string();
129 assert_eq!(error, format!("Contents of {resource} exceeded 10 bytes"));
130 }
131
132 #[test]
133 fn read_resource_ignores_http() {
134 let url = Url::parse("https://example.com").unwrap();
135
136 let client = FileResourceHandler { read_limit: 10 };
137 let error = client.read_resource(&url).unwrap_err().to_string();
138 assert_eq!(
139 error,
140 "Unsupported scheme in https://example.com/, expected one of [\"file\"]"
141 );
142 }
143}