Skip to main content

css_inline/
resolver.rs

1use crate::{InlineError, Result};
2use std::{borrow::Cow, io::ErrorKind, path::Path};
3
4/// Turn a `file://` URL into a filesystem path.
5///
6/// Percent-decodes the path and handles Windows drive letters. URLs with a host
7/// (e.g. `file://relative-dir/`) keep the legacy prefix-stripping behavior.
8fn to_file_path(location: &str) -> Cow<'_, Path> {
9    #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
10    if let Ok(url) = url::Url::parse(location) {
11        if url.scheme() == "file" && url.host().is_none() {
12            if let Ok(path) = url.to_file_path() {
13                return Cow::Owned(path);
14            }
15        }
16    }
17    Cow::Borrowed(Path::new(location.trim_start_matches("file://")))
18}
19
20/// Blocking way of resolving stylesheets from various sources.
21pub trait StylesheetResolver: Send + Sync {
22    /// Retrieve a stylesheet from a network or local filesystem location.
23    ///
24    /// # Errors
25    ///
26    /// Any network or filesystem related error, or an error during response parsing.
27    fn retrieve(&self, location: &str) -> Result<String> {
28        if location.starts_with("https") || location.starts_with("http") {
29            #[cfg(feature = "http")]
30            {
31                self.retrieve_from_url(location)
32            }
33
34            #[cfg(not(feature = "http"))]
35            {
36                Err(std::io::Error::new(
37                    ErrorKind::Unsupported,
38                    "Loading external URLs requires the `http` feature",
39                )
40                .into())
41            }
42        } else {
43            #[cfg(feature = "file")]
44            {
45                self.retrieve_from_path(location)
46            }
47            #[cfg(not(feature = "file"))]
48            {
49                Err(std::io::Error::new(
50                    ErrorKind::Unsupported,
51                    "Loading local files requires the `file` feature",
52                )
53                .into())
54            }
55        }
56    }
57    /// Retrieve a stylesheet from a network location.
58    ///
59    /// # Errors
60    ///
61    /// Any network-related error, or an error during response parsing.
62    fn retrieve_from_url(&self, url: &str) -> Result<String> {
63        Err(self.unsupported(&format!("Loading external URLs is not supported: {url}")))
64    }
65    /// Retrieve a stylesheet from the local filesystem.
66    ///
67    /// # Errors
68    ///
69    /// Any filesystem-related error.
70    fn retrieve_from_path(&self, path: &str) -> Result<String> {
71        let path = to_file_path(path);
72        std::fs::read_to_string(&path).map_err(|error| match error.kind() {
73            ErrorKind::NotFound => InlineError::MissingStyleSheet {
74                path: path.display().to_string(),
75            },
76            #[cfg(target_family = "wasm")]
77            ErrorKind::Unsupported => self.unsupported(&format!(
78                "Loading local files is not supported on WASM: {}",
79                path.display()
80            )),
81            _ => InlineError::IO(error),
82        })
83    }
84    /// Return the "Unsupported" kind of error.
85    fn unsupported(&self, reason: &str) -> InlineError {
86        std::io::Error::new(ErrorKind::Unsupported, reason).into()
87    }
88}
89
90/// Default stylesheet resolver.
91#[derive(Debug, Default)]
92pub struct DefaultStylesheetResolver;
93
94impl StylesheetResolver for DefaultStylesheetResolver {
95    #[cfg(feature = "http")]
96    fn retrieve_from_url(&self, url: &str) -> Result<String> {
97        let into_error = |error| InlineError::Network {
98            error,
99            location: url.to_string(),
100        };
101        reqwest::blocking::get(url)
102            .map_err(into_error)?
103            .text()
104            .map_err(into_error)
105    }
106}