1#[doc(hidden)]
9pub use phf;
10use std::{
11 borrow::Cow,
12 path::{Component, Path},
13};
14
15pub type AssetsIter<'a> = dyn Iterator<Item = (Cow<'a, str>, Cow<'a, [u8]>)> + 'a;
17
18#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
25pub struct AssetKey(String);
26
27impl From<AssetKey> for String {
28 fn from(key: AssetKey) -> Self {
29 key.0
30 }
31}
32
33impl AsRef<str> for AssetKey {
34 fn as_ref(&self) -> &str {
35 &self.0
36 }
37}
38
39impl<P: AsRef<Path>> From<P> for AssetKey {
40 fn from(path: P) -> Self {
41 let path = path.as_ref().to_owned();
43
44 let path = if path.has_root() {
46 path
47 } else {
48 Path::new(&Component::RootDir).join(path)
49 };
50
51 let buf = if cfg!(windows) {
52 let mut buf = String::new();
53 for component in path.components() {
54 match component {
55 Component::RootDir => buf.push('/'),
56 Component::CurDir => buf.push_str("./"),
57 Component::ParentDir => buf.push_str("../"),
58 Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()),
59 Component::Normal(s) => {
60 buf.push_str(&s.to_string_lossy());
61 buf.push('/')
62 }
63 }
64 }
65
66 if buf != "/" {
68 buf.pop();
69 }
70
71 buf
72 } else {
73 path.to_string_lossy().to_string()
74 };
75
76 AssetKey(buf)
77 }
78}
79
80#[non_exhaustive]
83#[derive(Debug, Clone, Copy)]
84pub enum CspHash<'a> {
85 Script(&'a str),
87
88 Style(&'a str),
90}
91
92impl CspHash<'_> {
93 pub fn directive(&self) -> &'static str {
95 match self {
96 Self::Script(_) => "script-src",
97 Self::Style(_) => "style-src",
98 }
99 }
100
101 pub fn hash(&self) -> &str {
103 match self {
104 Self::Script(hash) => hash,
105 Self::Style(hash) => hash,
106 }
107 }
108}
109
110#[derive(Debug)]
112pub struct EmbeddedAssets {
113 assets: phf::Map<&'static str, &'static [u8]>,
114 global_hashes: &'static [CspHash<'static>],
116 html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
118}
119
120impl EmbeddedAssets {
121 pub const fn new(
123 map: phf::Map<&'static str, &'static [u8]>,
124 global_hashes: &'static [CspHash<'static>],
125 html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
126 ) -> Self {
127 Self {
128 assets: map,
129 global_hashes,
130 html_hashes,
131 }
132 }
133
134 #[cfg(feature = "compression")]
136 pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
137 self
138 .assets
139 .get(key.as_ref())
140 .map(|&(mut asdf)| {
141 let mut buf = Vec::with_capacity(asdf.len());
144 brotli::BrotliDecompress(&mut asdf, &mut buf).map(|()| buf)
145 })
146 .and_then(Result::ok)
147 .map(Cow::Owned)
148 }
149
150 #[cfg(not(feature = "compression"))]
152 pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
153 self
154 .assets
155 .get(key.as_ref())
156 .copied()
157 .map(|a| Cow::Owned(a.to_vec()))
158 }
159
160 pub fn iter(&self) -> Box<AssetsIter<'_>> {
162 Box::new(
163 self
164 .assets
165 .into_iter()
166 .map(|(k, b)| (Cow::Borrowed(*k), Cow::Borrowed(*b))),
167 )
168 }
169
170 pub fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
172 Box::new(
173 self
174 .global_hashes
175 .iter()
176 .chain(
177 self
178 .html_hashes
179 .get(html_path.as_ref())
180 .copied()
181 .into_iter()
182 .flatten(),
183 )
184 .copied(),
185 )
186 }
187}