mod.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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};
  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)]
  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 Desktop directory.
  111. #[cfg(not(target_os = "android"))]
  112. Desktop,
  113. /// The Executable directory.
  114. #[cfg(not(target_os = "android"))]
  115. Executable,
  116. /// The Font directory.
  117. #[cfg(not(target_os = "android"))]
  118. Font,
  119. /// The Home directory.
  120. #[cfg(not(target_os = "android"))]
  121. Home,
  122. /// The Runtime directory.
  123. #[cfg(not(target_os = "android"))]
  124. Runtime,
  125. /// The Template directory.
  126. #[cfg(not(target_os = "android"))]
  127. Template,
  128. }
  129. impl BaseDirectory {
  130. /// Gets the variable that represents this [`BaseDirectory`] for string paths.
  131. pub fn variable(self) -> &'static str {
  132. match self {
  133. Self::Audio => "$AUDIO",
  134. Self::Cache => "$CACHE",
  135. Self::Config => "$CONFIG",
  136. Self::Data => "$DATA",
  137. Self::LocalData => "$LOCALDATA",
  138. Self::Document => "$DOCUMENT",
  139. Self::Download => "$DOWNLOAD",
  140. Self::Picture => "$PICTURE",
  141. Self::Public => "$PUBLIC",
  142. Self::Video => "$VIDEO",
  143. Self::Resource => "$RESOURCE",
  144. Self::Temp => "$TEMP",
  145. Self::AppConfig => "$APPCONFIG",
  146. Self::AppData => "$APPDATA",
  147. Self::AppLocalData => "$APPLOCALDATA",
  148. Self::AppCache => "$APPCACHE",
  149. Self::AppLog => "$APPLOG",
  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::Home => "$HOME",
  158. #[cfg(not(target_os = "android"))]
  159. Self::Runtime => "$RUNTIME",
  160. #[cfg(not(target_os = "android"))]
  161. Self::Template => "$TEMPLATE",
  162. }
  163. }
  164. /// Gets the [`BaseDirectory`] associated with the given variable, or [`None`] if the variable doesn't match any.
  165. pub fn from_variable(variable: &str) -> Option<Self> {
  166. let res = match variable {
  167. "$AUDIO" => Self::Audio,
  168. "$CACHE" => Self::Cache,
  169. "$CONFIG" => Self::Config,
  170. "$DATA" => Self::Data,
  171. "$LOCALDATA" => Self::LocalData,
  172. "$DOCUMENT" => Self::Document,
  173. "$DOWNLOAD" => Self::Download,
  174. "$PICTURE" => Self::Picture,
  175. "$PUBLIC" => Self::Public,
  176. "$VIDEO" => Self::Video,
  177. "$RESOURCE" => Self::Resource,
  178. "$TEMP" => Self::Temp,
  179. "$APPCONFIG" => Self::AppConfig,
  180. "$APPDATA" => Self::AppData,
  181. "$APPLOCALDATA" => Self::AppLocalData,
  182. "$APPCACHE" => Self::AppCache,
  183. "$APPLOG" => Self::AppLog,
  184. #[cfg(not(target_os = "android"))]
  185. "$DESKTOP" => Self::Desktop,
  186. #[cfg(not(target_os = "android"))]
  187. "$EXE" => Self::Executable,
  188. #[cfg(not(target_os = "android"))]
  189. "$FONT" => Self::Font,
  190. #[cfg(not(target_os = "android"))]
  191. "$HOME" => Self::Home,
  192. #[cfg(not(target_os = "android"))]
  193. "$RUNTIME" => Self::Runtime,
  194. #[cfg(not(target_os = "android"))]
  195. "$TEMPLATE" => Self::Template,
  196. _ => return None,
  197. };
  198. Some(res)
  199. }
  200. }
  201. impl<R: Runtime> PathResolver<R> {
  202. /// Resolves the path with the base directory.
  203. ///
  204. /// # Examples
  205. ///
  206. /// ```rust,no_run
  207. /// use tauri::{path::BaseDirectory, Manager};
  208. /// tauri::Builder::default()
  209. /// .setup(|app| {
  210. /// let path = app.path().resolve("path/to/something", BaseDirectory::Config)?;
  211. /// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.config/path/to/something");
  212. /// Ok(())
  213. /// });
  214. /// ```
  215. pub fn resolve<P: AsRef<Path>>(&self, path: P, base_directory: BaseDirectory) -> Result<PathBuf> {
  216. resolve_path::<R>(self, base_directory, Some(path.as_ref().to_path_buf()))
  217. }
  218. /// Parse the given path, resolving a [`BaseDirectory`] variable if the path starts with one.
  219. ///
  220. /// # Examples
  221. ///
  222. /// ```rust,no_run
  223. /// use tauri::Manager;
  224. /// tauri::Builder::default()
  225. /// .setup(|app| {
  226. /// let path = app.path().parse("$HOME/.bashrc")?;
  227. /// assert_eq!(path.to_str().unwrap(), "/home/${whoami}/.bashrc");
  228. /// Ok(())
  229. /// });
  230. /// ```
  231. pub fn parse<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
  232. let mut p = PathBuf::new();
  233. let mut components = path.as_ref().components();
  234. match components.next() {
  235. Some(Component::Normal(str)) => {
  236. if let Some(base_directory) = BaseDirectory::from_variable(&str.to_string_lossy()) {
  237. p.push(resolve_path::<R>(self, base_directory, None)?);
  238. } else {
  239. p.push(str);
  240. }
  241. }
  242. Some(component) => p.push(component),
  243. None => (),
  244. }
  245. for component in components {
  246. if let Component::ParentDir = component {
  247. continue;
  248. }
  249. p.push(component);
  250. }
  251. Ok(p)
  252. }
  253. }
  254. fn resolve_path<R: Runtime>(
  255. resolver: &PathResolver<R>,
  256. directory: BaseDirectory,
  257. path: Option<PathBuf>,
  258. ) -> Result<PathBuf> {
  259. let resolve_resource = matches!(directory, BaseDirectory::Resource);
  260. let mut base_dir_path = match directory {
  261. BaseDirectory::Audio => resolver.audio_dir(),
  262. BaseDirectory::Cache => resolver.cache_dir(),
  263. BaseDirectory::Config => resolver.config_dir(),
  264. BaseDirectory::Data => resolver.data_dir(),
  265. BaseDirectory::LocalData => resolver.local_data_dir(),
  266. BaseDirectory::Document => resolver.document_dir(),
  267. BaseDirectory::Download => resolver.download_dir(),
  268. BaseDirectory::Picture => resolver.picture_dir(),
  269. BaseDirectory::Public => resolver.public_dir(),
  270. BaseDirectory::Video => resolver.video_dir(),
  271. BaseDirectory::Resource => resolver.resource_dir(),
  272. BaseDirectory::Temp => resolver.temp_dir(),
  273. BaseDirectory::AppConfig => resolver.app_config_dir(),
  274. BaseDirectory::AppData => resolver.app_data_dir(),
  275. BaseDirectory::AppLocalData => resolver.app_local_data_dir(),
  276. BaseDirectory::AppCache => resolver.app_cache_dir(),
  277. BaseDirectory::AppLog => resolver.app_log_dir(),
  278. #[cfg(not(target_os = "android"))]
  279. BaseDirectory::Desktop => resolver.desktop_dir(),
  280. #[cfg(not(target_os = "android"))]
  281. BaseDirectory::Executable => resolver.executable_dir(),
  282. #[cfg(not(target_os = "android"))]
  283. BaseDirectory::Font => resolver.font_dir(),
  284. #[cfg(not(target_os = "android"))]
  285. BaseDirectory::Home => resolver.home_dir(),
  286. #[cfg(not(target_os = "android"))]
  287. BaseDirectory::Runtime => resolver.runtime_dir(),
  288. #[cfg(not(target_os = "android"))]
  289. BaseDirectory::Template => resolver.template_dir(),
  290. }?;
  291. if let Some(path) = path {
  292. // use the same path resolution mechanism as the bundler's resource injection algorithm
  293. if resolve_resource {
  294. let mut resource_path = PathBuf::new();
  295. for component in path.components() {
  296. match component {
  297. Component::Prefix(_) => {}
  298. Component::RootDir => resource_path.push("_root_"),
  299. Component::CurDir => {}
  300. Component::ParentDir => resource_path.push("_up_"),
  301. Component::Normal(p) => resource_path.push(p),
  302. }
  303. }
  304. base_dir_path.push(resource_path);
  305. } else {
  306. base_dir_path.push(path);
  307. }
  308. }
  309. Ok(base_dir_path)
  310. }
  311. #[cfg(test)]
  312. mod test {
  313. use super::SafePathBuf;
  314. use quickcheck::{Arbitrary, Gen};
  315. use std::path::PathBuf;
  316. impl Arbitrary for SafePathBuf {
  317. fn arbitrary(g: &mut Gen) -> Self {
  318. Self(PathBuf::arbitrary(g))
  319. }
  320. fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
  321. Box::new(self.0.shrink().map(SafePathBuf))
  322. }
  323. }
  324. }