1#[doc(hidden)]
9pub use phf;
10use std::{
11 borrow::Cow,
12 path::{Component, Path},
13};
14
15pub const SCRIPT_NONCE_TOKEN: &str = "__TAURI_SCRIPT_NONCE__";
17pub const STYLE_NONCE_TOKEN: &str = "__TAURI_STYLE_NONCE__";
19
20pub type AssetsIter<'a> = dyn Iterator<Item = (Cow<'a, str>, Cow<'a, [u8]>)> + 'a;
22
23#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
30pub struct AssetKey(String);
31
32impl From<AssetKey> for String {
33 fn from(key: AssetKey) -> Self {
34 key.0
35 }
36}
37
38impl AsRef<str> for AssetKey {
39 fn as_ref(&self) -> &str {
40 &self.0
41 }
42}
43
44impl<P: AsRef<Path>> From<P> for AssetKey {
45 fn from(path: P) -> Self {
46 let path = path.as_ref().to_owned();
48
49 let path = if path.has_root() {
51 path
52 } else {
53 Path::new(&Component::RootDir).join(path)
54 };
55
56 let buf = if cfg!(windows) {
57 let mut buf = String::new();
58 for component in path.components() {
59 match component {
60 Component::RootDir => buf.push('/'),
61 Component::CurDir => buf.push_str("./"),
62 Component::ParentDir => buf.push_str("../"),
63 Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()),
64 Component::Normal(s) => {
65 buf.push_str(&s.to_string_lossy());
66 buf.push('/')
67 }
68 }
69 }
70
71 if buf != "/" {
73 buf.pop();
74 }
75
76 buf
77 } else {
78 path.to_string_lossy().to_string()
79 };
80
81 AssetKey(buf)
82 }
83}
84
85#[non_exhaustive]
88#[derive(Debug, Clone, Copy)]
89pub enum CspHash<'a> {
90 Script(&'a str),
92
93 Style(&'a str),
95}
96
97impl CspHash<'_> {
98 pub fn directive(&self) -> &'static str {
100 match self {
101 Self::Script(_) => "script-src",
102 Self::Style(_) => "style-src",
103 }
104 }
105
106 pub fn hash(&self) -> &str {
108 match self {
109 Self::Script(hash) => hash,
110 Self::Style(hash) => hash,
111 }
112 }
113}
114
115pub struct EmbeddedAssets {
117 assets: phf::Map<&'static str, &'static [u8]>,
118 global_hashes: &'static [CspHash<'static>],
120 html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
122}
123
124struct DebugAssetMap<'a>(&'a phf::Map<&'static str, &'static [u8]>);
132
133impl std::fmt::Debug for DebugAssetMap<'_> {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 let mut map = f.debug_map();
136 for (k, v) in self.0.entries() {
137 map.key(k);
138 map.value(&format_args!("[u8; {}]", v.len()));
139 }
140 map.finish()
141 }
142}
143
144impl std::fmt::Debug for EmbeddedAssets {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 f.debug_struct("EmbeddedAssets")
147 .field("assets", &DebugAssetMap(&self.assets))
148 .field("global_hashes", &self.global_hashes)
149 .field("html_hashes", &self.html_hashes)
150 .finish()
151 }
152}
153
154impl EmbeddedAssets {
155 pub const fn new(
157 map: phf::Map<&'static str, &'static [u8]>,
158 global_hashes: &'static [CspHash<'static>],
159 html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
160 ) -> Self {
161 Self {
162 assets: map,
163 global_hashes,
164 html_hashes,
165 }
166 }
167
168 #[cfg(feature = "compression")]
170 pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
171 self
172 .assets
173 .get(key.as_ref())
174 .map(|&(mut asdf)| {
175 let mut buf = Vec::with_capacity(asdf.len());
178 brotli::BrotliDecompress(&mut asdf, &mut buf).map(|()| buf)
179 })
180 .and_then(Result::ok)
181 .map(Cow::Owned)
182 }
183
184 #[cfg(not(feature = "compression"))]
186 pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
187 self
188 .assets
189 .get(key.as_ref())
190 .copied()
191 .map(|a| Cow::Owned(a.to_vec()))
192 }
193
194 pub fn iter(&self) -> Box<AssetsIter<'_>> {
196 Box::new(
197 self
198 .assets
199 .into_iter()
200 .map(|(k, b)| (Cow::Borrowed(*k), Cow::Borrowed(*b))),
201 )
202 }
203
204 pub fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
206 Box::new(
207 self
208 .global_hashes
209 .iter()
210 .chain(
211 self
212 .html_hashes
213 .get(html_path.as_ref())
214 .copied()
215 .into_iter()
216 .flatten(),
217 )
218 .copied(),
219 )
220 }
221}