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();
47
48 let path = if path.has_root() {
50 Cow::Borrowed(path)
51 } else {
52 Cow::Owned(Path::new(&Component::RootDir).join(path))
53 };
54
55 let buf = if cfg!(windows) {
56 let mut buf = String::new();
57 for component in path.components() {
58 match component {
59 Component::RootDir => buf.push('/'),
60 Component::CurDir => buf.push_str("./"),
61 Component::ParentDir => buf.push_str("../"),
62 Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()),
63 Component::Normal(s) => {
64 buf.push_str(&s.to_string_lossy());
65 buf.push('/')
66 }
67 }
68 }
69
70 if buf != "/" {
72 buf.pop();
73 }
74
75 buf
76 } else {
77 path.to_string_lossy().to_string()
78 };
79
80 AssetKey(buf)
81 }
82}
83
84#[non_exhaustive]
87#[derive(Debug, Clone, Copy)]
88pub enum CspHash<'a> {
89 Script(&'a str),
91
92 Style(&'a str),
94}
95
96impl CspHash<'_> {
97 pub fn directive(&self) -> &'static str {
99 match self {
100 Self::Script(_) => "script-src",
101 Self::Style(_) => "style-src",
102 }
103 }
104
105 pub fn hash(&self) -> &str {
107 match self {
108 Self::Script(hash) => hash,
109 Self::Style(hash) => hash,
110 }
111 }
112}
113
114pub struct EmbeddedAssets {
116 assets: phf::Map<&'static str, &'static [u8]>,
117 global_hashes: &'static [CspHash<'static>],
119 html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
121}
122
123struct DebugAssetMap<'a>(&'a phf::Map<&'static str, &'static [u8]>);
131
132impl std::fmt::Debug for DebugAssetMap<'_> {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 let mut map = f.debug_map();
135 for (k, v) in self.0.entries() {
136 map.key(k);
137 map.value(&format_args!("[u8; {}]", v.len()));
138 }
139 map.finish()
140 }
141}
142
143impl std::fmt::Debug for EmbeddedAssets {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_struct("EmbeddedAssets")
146 .field("assets", &DebugAssetMap(&self.assets))
147 .field("global_hashes", &self.global_hashes)
148 .field("html_hashes", &self.html_hashes)
149 .finish()
150 }
151}
152
153impl EmbeddedAssets {
154 pub const fn new(
156 map: phf::Map<&'static str, &'static [u8]>,
157 global_hashes: &'static [CspHash<'static>],
158 html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
159 ) -> Self {
160 Self {
161 assets: map,
162 global_hashes,
163 html_hashes,
164 }
165 }
166
167 #[cfg(feature = "compression")]
169 pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
170 let &(mut asdf) = self.assets.get(key.as_ref())?;
171 let mut buf = Vec::with_capacity(asdf.len());
174 brotli::BrotliDecompress(&mut asdf, &mut buf).ok()?;
175 Some(Cow::Owned(buf))
176 }
177
178 #[cfg(not(feature = "compression"))]
180 pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
181 Some(Cow::Borrowed(self.assets.get(key.as_ref())?))
182 }
183
184 pub fn iter(&self) -> Box<AssetsIter<'_>> {
186 Box::new(
187 self
188 .assets
189 .into_iter()
190 .map(|(k, b)| (Cow::Borrowed(*k), Cow::Borrowed(*b))),
191 )
192 }
193
194 pub fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
196 Box::new(
197 self
198 .global_hashes
199 .iter()
200 .chain(
201 self
202 .html_hashes
203 .get(html_path.as_ref())
204 .copied()
205 .into_iter()
206 .flatten(),
207 )
208 .copied(),
209 )
210 }
211}