feat: add password support for decompress and list
[ouch.git] / src / commands / list.rs
blobbb309689dbed54b158dc01c5fbb99a1b46d0aba0
1 use std::{
2     io::{self, BufReader, Read},
3     path::Path,
4 };
6 use fs_err as fs;
8 use crate::{
9     commands::warn_user_about_loading_zip_in_memory,
10     extension::CompressionFormat::{self, *},
11     list::{self, FileInArchive, ListOptions},
12     utils::{io::lock_and_flush_output_stdio, user_wants_to_continue},
13     QuestionAction, QuestionPolicy, BUFFER_CAPACITY,
16 /// File at input_file_path is opened for reading, example: "archive.tar.gz"
17 /// formats contains each format necessary for decompression, example: [Gz, Tar] (in decompression order)
18 pub fn list_archive_contents(
19     archive_path: &Path,
20     formats: Vec<CompressionFormat>,
21     list_options: ListOptions,
22     question_policy: QuestionPolicy,
23     password: Option<&str>,
24 ) -> crate::Result<()> {
25     let reader = fs::File::open(archive_path)?;
27     // Zip archives are special, because they require io::Seek, so it requires it's logic separated
28     // from decoder chaining.
29     //
30     // This is the only case where we can read and unpack it directly, without having to do
31     // in-memory decompression/copying first.
32     //
33     // Any other Zip decompression done can take up the whole RAM and freeze ouch.
34     if let &[Zip] = formats.as_slice() {
35         let zip_archive = zip::ZipArchive::new(reader)?;
36         let files = crate::archive::zip::list_archive(zip_archive);
37         list::list_files(archive_path, files, list_options)?;
39         return Ok(());
40     }
42     // Will be used in decoder chaining
43     let reader = BufReader::with_capacity(BUFFER_CAPACITY, reader);
44     let mut reader: Box<dyn Read + Send> = Box::new(reader);
46     // Grab previous decoder and wrap it inside of a new one
47     let chain_reader_decoder =
48         |format: &CompressionFormat, decoder: Box<dyn Read + Send>| -> crate::Result<Box<dyn Read + Send>> {
49             let decoder: Box<dyn Read + Send> = match format {
50                 Gzip => Box::new(flate2::read::GzDecoder::new(decoder)),
51                 Bzip => Box::new(bzip2::read::BzDecoder::new(decoder)),
52                 Lz4 => Box::new(lz4_flex::frame::FrameDecoder::new(decoder)),
53                 Lzma => Box::new(xz2::read::XzDecoder::new(decoder)),
54                 Snappy => Box::new(snap::read::FrameDecoder::new(decoder)),
55                 Zstd => Box::new(zstd::stream::Decoder::new(decoder)?),
56                 Tar | Zip | Rar | SevenZip => unreachable!(),
57             };
58             Ok(decoder)
59         };
61     for format in formats.iter().skip(1).rev() {
62         reader = chain_reader_decoder(format, reader)?;
63     }
65     let files: Box<dyn Iterator<Item = crate::Result<FileInArchive>>> = match formats[0] {
66         Tar => Box::new(crate::archive::tar::list_archive(tar::Archive::new(reader))),
67         Zip => {
68             if formats.len() > 1 {
69                 // Locking necessary to guarantee that warning and question
70                 // messages stay adjacent
71                 let _locks = lock_and_flush_output_stdio();
73                 warn_user_about_loading_zip_in_memory();
74                 if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? {
75                     return Ok(());
76                 }
77             }
79             let mut vec = vec![];
80             io::copy(&mut reader, &mut vec)?;
81             let zip_archive = zip::ZipArchive::new(io::Cursor::new(vec))?;
83             Box::new(crate::archive::zip::list_archive(zip_archive))
84         }
85         #[cfg(feature = "unrar")]
86         Rar => {
87             if formats.len() > 1 {
88                 let mut temp_file = tempfile::NamedTempFile::new()?;
89                 io::copy(&mut reader, &mut temp_file)?;
90                 Box::new(crate::archive::rar::list_archive(temp_file.path(), password))
91             } else {
92                 Box::new(crate::archive::rar::list_archive(archive_path, password))
93             }
94         }
95         #[cfg(not(feature = "unrar"))]
96         Rar => {
97             return Err(crate::archive::rar_stub::no_support());
98         }
99         SevenZip => {
100             if formats.len() > 1 {
101                 // Locking necessary to guarantee that warning and question
102                 // messages stay adjacent
103                 let _locks = lock_and_flush_output_stdio();
105                 warn_user_about_loading_zip_in_memory();
106                 if !user_wants_to_continue(archive_path, question_policy, QuestionAction::Decompression)? {
107                     return Ok(());
108                 }
109             }
111             let mut files = Vec::new();
113             sevenz_rust::decompress_file_with_extract_fn(archive_path, ".", |entry, _, _| {
114                 files.push(Ok(FileInArchive {
115                     path: entry.name().into(),
116                     is_dir: entry.is_directory(),
117                 }));
118                 Ok(true)
119             })?;
120             Box::new(files.into_iter())
121         }
122         Gzip | Bzip | Lz4 | Lzma | Snappy | Zstd => {
123             panic!("Not an archive! This should never happen, if it does, something is wrong with `CompressionFormat::is_archive()`. Please report this error!");
124         }
125     };
126     list::list_files(archive_path, files, list_options)?;
127     Ok(())