1use std::io;
6use std::io::Read;
7use std::path::{Path, PathBuf};
8
9use data_encoding::HEXLOWER;
10use flate2::read::GzDecoder;
11use tar::Archive;
12use xz2::read::XzDecoder;
13
14use crate::tag::TagKind;
15
16pub fn checksums_match(compressed_tar: &[u8], expected_sum: &[u8]) -> bool {
40 let expected_sum = String::from_utf8_lossy(expected_sum)
41 .split_whitespace()
42 .next()
43 .map(String::from)
44 .unwrap_or(String::new());
45
46 let digest = ring::digest::digest(&ring::digest::SHA512, compressed_tar);
47 let sum = HEXLOWER.encode(digest.as_ref());
48
49 expected_sum.eq(&sum)
50}
51
52pub fn extract_compressed(
94 kind: &TagKind,
95 compressed_tar: impl Read,
96 extract_destination: &Path,
97) -> Result<PathBuf, io::Error> {
98 let extracted_dst = match kind {
99 TagKind::Proton => {
100 let decoder = GzDecoder::new(compressed_tar);
101 extract_tar(decoder, extract_destination)?
102 }
103 TagKind::Wine { .. } => {
104 let decoder = XzDecoder::new(compressed_tar);
105 extract_tar(decoder, extract_destination)?
106 }
107 };
108
109 Ok(extracted_dst)
110}
111
112fn extract_tar(decoder: impl Read, extract_destination: &Path) -> Result<PathBuf, std::io::Error> {
113 let mut archive = Archive::new(decoder);
114
115 let mut iter = archive.entries()?;
116 let first_entry = &mut iter.next().unwrap()?;
117
118 let dir_name = first_entry.path().unwrap().into_owned();
119
120 first_entry.unpack_in(extract_destination)?;
121 for entry in iter {
122 let mut entry = entry?;
123 entry.unpack_in(extract_destination)?;
124 }
125
126 Ok(extract_destination.join(dir_name))
127}
128
129#[cfg(test)]
130mod checksum_tests {
131 use std::fs;
132
133 use super::*;
134
135 #[test]
136 fn check_if_equal_checksums_where_expected_sum_contains_file_name_match() {
137 let tar = fs::read("test_resources/assets/test.tar.gz").unwrap();
138 let checksum =
139 "f2ad7b96bb24ae5fa71398127927b22c8c11eba2d3578df5a47e6ad5b5a06b0c4c66d25cf53bed0d9ed0864b76aea73794cc4be7f01249f43b796f70d068f972 test.tar.gz";
140
141 let is_equal = checksums_match(&tar, checksum.as_bytes());
142 assert!(is_equal);
143 }
144
145 #[test]
146 fn check_if_equal_checksums_match() {
147 let tar = fs::read("test_resources/assets/test.tar.gz").unwrap();
148 let checksum =
149 "f2ad7b96bb24ae5fa71398127927b22c8c11eba2d3578df5a47e6ad5b5a06b0c4c66d25cf53bed0d9ed0864b76aea73794cc4be7f01249f43b796f70d068f972";
150
151 let is_equal = checksums_match(&tar, checksum.as_bytes());
152 assert!(is_equal);
153 }
154
155 #[test]
156 fn check_if_not_equal_checksums_do_not_match() {
157 let tar = fs::read("test_resources/assets/test.tar.gz").unwrap();
158 let checksum = "unreal-checksum";
159
160 let is_equal = checksums_match(&tar, checksum.as_bytes());
161 assert!(!is_equal);
162 }
163}
164
165#[cfg(test)]
166mod extraction_tests {
167 use std::fs::File;
168 use std::io;
169
170 use assert_fs::assert::PathAssert;
171 use assert_fs::fixture::PathChild;
172 use assert_fs::TempDir;
173 use test_case::test_case;
174
175 use super::*;
176
177 #[test]
178 fn extract_proton_ge_release_with_correct_tag_kind() {
179 let tmp_dir = TempDir::new().unwrap();
180
181 let archive = File::open("test_resources/assets/test.tar.gz").unwrap();
182 let kind = TagKind::Proton;
183
184 let dst = super::extract_compressed(&kind, archive, tmp_dir.path()).unwrap();
185
186 assert_eq!(dst, tmp_dir.join("test"));
187 tmp_dir
188 .child(&dst)
189 .assert(predicates::path::exists())
190 .child(dst.join("hello-world.txt"))
191 .assert(predicates::path::exists());
192 tmp_dir
193 .child(&dst)
194 .child(dst.join("nested"))
195 .assert(predicates::path::exists())
196 .child(dst.join("nested/nested.txt"));
197 tmp_dir
198 .child(&dst)
199 .child(dst.join("other-file.txt"))
200 .assert(predicates::path::exists());
201
202 tmp_dir.close().unwrap();
203 }
204
205 #[test]
206 fn extract_proton_ge_release_with_wrong_tag_kind() {
207 let tmp_dir = TempDir::new().unwrap();
208
209 let archive = File::open("test_resources/assets/test.tar.gz").unwrap();
210 let kind = TagKind::wine();
211
212 let result = extract_compressed(&kind, archive, tmp_dir.path());
213 assert!(result.is_err());
214
215 let err = result.unwrap_err();
216 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
217
218 assert!(tmp_dir.iter().size_hint().eq(&(0, None)));
219 tmp_dir.close().unwrap();
220 }
221
222 #[test_case(TagKind::wine(); "Running with Wine kind")]
223 #[test_case(TagKind::lol(); "Running with LoL kind")]
224 fn extract_wine_ge_release_with_correct_tag_kind(kind: TagKind) {
225 let tmp_dir = TempDir::new().unwrap();
226
227 let archive = File::open("test_resources/assets/test.tar.xz").unwrap();
228
229 let dst = super::extract_compressed(&kind, archive, tmp_dir.path()).unwrap();
230
231 assert_eq!(dst, tmp_dir.join("test"));
232 tmp_dir
233 .child(&dst)
234 .assert(predicates::path::exists())
235 .child(dst.join("hello-world.txt"))
236 .assert(predicates::path::exists());
237 tmp_dir
238 .child(&dst)
239 .child(dst.join("nested"))
240 .assert(predicates::path::exists())
241 .child(dst.join("nested/nested.txt"));
242 tmp_dir
243 .child(&dst)
244 .child(dst.join("other-file.txt"))
245 .assert(predicates::path::exists());
246
247 tmp_dir.close().unwrap();
248 }
249
250 #[test]
251 fn extract_wine_ge_release_with_wrong_tag_kind() {
252 let tmp_dir = TempDir::new().unwrap();
253 let kind = TagKind::Proton;
254
255 let archive = File::open("test_resources/assets/test.tar.xz").unwrap();
256 let result = super::extract_compressed(&kind, archive, tmp_dir.path());
257 assert!(result.is_err());
258
259 let err = result.unwrap_err();
260 assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
261
262 assert!(tmp_dir.iter().size_hint().eq(&(0, None)));
263 tmp_dir.close().unwrap();
264 }
265}