mod.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::path::{Component, Display, Path, PathBuf};
  5. use crate::Runtime;
  6. use serde::{de::Error as DeError, Deserialize, Deserializer};
  7. use serde_repr::{Deserialize_repr, Serialize_repr};
  8. pub(crate) mod plugin;
  9. use crate::error::*;
  10. #[cfg(target_os = "android")]
  11. mod android;
  12. #[cfg(not(target_os = "android"))]
  13. mod desktop;
  14. #[cfg(target_os = "android")]
  15. pub use android::PathResolver;
  16. #[cfg(not(target_os = "android"))]
  17. pub use desktop::PathResolver;
  18. /// A wrapper for [`PathBuf`] that prevents path traversal.
  19. #[derive(Clone, Debug)]
  20. pub struct SafePathBuf(PathBuf);
  21. impl SafePathBuf {
  22. /// Validates the path for directory traversal vulnerabilities and returns a new [`SafePathBuf`] instance if it is safe.
  23. pub fn new(path: PathBuf) -> std::result::Result<Self, &'static str> {
  24. if path.components().any(|x| matches!(x, Component::ParentDir)) {
  25. Err("cannot traverse directory, rewrite the path without the use of `../`")
  26. } else {
  27. Ok(Self(path))
  28. }
  29. }
  30. /// Returns an object that implements [`std::fmt::Display`] for safely printing paths.
  31. ///
  32. /// See [`PathBuf#method.display`] for more information.
  33. pub fn display(&self) -> Display<'_> {
  34. self.0.display()
  35. }
  36. }
  37. impl AsRef<Path> for SafePathBuf {
  38. fn as_ref(&self) -> &Path {
  39. self.0.as_ref()
  40. }
  41. }
  42. impl<'de> Deserialize<'de> for SafePathBuf {
  43. fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
  44. where
  45. D: Deserializer<'de>,
  46. {
  47. let path = PathBuf::deserialize(deserializer)?;
  48. SafePathBuf::new(path).map_err(DeError::custom)
  49. }
  50. }
  51. /// A base directory for a path.
  52. ///
  53. /// The base directory is the optional root of a file system operation.
  54. /// If informed by the API call, all paths will be relative to the path of the given directory.
  55. ///
  56. /// For more information, check the [`dirs_next` documentation](https://docs.rs/dirs_next/).
  57. #[derive(Serialize_repr, Deserialize_repr, Clone, Copy, Debug)]
  58. #[repr(u16)]
  59. #[non_exhaustive]
  60. pub enum BaseDirectory {
  61. /// The Audio directory.
  62. Audio = 1,
  63. /// The Cache directory.
  64. Cache,
  65. /// The Config directory.
  66. Config,
  67. /// The Data directory.
  68. Data,
  69. /// The LocalData directory.
  70. LocalData,
  71. /// The Document directory.
  72. Document,
  73. /// The Download directory.
  74. Download,
  75. /// The Picture directory.
  76. Picture,
  77. /// The Public directory.
  78. Public,
  79. /// The Video directory.
  80. Video,
  81. /// The Resource directory.
  82. Resource,
  83. /// A temporary directory. Resolves to [`std::env::temp_dir`].
  84. Temp,
  85. /// The default app config directory.
  86. /// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`.
  87. AppConfig,
  88. /// The default app data directory.
  89. /// Resolves to [`BaseDirectory::Data`]`/{bundle_identifier}`.
  90. AppData,
  91. /// The default app local data directory.
  92. /// Resolves to [`BaseDirectory::LocalData`]`/{bundle_identifier}`.
  93. AppLocalData,
  94. /// The default app cache directory.
  95. /// Resolves to [`BaseDirectory::Cache`]`/{bundle_identifier}`.
  96. AppCache,
  97. /// The default app log directory.
  98. /// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS
  99. /// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows.
  100. AppLog,
  101. /// The Desktop directory.
  102. #[cfg(not(target_os = "android"))]
  103. Desktop,
  104. /// The Executable directory.
  105. #[cfg(not(target_os = "android"))]
  106. Executable,
  107. /// The Font directory.
  108. #[cfg(not(target_os = "android"))]
  109. Font,
  110. /// The Home directory.
  111. #[cfg(not(target_os = "android"))]
  112. Home,
  113. /// The Runtime directory.
  114. #[cfg(not(target_os = "android"))]
  115. Runtime,
  116. /// The Template directory.
  117. #[cfg(not(target_os = "android"))]
  118. Template,
  119. }
  120. impl BaseDirectory {
  121. /// Gets the variable that represents this [`BaseDirectory`] for string paths.
  122. pub fn variable(self) -> &'static str {
  123. match self {
  124. Self::Audio => "$AUDIO",
  125. Self::Cache => "$CACHE",
  126. Self::Config => "$CONFIG",
  127. Self::Data => "$DATA",
  128. Self::LocalData => "$LOCALDATA",
  129. Self::Document => "$DOCUMENT",
  130. Self::Download => "$DOWNLOAD",
  131. Self::Picture => "$PICTURE",
  132. Self::Public => "$PUBLIC",
  133. Self::Video => "$VIDEO",
  134. Self::Resource => "$RESOURCE",
  135. Self::Temp => "$TEMP",
  136. Self::AppConfig => "$APPCONFIG",
  137. Self::AppData => "$APPDATA",
  138. Self::AppLocalData => "$APPLOCALDATA",
  139. Self::AppCache => "$APPCACHE",
  140. Self::AppLog => "$APPLOG",
  141. #[cfg(not(target_os = "android"))]
  142. Self::Desktop => "$DESKTOP",
  143. #[cfg(not(target_os = "android"))]
  144. Self::Executable => "$EXE",
  145. #[cfg(not(target_os = "android"))]
  146. Self::Font => "$FONT",
  147. #[cfg(not(target_os = "android"))]
  148. Self::Home => "$HOME",
  149. #[cfg(not(target_os = "android"))]
  150. Self::Runtime => "$RUNTIME",
  151. #[cfg(not(target_os = "android"))]
  152. Self::Template => "$TEMPLATE",
  153. }
  154. }
  155. /// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any.
  156. pub fn from_variable(variable: &str) -> Option<Self> {
  157. let res = match variable {
  158. "$AUDIO" => Self::Audio,
  159. "$CACHE" => Self::Cache,
  160. "$CONFIG" => Self::Config,
  161. "$DATA" => Self::Data,
  162. "$LOCALDATA" => Self::LocalData,
  163. "$DOCUMENT" => Self::Document,
  164. "$DOWNLOAD" => Self::Download,
  165. "$PICTURE" => Self::Picture,
  166. "$PUBLIC" => Self::Public,
  167. "$VIDEO" => Self::Video,
  168. "$RESOURCE" => Self::Resource,
  169. "$TEMP" => Self::Temp,
  170. "$APPCONFIG" => Self::AppConfig,
  171. "$APPDATA" => Self::AppData,
  172. "$APPLOCALDATA" => Self::AppLocalData,
  173. "$APPCACHE" => Self::AppCache,
  174. "$APPLOG" => Self::AppLog,
  175. #[cfg(not(target_os = "android"))]
  176. "$DESKTOP" => Self::Desktop,
  177. #[cfg(not(target_os = "android"))]
  178. "$EXE" => Self::Executable,
  179. #[cfg(not(target_os = "android"))]
  180. "$FONT" => Self::Font,
  181. #[cfg(not(target_os = "android"))]
  182. "$HOME" => Self::Home,
  183. #[cfg(not(target_os = "android"))]
  184. "$RUNTIME" => Self::Runtime,
  185. #[cfg(not(target_os = "android"))]
  186. "$TEMPLATE" => Self::Template,
  187. _ => return None,
  188. };
  189. Some(res)
  190. }
  191. }
  192. impl<R: Runtime> PathResolver<R> {
  193. /// Resolves the path with the base directory.
  194. ///
  195. /// # Examples
  196. ///
  197. /// ```rust,no_run
  198. /// use tauri::{path::BaseDirectory, Manager};
  199. /// tauri::Builder::default()
  200. /// .setup(|app| {
  201. /// let path = app.path().resolve("path/to/something", BaseDirectory::Config)?;
  202. /// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something");
  203. /// Ok(())
  204. /// });
  205. /// ```
  206. pub fn resolve<P: AsRef<Path>>(&self, path: P, base_directory: BaseDirectory) -> Result<PathBuf> {
  207. resolve_path::<R>(self, base_directory, Some(path.as_ref().to_path_buf()))
  208. }
  209. /// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one.
  210. ///
  211. /// # Examples
  212. ///
  213. /// ```rust,no_run
  214. /// use tauri::Manager;
  215. /// tauri::Builder::default()
  216. /// .setup(|app| {
  217. /// let path = app.path().parse("$HOME/.bashrc")?;
  218. /// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc");
  219. /// Ok(())
  220. /// });
  221. /// ```
  222. pub fn parse<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
  223. let mut p = PathBuf::new();
  224. let mut components = path.as_ref().components();
  225. match components.next() {
  226. Some(Component::Normal(str)) => {
  227. if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) {
  228. p.push(resolve_path::<R>(self, base_directory, None)?);
  229. } else {
  230. p.push(str);
  231. }
  232. }
  233. Some(component) => p.push(component),
  234. None => (),
  235. }
  236. for component in components {
  237. if let Component::ParentDir = component {
  238. continue;
  239. }
  240. p.push(component);
  241. }
  242. Ok(p)
  243. }
  244. }
  245. fn resolve_path<R: Runtime>(
  246. resolver: &PathResolver<R>,
  247. directory: BaseDirectory,
  248. path: Option<PathBuf>,
  249. ) -> Result<PathBuf> {
  250. let resolve_resource = matches!(directory, BaseDirectory::Resource);
  251. let mut base_dir_path = match directory {
  252. BaseDirectory::Audio => resolver.audio_dir(),
  253. BaseDirectory::Cache => resolver.cache_dir(),
  254. BaseDirectory::Config => resolver.config_dir(),
  255. BaseDirectory::Data => resolver.data_dir(),
  256. BaseDirectory::LocalData => resolver.local_data_dir(),
  257. BaseDirectory::Document => resolver.document_dir(),
  258. BaseDirectory::Download => resolver.download_dir(),
  259. BaseDirectory::Picture => resolver.picture_dir(),
  260. BaseDirectory::Public => resolver.public_dir(),
  261. BaseDirectory::Video => resolver.video_dir(),
  262. BaseDirectory::Resource => resolver.resource_dir(),
  263. BaseDirectory::Temp => resolver.temp_dir(),
  264. BaseDirectory::AppConfig => resolver.app_config_dir(),
  265. BaseDirectory::AppData => resolver.app_data_dir(),
  266. BaseDirectory::AppLocalData => resolver.app_local_data_dir(),
  267. BaseDirectory::AppCache => resolver.app_cache_dir(),
  268. BaseDirectory::AppLog => resolver.app_log_dir(),
  269. #[cfg(not(target_os = "android"))]
  270. BaseDirectory::Desktop => resolver.desktop_dir(),
  271. #[cfg(not(target_os = "android"))]
  272. BaseDirectory::Executable => resolver.executable_dir(),
  273. #[cfg(not(target_os = "android"))]
  274. BaseDirectory::Font => resolver.font_dir(),
  275. #[cfg(not(target_os = "android"))]
  276. BaseDirectory::Home => resolver.home_dir(),
  277. #[cfg(not(target_os = "android"))]
  278. BaseDirectory::Runtime => resolver.runtime_dir(),
  279. #[cfg(not(target_os = "android"))]
  280. BaseDirectory::Template => resolver.template_dir(),
  281. }?;
  282. if let Some(path) = path {
  283. // use the same path resolution mechanism as the bundler's resource injection algorithm
  284. if resolve_resource {
  285. let mut resource_path = PathBuf::new();
  286. for component in path.components() {
  287. match component {
  288. Component::Prefix(_) => {}
  289. Component::RootDir => resource_path.push("_root_"),
  290. Component::CurDir => {}
  291. Component::ParentDir => resource_path.push("_up_"),
  292. Component::Normal(p) => resource_path.push(p),
  293. }
  294. }
  295. base_dir_path.push(resource_path);
  296. } else {
  297. base_dir_path.push(path);
  298. }
  299. }
  300. Ok(base_dir_path)
  301. }
  302. #[cfg(test)]
  303. mod test {
  304. use super::SafePathBuf;
  305. use quickcheck::{Arbitrary, Gen};
  306. use std::path::PathBuf;
  307. impl Arbitrary for SafePathBuf {
  308. fn arbitrary(g: &mut Gen) -> Self {
  309. Self(PathBuf::arbitrary(g))
  310. }
  311. fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
  312. Box::new(self.0.shrink().map(SafePathBuf))
  313. }
  314. }
  315. }