extract.rs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // Copyright 2019-2022 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{
  5. borrow::Cow,
  6. fs,
  7. io::{self, Cursor, Read, Seek},
  8. path::{self, Path, PathBuf},
  9. };
  10. /// The archive reader.
  11. #[derive(Debug)]
  12. pub enum ArchiveReader<R: Read + Seek> {
  13. /// A plain reader.
  14. Plain(R),
  15. /// A GZ- compressed reader (decoder).
  16. GzCompressed(Box<flate2::read::GzDecoder<R>>),
  17. }
  18. impl<R: Read + Seek> Read for ArchiveReader<R> {
  19. fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
  20. match self {
  21. Self::Plain(r) => r.read(buf),
  22. Self::GzCompressed(decoder) => decoder.read(buf),
  23. }
  24. }
  25. }
  26. impl<R: Read + Seek> ArchiveReader<R> {
  27. #[allow(dead_code)]
  28. fn get_mut(&mut self) -> &mut R {
  29. match self {
  30. Self::Plain(r) => r,
  31. Self::GzCompressed(decoder) => decoder.get_mut(),
  32. }
  33. }
  34. }
  35. /// The supported archive formats.
  36. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  37. #[non_exhaustive]
  38. pub enum ArchiveFormat {
  39. /// Tar archive.
  40. Tar(Option<Compression>),
  41. /// Zip archive.
  42. Zip,
  43. }
  44. /// The supported compression types.
  45. #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  46. #[non_exhaustive]
  47. pub enum Compression {
  48. /// Gz compression (e.g. `.tar.gz` archives)
  49. Gz,
  50. }
  51. /// The zip entry.
  52. pub struct ZipEntry {
  53. path: PathBuf,
  54. is_dir: bool,
  55. file_contents: Vec<u8>,
  56. }
  57. /// A read-only view into an entry of an archive.
  58. #[non_exhaustive]
  59. pub enum Entry<'a, R: Read> {
  60. /// An entry of a tar archive.
  61. #[non_exhaustive]
  62. Tar(Box<tar::Entry<'a, R>>),
  63. /// An entry of a zip archive.
  64. #[non_exhaustive]
  65. Zip(ZipEntry),
  66. }
  67. impl<'a, R: Read> Entry<'a, R> {
  68. /// The entry path.
  69. pub fn path(&self) -> crate::api::Result<Cow<'_, Path>> {
  70. match self {
  71. Self::Tar(e) => e.path().map_err(Into::into),
  72. Self::Zip(e) => Ok(Cow::Borrowed(&e.path)),
  73. }
  74. }
  75. /// Extract this entry into `into_path`.
  76. /// If it's a directory, the target will be created, if it's a file, it'll be extracted at this location.
  77. /// Note: You need to include the complete path, with file name and extension.
  78. pub fn extract(self, into_path: &path::Path) -> crate::api::Result<()> {
  79. match self {
  80. Self::Tar(mut entry) => {
  81. // determine if it's a file or a directory
  82. if entry.header().entry_type() == tar::EntryType::Directory {
  83. // this is a directory, lets create it
  84. match fs::create_dir_all(into_path) {
  85. Ok(_) => (),
  86. Err(e) => {
  87. if e.kind() != io::ErrorKind::AlreadyExists {
  88. return Err(e.into());
  89. }
  90. }
  91. }
  92. } else {
  93. let mut out_file = fs::File::create(into_path)?;
  94. io::copy(&mut entry, &mut out_file)?;
  95. // make sure we set permissions
  96. if let Ok(mode) = entry.header().mode() {
  97. set_perms(into_path, Some(&mut out_file), mode, true)?;
  98. }
  99. }
  100. }
  101. Self::Zip(entry) => {
  102. if entry.is_dir {
  103. // this is a directory, lets create it
  104. match fs::create_dir_all(into_path) {
  105. Ok(_) => (),
  106. Err(e) => {
  107. if e.kind() != io::ErrorKind::AlreadyExists {
  108. return Err(e.into());
  109. }
  110. }
  111. }
  112. } else {
  113. let mut out_file = fs::File::create(into_path)?;
  114. io::copy(&mut Cursor::new(entry.file_contents), &mut out_file)?;
  115. }
  116. }
  117. }
  118. Ok(())
  119. }
  120. }
  121. /// The extract manager to retrieve files from archives.
  122. pub struct Extract<'a, R: Read + Seek> {
  123. reader: ArchiveReader<R>,
  124. archive_format: ArchiveFormat,
  125. tar_archive: Option<tar::Archive<&'a mut ArchiveReader<R>>>,
  126. }
  127. impl<'a, R: std::fmt::Debug + Read + Seek> std::fmt::Debug for Extract<'a, R> {
  128. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  129. f.debug_struct("Extract")
  130. .field("reader", &self.reader)
  131. .field("archive_format", &self.archive_format)
  132. .finish()
  133. }
  134. }
  135. impl<'a, R: Read + Seek> Extract<'a, R> {
  136. /// Create archive from reader.
  137. pub fn from_cursor(mut reader: R, archive_format: ArchiveFormat) -> Extract<'a, R> {
  138. if reader.rewind().is_err() {
  139. #[cfg(debug_assertions)]
  140. eprintln!("Could not seek to start of the file");
  141. }
  142. let compression = if let ArchiveFormat::Tar(compression) = archive_format {
  143. compression
  144. } else {
  145. None
  146. };
  147. Extract {
  148. reader: match compression {
  149. Some(Compression::Gz) => {
  150. ArchiveReader::GzCompressed(Box::new(flate2::read::GzDecoder::new(reader)))
  151. }
  152. _ => ArchiveReader::Plain(reader),
  153. },
  154. archive_format,
  155. tar_archive: None,
  156. }
  157. }
  158. /// Reads the archive content.
  159. pub fn with_files<
  160. E: Into<crate::api::Error>,
  161. F: FnMut(Entry<'_, &mut ArchiveReader<R>>) -> std::result::Result<bool, E>,
  162. >(
  163. &'a mut self,
  164. mut f: F,
  165. ) -> crate::api::Result<()> {
  166. match self.archive_format {
  167. ArchiveFormat::Tar(_) => {
  168. let archive = tar::Archive::new(&mut self.reader);
  169. self.tar_archive.replace(archive);
  170. for entry in self.tar_archive.as_mut().unwrap().entries()? {
  171. let entry = entry?;
  172. if entry.path().is_ok() {
  173. let stop = f(Entry::Tar(Box::new(entry))).map_err(Into::into)?;
  174. if stop {
  175. break;
  176. }
  177. }
  178. }
  179. }
  180. ArchiveFormat::Zip => {
  181. #[cfg(feature = "fs-extract-api")]
  182. {
  183. let mut archive = zip::ZipArchive::new(self.reader.get_mut())?;
  184. let file_names = archive
  185. .file_names()
  186. .map(|f| f.to_string())
  187. .collect::<Vec<String>>();
  188. for path in file_names {
  189. let mut zip_file = archive.by_name(&path)?;
  190. let is_dir = zip_file.is_dir();
  191. let mut file_contents = Vec::new();
  192. zip_file.read_to_end(&mut file_contents)?;
  193. let stop = f(Entry::Zip(ZipEntry {
  194. path: path.into(),
  195. is_dir,
  196. file_contents,
  197. }))
  198. .map_err(Into::into)?;
  199. if stop {
  200. break;
  201. }
  202. }
  203. }
  204. }
  205. }
  206. Ok(())
  207. }
  208. /// Extract an entire source archive into a specified path. If the source is a single compressed
  209. /// file and not an archive, it will be extracted into a file with the same name inside of
  210. /// `into_dir`.
  211. pub fn extract_into(&mut self, into_dir: &path::Path) -> crate::api::Result<()> {
  212. match self.archive_format {
  213. ArchiveFormat::Tar(_) => {
  214. let mut archive = tar::Archive::new(&mut self.reader);
  215. archive.unpack(into_dir)?;
  216. }
  217. ArchiveFormat::Zip => {
  218. #[cfg(feature = "fs-extract-api")]
  219. {
  220. let mut archive = zip::ZipArchive::new(self.reader.get_mut())?;
  221. for i in 0..archive.len() {
  222. let mut file = archive.by_index(i)?;
  223. // Decode the file name from raw bytes instead of using file.name() directly.
  224. // file.name() uses String::from_utf8_lossy() which may return messy characters
  225. // such as: 爱交易.app/, that does not work as expected.
  226. // Here we require the file name must be a valid UTF-8.
  227. let file_name = String::from_utf8(file.name_raw().to_vec())?;
  228. let out_path = into_dir.join(file_name);
  229. if file.is_dir() {
  230. fs::create_dir_all(&out_path)?;
  231. } else {
  232. if let Some(out_path_parent) = out_path.parent() {
  233. fs::create_dir_all(out_path_parent)?;
  234. }
  235. let mut out_file = fs::File::create(&out_path)?;
  236. io::copy(&mut file, &mut out_file)?;
  237. }
  238. // Get and Set permissions
  239. #[cfg(unix)]
  240. {
  241. use std::os::unix::fs::PermissionsExt;
  242. if let Some(mode) = file.unix_mode() {
  243. fs::set_permissions(&out_path, fs::Permissions::from_mode(mode))?;
  244. }
  245. }
  246. }
  247. }
  248. }
  249. }
  250. Ok(())
  251. }
  252. }
  253. fn set_perms(
  254. dst: &Path,
  255. f: Option<&mut std::fs::File>,
  256. mode: u32,
  257. preserve: bool,
  258. ) -> crate::api::Result<()> {
  259. _set_perms(dst, f, mode, preserve).map_err(|_| {
  260. crate::api::Error::Extract(format!(
  261. "failed to set permissions to {:o} \
  262. for `{}`",
  263. mode,
  264. dst.display()
  265. ))
  266. })
  267. }
  268. #[cfg(unix)]
  269. fn _set_perms(
  270. dst: &Path,
  271. f: Option<&mut std::fs::File>,
  272. mode: u32,
  273. preserve: bool,
  274. ) -> io::Result<()> {
  275. use std::os::unix::prelude::*;
  276. let mode = if preserve { mode } else { mode & 0o777 };
  277. let perm = fs::Permissions::from_mode(mode as _);
  278. match f {
  279. Some(f) => f.set_permissions(perm),
  280. None => fs::set_permissions(dst, perm),
  281. }
  282. }
  283. #[cfg(windows)]
  284. fn _set_perms(
  285. dst: &Path,
  286. f: Option<&mut std::fs::File>,
  287. mode: u32,
  288. _preserve: bool,
  289. ) -> io::Result<()> {
  290. if mode & 0o200 == 0o200 {
  291. return Ok(());
  292. }
  293. match f {
  294. Some(f) => {
  295. let mut perm = f.metadata()?.permissions();
  296. perm.set_readonly(true);
  297. f.set_permissions(perm)
  298. }
  299. None => {
  300. let mut perm = fs::metadata(dst)?.permissions();
  301. perm.set_readonly(true);
  302. fs::set_permissions(dst, perm)
  303. }
  304. }
  305. }