mod.rs 11 KB

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