Skip to main content

ghactions_toolcache/
archives.rs

1//! # ToolCache Archives
2#[cfg(not(feature = "zip"))]
3use std::path::Path;
4use std::path::PathBuf;
5
6use super::ToolCache;
7use crate::ToolCacheError;
8
9impl ToolCache {
10    /// Extract an archive
11    pub async fn extract_archive(
12        &self,
13        archive: &PathBuf,
14        output: &PathBuf,
15    ) -> Result<(), ToolCacheError> {
16        let Some(extension) = archive.extension() else {
17            return Err(ToolCacheError::IoError(std::io::Error::new(
18                std::io::ErrorKind::InvalidInput,
19                "Unknown archive format",
20            )));
21        };
22
23        match extension.to_str() {
24            Some("zip") => self.extract_zip(archive, output).await,
25            Some("gz") | Some("tgz") => self.extract_targz(archive, output).await,
26            Some("tar") => self.extract_tarball(archive, output).await,
27            _ => Err(ToolCacheError::IoError(std::io::Error::new(
28                std::io::ErrorKind::InvalidInput,
29                "Unknown archive format",
30            )))?,
31        }
32    }
33
34    /// Extract a tarball (gzip compressed) natively in Rust using
35    /// `flate2` and `tar` crates
36    #[cfg(feature = "tarball")]
37    async fn extract_targz(
38        &self,
39        tarball: &PathBuf,
40        output: &PathBuf,
41    ) -> Result<(), ToolCacheError> {
42        log::debug!("Extracting tarball gzip: {:?}", tarball);
43
44        // TODO: Security considerations?
45        let file = std::fs::File::open(tarball)?;
46        let decoder = flate2::read::GzDecoder::new(file);
47        let mut archive = tar::Archive::new(decoder);
48        archive.set_preserve_permissions(true);
49
50        archive.unpack(output)?;
51
52        Ok(())
53    }
54
55    /// Extract a tarball using the `tar` command
56    #[cfg(not(feature = "tarball"))]
57    async fn extract_targz(
58        &self,
59        tarball: &PathBuf,
60        output: &PathBuf,
61    ) -> Result<(), ToolCacheError> {
62        tokio::process::Command::new("tar")
63            .arg("-xzf")
64            .arg(tarball)
65            .arg("-C")
66            .arg(output)
67            .spawn()?
68            .wait()
69            .await?;
70
71        if !output.exists() {
72            return Err(ToolCacheError::IoError(std::io::Error::new(
73                std::io::ErrorKind::NotFound,
74                "Failed to extract tarball",
75            )));
76        }
77
78        Ok(())
79    }
80
81    /// Extract a tarball (tar compressed) natively in Rust using
82    /// `flate2` and `tar` crates
83    #[cfg(feature = "tarball")]
84    async fn extract_tarball(
85        &self,
86        tarball: &PathBuf,
87        output: &PathBuf,
88    ) -> Result<(), ToolCacheError> {
89        log::debug!("Extracting tarball: {:?}", tarball);
90
91        let file = std::fs::File::open(tarball)?;
92        let mut archive = tar::Archive::new(file);
93        archive.unpack(output)?;
94
95        Ok(())
96    }
97
98    /// Extract a tarball using the `tar` command
99    #[cfg(not(feature = "tarball"))]
100    async fn extract_tarball(
101        &self,
102        tarball: &PathBuf,
103        output: &PathBuf,
104    ) -> Result<(), ToolCacheError> {
105        tokio::process::Command::new("tar")
106            .arg("-xf")
107            .arg(tarball)
108            .arg("-C")
109            .arg(output)
110            .spawn()?
111            .wait()
112            .await?;
113
114        if !output.exists() {
115            return Err(ToolCacheError::IoError(std::io::Error::new(
116                std::io::ErrorKind::NotFound,
117                "Failed to extract tarball",
118            )));
119        }
120
121        Ok(())
122    }
123
124    #[cfg(feature = "zip")]
125    /// Extract a zip file natively in Rust using the `zip` crate
126    async fn extract_zip(&self, zipfile: &PathBuf, output: &PathBuf) -> Result<(), ToolCacheError> {
127        log::debug!("Extracting zipfile: {:?}", zipfile);
128
129        let file = std::fs::File::open(zipfile)?;
130        let mut archive = zip::ZipArchive::new(file)?;
131        archive.extract(output)?;
132
133        Ok(())
134    }
135
136    /// Extract a zip file using the `unzip` command
137    ///
138    /// For native support, the `toolcache-zip` feature must be enabled.
139    #[cfg(not(feature = "zip"))]
140    async fn extract_zip(&self, zipfile: &Path, output: &PathBuf) -> Result<(), ToolCacheError> {
141        tokio::process::Command::new("unzip")
142            .arg(zipfile.display().to_string())
143            .arg("-d")
144            .arg(output)
145            .spawn()?
146            .wait()
147            .await?;
148
149        if !output.exists() {
150            return Err(ToolCacheError::IoError(std::io::Error::new(
151                std::io::ErrorKind::NotFound,
152                "Failed to extract zip file",
153            )));
154        }
155
156        Ok(())
157    }
158}