1use std::path::Path;
4
5use crate::ExtractionError;
6use crate::ExtractionReport;
7use crate::NoopProgress;
8use crate::ProgressCallback;
9use crate::Result;
10use crate::SecurityConfig;
11use crate::creation::CreationConfig;
12use crate::creation::CreationReport;
13use crate::formats::detect::ArchiveType;
14use crate::formats::detect::detect_format;
15use crate::inspection::ArchiveManifest;
16use crate::inspection::VerificationReport;
17
18pub fn extract_archive<P: AsRef<Path>, Q: AsRef<Path>>(
51 archive_path: P,
52 output_dir: Q,
53 config: &SecurityConfig,
54) -> Result<ExtractionReport> {
55 let mut noop = NoopProgress;
56 extract_archive_with_progress(archive_path, output_dir, config, &mut noop)
57}
58
59pub fn extract_archive_with_progress<P: AsRef<Path>, Q: AsRef<Path>>(
96 archive_path: P,
97 output_dir: Q,
98 config: &SecurityConfig,
99 _progress: &mut dyn ProgressCallback,
100) -> Result<ExtractionReport> {
101 let archive_path = archive_path.as_ref();
102 let output_dir = output_dir.as_ref();
103
104 let format = detect_format(archive_path)?;
106
107 match format {
109 ArchiveType::Tar => extract_tar(archive_path, output_dir, config),
110 ArchiveType::TarGz => extract_tar_gz(archive_path, output_dir, config),
111 ArchiveType::TarBz2 => extract_tar_bz2(archive_path, output_dir, config),
112 ArchiveType::TarXz => extract_tar_xz(archive_path, output_dir, config),
113 ArchiveType::TarZst => extract_tar_zst(archive_path, output_dir, config),
114 ArchiveType::Zip => extract_zip(archive_path, output_dir, config),
115 ArchiveType::SevenZ => extract_7z(archive_path, output_dir, config),
116 }
117}
118
119fn extract_tar(
120 archive_path: &Path,
121 output_dir: &Path,
122 config: &SecurityConfig,
123) -> Result<ExtractionReport> {
124 use crate::formats::TarArchive;
125 use crate::formats::traits::ArchiveFormat;
126 use std::fs::File;
127 use std::io::BufReader;
128
129 let file = File::open(archive_path)?;
130 let reader = BufReader::new(file);
131 let mut archive = TarArchive::new(reader);
132 archive.extract(output_dir, config)
133}
134
135fn extract_tar_gz(
136 archive_path: &Path,
137 output_dir: &Path,
138 config: &SecurityConfig,
139) -> Result<ExtractionReport> {
140 use crate::formats::TarArchive;
141 use crate::formats::traits::ArchiveFormat;
142 use flate2::read::GzDecoder;
143 use std::fs::File;
144 use std::io::BufReader;
145
146 let file = File::open(archive_path)?;
147 let reader = BufReader::new(file);
148 let decoder = GzDecoder::new(reader);
149 let mut archive = TarArchive::new(decoder);
150 archive.extract(output_dir, config)
151}
152
153fn extract_tar_bz2(
154 archive_path: &Path,
155 output_dir: &Path,
156 config: &SecurityConfig,
157) -> Result<ExtractionReport> {
158 use crate::formats::TarArchive;
159 use crate::formats::traits::ArchiveFormat;
160 use bzip2::read::BzDecoder;
161 use std::fs::File;
162 use std::io::BufReader;
163
164 let file = File::open(archive_path)?;
165 let reader = BufReader::new(file);
166 let decoder = BzDecoder::new(reader);
167 let mut archive = TarArchive::new(decoder);
168 archive.extract(output_dir, config)
169}
170
171fn extract_tar_xz(
172 archive_path: &Path,
173 output_dir: &Path,
174 config: &SecurityConfig,
175) -> Result<ExtractionReport> {
176 use crate::formats::TarArchive;
177 use crate::formats::traits::ArchiveFormat;
178 use std::fs::File;
179 use std::io::BufReader;
180 use xz2::read::XzDecoder;
181
182 let file = File::open(archive_path)?;
183 let reader = BufReader::new(file);
184 let decoder = XzDecoder::new(reader);
185 let mut archive = TarArchive::new(decoder);
186 archive.extract(output_dir, config)
187}
188
189fn extract_tar_zst(
190 archive_path: &Path,
191 output_dir: &Path,
192 config: &SecurityConfig,
193) -> Result<ExtractionReport> {
194 use crate::formats::TarArchive;
195 use crate::formats::traits::ArchiveFormat;
196 use std::fs::File;
197 use std::io::BufReader;
198 use zstd::stream::read::Decoder as ZstdDecoder;
199
200 let file = File::open(archive_path)?;
201 let reader = BufReader::new(file);
202 let decoder = ZstdDecoder::new(reader)?;
203 let mut archive = TarArchive::new(decoder);
204 archive.extract(output_dir, config)
205}
206
207fn extract_zip(
208 archive_path: &Path,
209 output_dir: &Path,
210 config: &SecurityConfig,
211) -> Result<ExtractionReport> {
212 use crate::formats::ZipArchive;
213 use crate::formats::traits::ArchiveFormat;
214 use std::fs::File;
215
216 let file = File::open(archive_path)?;
217 let mut archive = ZipArchive::new(file)?;
218 archive.extract(output_dir, config)
219}
220
221fn extract_7z(
222 archive_path: &Path,
223 output_dir: &Path,
224 config: &SecurityConfig,
225) -> Result<ExtractionReport> {
226 use crate::formats::SevenZArchive;
227 use crate::formats::traits::ArchiveFormat;
228 use std::fs::File;
229
230 let file = File::open(archive_path)?;
231 let mut archive = SevenZArchive::new(file)?;
232 archive.extract(output_dir, config)
233}
234
235pub fn create_archive<P: AsRef<Path>, Q: AsRef<Path>>(
268 output_path: P,
269 sources: &[Q],
270 config: &CreationConfig,
271) -> Result<CreationReport> {
272 let mut noop = NoopProgress;
273 create_archive_with_progress(output_path, sources, config, &mut noop)
274}
275
276pub fn create_archive_with_progress<P: AsRef<Path>, Q: AsRef<Path>>(
317 output_path: P,
318 sources: &[Q],
319 config: &CreationConfig,
320 progress: &mut dyn ProgressCallback,
321) -> Result<CreationReport> {
322 let output = output_path.as_ref();
323
324 let format = determine_creation_format(output, config)?;
326
327 match format {
329 ArchiveType::Tar => {
330 crate::creation::tar::create_tar_with_progress(output, sources, config, progress)
331 }
332 ArchiveType::TarGz => {
333 crate::creation::tar::create_tar_gz_with_progress(output, sources, config, progress)
334 }
335 ArchiveType::TarBz2 => {
336 crate::creation::tar::create_tar_bz2_with_progress(output, sources, config, progress)
337 }
338 ArchiveType::TarXz => {
339 crate::creation::tar::create_tar_xz_with_progress(output, sources, config, progress)
340 }
341 ArchiveType::TarZst => {
342 crate::creation::tar::create_tar_zst_with_progress(output, sources, config, progress)
343 }
344 ArchiveType::Zip => {
345 crate::creation::zip::create_zip_with_progress(output, sources, config, progress)
346 }
347 ArchiveType::SevenZ => Err(ExtractionError::InvalidArchive(
348 "7z archive creation not yet supported".into(),
349 )),
350 }
351}
352
353pub fn list_archive<P: AsRef<Path>>(
388 archive_path: P,
389 config: &SecurityConfig,
390) -> Result<ArchiveManifest> {
391 crate::inspection::list_archive(archive_path, config)
392}
393
394pub fn verify_archive<P: AsRef<Path>>(
438 archive_path: P,
439 config: &SecurityConfig,
440) -> Result<VerificationReport> {
441 crate::inspection::verify_archive(archive_path, config)
442}
443
444fn determine_creation_format(output: &Path, config: &CreationConfig) -> Result<ArchiveType> {
446 if let Some(format) = config.format {
448 return Ok(format);
449 }
450
451 detect_format(output)
453}
454
455#[cfg(test)]
456#[allow(clippy::unwrap_used)]
457mod tests {
458 use super::*;
459 use std::path::PathBuf;
460
461 #[test]
462 fn test_extract_archive_nonexistent_file() {
463 let config = SecurityConfig::default();
464 let result = extract_archive(
465 PathBuf::from("nonexistent_test.tar"),
466 PathBuf::from("/tmp/test"),
467 &config,
468 );
469 assert!(result.is_err());
471 }
472
473 #[test]
474 fn test_determine_creation_format_tar() {
475 let config = CreationConfig::default();
476 let path = PathBuf::from("archive.tar");
477 let format = determine_creation_format(&path, &config).unwrap();
478 assert_eq!(format, ArchiveType::Tar);
479 }
480
481 #[test]
482 fn test_determine_creation_format_tar_gz() {
483 let config = CreationConfig::default();
484 let path = PathBuf::from("archive.tar.gz");
485 let format = determine_creation_format(&path, &config).unwrap();
486 assert_eq!(format, ArchiveType::TarGz);
487
488 let path2 = PathBuf::from("archive.tgz");
489 let format2 = determine_creation_format(&path2, &config).unwrap();
490 assert_eq!(format2, ArchiveType::TarGz);
491 }
492
493 #[test]
494 fn test_determine_creation_format_tar_bz2() {
495 let config = CreationConfig::default();
496 let path = PathBuf::from("archive.tar.bz2");
497 let format = determine_creation_format(&path, &config).unwrap();
498 assert_eq!(format, ArchiveType::TarBz2);
499 }
500
501 #[test]
502 fn test_determine_creation_format_tar_xz() {
503 let config = CreationConfig::default();
504 let path = PathBuf::from("archive.tar.xz");
505 let format = determine_creation_format(&path, &config).unwrap();
506 assert_eq!(format, ArchiveType::TarXz);
507 }
508
509 #[test]
510 fn test_determine_creation_format_tar_zst() {
511 let config = CreationConfig::default();
512 let path = PathBuf::from("archive.tar.zst");
513 let format = determine_creation_format(&path, &config).unwrap();
514 assert_eq!(format, ArchiveType::TarZst);
515 }
516
517 #[test]
518 fn test_determine_creation_format_zip() {
519 let config = CreationConfig::default();
520 let path = PathBuf::from("archive.zip");
521 let format = determine_creation_format(&path, &config).unwrap();
522 assert_eq!(format, ArchiveType::Zip);
523 }
524
525 #[test]
526 fn test_determine_creation_format_explicit() {
527 let config = CreationConfig::default().with_format(Some(ArchiveType::TarGz));
528 let path = PathBuf::from("archive.xyz");
529 let format = determine_creation_format(&path, &config).unwrap();
530 assert_eq!(format, ArchiveType::TarGz);
531 }
532
533 #[test]
534 fn test_determine_creation_format_unknown() {
535 let config = CreationConfig::default();
536 let path = PathBuf::from("archive.rar");
537 let result = determine_creation_format(&path, &config);
538 assert!(result.is_err());
539 }
540
541 #[test]
542 fn test_extract_archive_7z_not_implemented() {
543 let dest = tempfile::TempDir::new().unwrap();
544 let path = PathBuf::from("test.7z");
545
546 let result = extract_archive(&path, dest.path(), &SecurityConfig::default());
547
548 assert!(result.is_err());
549 }
550
551 #[test]
552 fn test_create_archive_7z_not_supported() {
553 let dest = tempfile::TempDir::new().unwrap();
554 let archive_path = dest.path().join("output.7z");
555
556 let result = create_archive(&archive_path, &[] as &[&str], &CreationConfig::default());
557
558 assert!(result.is_err());
559 assert!(matches!(
560 result.unwrap_err(),
561 ExtractionError::InvalidArchive(_)
562 ));
563 }
564}