idt/downloaders/http/
deflate.rs1use std::fs::File;
2use std::io::BufReader;
3use std::io::Read;
4use std::path::Path;
5use std::path::PathBuf;
6
7use flate2::read::GzDecoder;
8use rootcause::prelude::ResultExt;
9use tar::Archive;
10use xz2::read::XzDecoder;
11
12pub enum HttpDeflateOption<'a> {
13 DecompressGz {
14 dest_path: &'a Path,
15 },
16 ExtractTarGz {
17 dest_dir: &'a Path,
18 dest_name: Option<&'a str>,
23 },
24 ExtractTarXz {
25 dest_dir: &'a Path,
26 dest_name: Option<&'a str>,
27 },
28 ExtractZip {
29 dest_dir: &'a Path,
30 dest_name: Option<&'a str>,
31 },
32 WriteTo {
33 dest_path: &'a Path,
34 },
35}
36
37impl HttpDeflateOption<'_> {
38 pub fn process(&self, tmp_file: &Path) -> rootcause::Result<PathBuf> {
43 match self {
44 Self::DecompressGz { dest_path } => {
45 let input = File::open(tmp_file)
46 .context("error opening tmp file for gz decompression")
47 .attach_with(|| format!("path={}", tmp_file.display()))?;
48 let mut decoder = GzDecoder::new(input);
49
50 let mut dest = File::create(dest_path)
51 .context("error creating dest file")
52 .attach_with(|| format!("path={}", dest_path.display()))?;
53 std::io::copy(&mut decoder, &mut dest)
54 .context("error decompressing gz to dest file")
55 .attach_with(|| format!("path={}", dest_path.display()))?;
56
57 Ok(dest_path.into())
58 }
59 Self::ExtractTarGz { dest_dir, dest_name } => {
60 let input = File::open(tmp_file)
61 .context("error opening tmp file for tar.gz extraction")
62 .attach_with(|| format!("path={}", tmp_file.display()))?;
63 let decoder = GzDecoder::new(input);
64 let archive = Archive::new(decoder);
65
66 Ok(extract_tar(archive, tmp_file, dest_dir, *dest_name)?)
67 }
68 Self::ExtractTarXz { dest_dir, dest_name } => {
69 let input = File::open(tmp_file)
70 .context("error opening tmp file for tar.xz extraction")
71 .attach_with(|| format!("path={}", tmp_file.display()))?;
72 let decoder = XzDecoder::new(input);
73 let archive = Archive::new(decoder);
74
75 Ok(extract_tar(archive, tmp_file, dest_dir, *dest_name)?)
76 }
77 Self::ExtractZip { dest_dir, dest_name } => {
78 let input = File::open(tmp_file)
79 .context("error opening tmp file for zip extraction")
80 .attach_with(|| format!("path={}", tmp_file.display()))?;
81 let reader = BufReader::new(input);
82 let mut archive = zip::ZipArchive::new(reader)
83 .context("error reading zip archive")
84 .attach_with(|| format!("path={}", tmp_file.display()))?;
85
86 if let Some(dest_name) = dest_name {
87 let mut entry = archive
88 .by_name(dest_name)
89 .context("error finding entry in zip archive")
90 .attach_with(|| format!("path={}", tmp_file.display()))
91 .attach_with(|| format!("entry={dest_name}"))?;
92 let dest_path = dest_dir.join(dest_name);
93 let mut dest = File::create(&dest_path)
94 .context("error creating dest file for zip entry")
95 .attach_with(|| format!("path={}", dest_path.display()))?;
96 std::io::copy(&mut entry, &mut dest)
97 .context("error extracting zip entry")
98 .attach_with(|| format!("path={}", tmp_file.display()))
99 .attach_with(|| format!("entry={dest_name}"))?;
100
101 Ok(dest_path)
102 } else {
103 archive
104 .extract(dest_dir)
105 .context("error extracting zip archive")
106 .attach_with(|| format!("path={}", tmp_file.display()))
107 .attach_with(|| format!("dest_dir={}", dest_dir.display()))?;
108
109 Ok(dest_dir.into())
110 }
111 }
112 Self::WriteTo { dest_path } => {
113 std::fs::copy(tmp_file, dest_path)
115 .context("error copying tmp file to dest")
116 .attach_with(|| format!("src={}", tmp_file.display()))
117 .attach_with(|| format!("dest={}", dest_path.display()))?;
118
119 Ok(dest_path.into())
120 }
121 }
122 }
123}
124
125pub struct ChecksumSource<'a> {
127 pub checksums_url: &'a str,
129 pub filename: &'a str,
131}
132
133fn extract_tar<R: Read>(
136 mut archive: Archive<R>,
137 archive_path: &Path,
138 dest_dir: &Path,
139 dest_name: Option<&str>,
140) -> rootcause::Result<PathBuf> {
141 if let Some(dest_name) = dest_name {
142 for entry in archive
143 .entries()
144 .context("error reading tar entries")
145 .attach_with(|| format!("path={}", archive_path.display()))?
146 {
147 let mut entry = entry
148 .context("error reading tar entry")
149 .attach_with(|| format!("entry={dest_name}"))?;
150 let entry_path = entry
151 .path()
152 .context("error reading tar entry path")
153 .attach_with(|| format!("entry={dest_name}"))?;
154 if entry_path.to_str() == Some(dest_name) {
155 let dest_path = dest_dir.join(dest_name);
156 entry
157 .unpack(&dest_path)
158 .context("error extracting tar entry")
159 .attach_with(|| format!("entry={dest_name}"))?;
160 return Ok(dest_path);
161 }
162 }
163 Err(rootcause::report!("entry not found in tar archive")).attach_with(|| format!("entry={dest_name}"))
164 } else {
165 archive
166 .unpack(dest_dir)
167 .context("error extracting tar archive")
168 .attach_with(|| format!("dest_dir={}", dest_dir.display()))?;
169 Ok(dest_dir.into())
170 }
171}