assets.rs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright 2019-2023 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. /// Gets the hashes for the CSP tag of the HTML on the given path.
  96. fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_>;
  97. }
  98. /// [`Assets`] implementation that only contains compile-time compressed and embedded assets.
  99. #[derive(Debug)]
  100. pub struct EmbeddedAssets {
  101. assets: phf::Map<&'static str, &'static [u8]>,
  102. // Hashes that must be injected to the CSP of every HTML file.
  103. global_hashes: &'static [CspHash<'static>],
  104. // Hashes that are associated to the CSP of the HTML file identified by the map key (the HTML asset key).
  105. html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
  106. }
  107. impl EmbeddedAssets {
  108. /// Creates a new instance from the given asset map and script hash list.
  109. pub const fn new(
  110. map: phf::Map<&'static str, &'static [u8]>,
  111. global_hashes: &'static [CspHash<'static>],
  112. html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
  113. ) -> Self {
  114. Self {
  115. assets: map,
  116. global_hashes,
  117. html_hashes,
  118. }
  119. }
  120. }
  121. impl Assets for EmbeddedAssets {
  122. #[cfg(feature = "compression")]
  123. fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
  124. self
  125. .assets
  126. .get(key.as_ref())
  127. .map(|&(mut asdf)| {
  128. // with the exception of extremely small files, output should usually be
  129. // at least as large as the compressed version.
  130. let mut buf = Vec::with_capacity(asdf.len());
  131. brotli::BrotliDecompress(&mut asdf, &mut buf).map(|()| buf)
  132. })
  133. .and_then(Result::ok)
  134. .map(Cow::Owned)
  135. }
  136. #[cfg(not(feature = "compression"))]
  137. fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
  138. self
  139. .assets
  140. .get(key.as_ref())
  141. .copied()
  142. .map(|a| Cow::Owned(a.to_vec()))
  143. }
  144. fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
  145. Box::new(
  146. self
  147. .global_hashes
  148. .iter()
  149. .chain(
  150. self
  151. .html_hashes
  152. .get(html_path.as_ref())
  153. .copied()
  154. .into_iter()
  155. .flatten(),
  156. )
  157. .copied(),
  158. )
  159. }
  160. }