assets.rs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! The Assets module allows you to read files that have been bundled by tauri
  5. //! during both compile time and runtime.
  6. #[doc(hidden)]
  7. pub use phf;
  8. use std::{
  9. borrow::Cow,
  10. path::{Component, Path},
  11. };
  12. /// Represent an asset file path in a normalized way.
  13. ///
  14. /// The following rules are enforced and added if needed:
  15. /// * Unix path component separators
  16. /// * Has a root directory
  17. /// * No trailing slash - directories are not included in assets
  18. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
  19. pub struct AssetKey(String);
  20. impl From<AssetKey> for String {
  21. fn from(key: AssetKey) -> Self {
  22. key.0
  23. }
  24. }
  25. impl AsRef<str> for AssetKey {
  26. fn as_ref(&self) -> &str {
  27. &self.0
  28. }
  29. }
  30. impl<P: AsRef<Path>> From<P> for AssetKey {
  31. fn from(path: P) -> Self {
  32. // TODO: change this to utilize `Cow` to prevent allocating an intermediate `PathBuf` when not necessary
  33. let path = path.as_ref().to_owned();
  34. // add in root to mimic how it is used from a server url
  35. let path = if path.has_root() {
  36. path
  37. } else {
  38. Path::new(&Component::RootDir).join(path)
  39. };
  40. let buf = if cfg!(windows) {
  41. let mut buf = String::new();
  42. for component in path.components() {
  43. match component {
  44. Component::RootDir => buf.push('/'),
  45. Component::CurDir => buf.push_str("./"),
  46. Component::ParentDir => buf.push_str("../"),
  47. Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()),
  48. Component::Normal(s) => {
  49. buf.push_str(&s.to_string_lossy());
  50. buf.push('/')
  51. }
  52. }
  53. }
  54. // remove the last slash
  55. if buf != "/" {
  56. buf.pop();
  57. }
  58. buf
  59. } else {
  60. path.to_string_lossy().to_string()
  61. };
  62. AssetKey(buf)
  63. }
  64. }
  65. /// A Content-Security-Policy hash value for a specific directive.
  66. /// For more information see [the MDN page](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives).
  67. #[non_exhaustive]
  68. #[derive(Debug, Clone, Copy)]
  69. pub enum CspHash<'a> {
  70. /// The `script-src` directive.
  71. Script(&'a str),
  72. /// The `style-src` directive.
  73. Style(&'a str),
  74. }
  75. impl CspHash<'_> {
  76. /// The Content-Security-Policy directive this hash applies to.
  77. pub fn directive(&self) -> &'static str {
  78. match self {
  79. Self::Script(_) => "script-src",
  80. Self::Style(_) => "style-src",
  81. }
  82. }
  83. /// The value of the Content-Security-Policy hash.
  84. pub fn hash(&self) -> &str {
  85. match self {
  86. Self::Script(hash) => hash,
  87. Self::Style(hash) => hash,
  88. }
  89. }
  90. }
  91. /// Represents a container of file assets that are retrievable during runtime.
  92. pub trait Assets: Send + Sync + 'static {
  93. /// Get the content of the passed [`AssetKey`].
  94. fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>>;
  95. /// Iterator for the assets.
  96. fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_>;
  97. /// Gets the hashes for the CSP tag of the HTML on the given path.
  98. fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
  99. }
  100. /// [`Assets`] implementation that only contains compile-time compressed and embedded assets.
  101. #[derive(Debug)]
  102. pub struct EmbeddedAssets {
  103. assets: phf::Map<&'static str, &'static [u8]>,
  104. // Hashes that must be injected to the CSP of every HTML file.
  105. global_hashes: &'static [CspHash<'static>],
  106. // Hashes that are associated to the CSP of the HTML file identified by the map key (the HTML asset key).
  107. html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
  108. }
  109. impl EmbeddedAssets {
  110. /// Creates a new instance from the given asset map and script hash list.
  111. pub const fn new(
  112. map: phf::Map<&'static str, &'static [u8]>,
  113. global_hashes: &'static [CspHash<'static>],
  114. html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
  115. ) -> Self {
  116. Self {
  117. assets: map,
  118. global_hashes,
  119. html_hashes,
  120. }
  121. }
  122. }
  123. impl Assets for EmbeddedAssets {
  124. #[cfg(feature = "compression")]
  125. fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
  126. self
  127. .assets
  128. .get(key.as_ref())
  129. .map(|&(mut asdf)| {
  130. // with the exception of extremely small files, output should usually be
  131. // at least as large as the compressed version.
  132. let mut buf = Vec::with_capacity(asdf.len());
  133. brotli::BrotliDecompress(&mut asdf, &mut buf).map(|()| buf)
  134. })
  135. .and_then(Result::ok)
  136. .map(Cow::Owned)
  137. }
  138. #[cfg(not(feature = "compression"))]
  139. fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
  140. self
  141. .assets
  142. .get(key.as_ref())
  143. .copied()
  144. .map(|a| Cow::Owned(a.to_vec()))
  145. }
  146. fn iter(&self) -> Box<dyn Iterator<Item = (&&str, &&[u8])> + '_> {
  147. Box::new(self.assets.into_iter())
  148. }
  149. fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
  150. Box::new(
  151. self
  152. .global_hashes
  153. .iter()
  154. .chain(
  155. self
  156. .html_hashes
  157. .get(html_path.as_ref())
  158. .copied()
  159. .into_iter()
  160. .flatten(),
  161. )
  162. .copied(),
  163. )
  164. }
  165. }