Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature-gate RAR support #566

Merged
merged 4 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
- armv7-unknown-linux-musleabihf
feature-use-zlib: [true, false]
feature-use-zstd-thin: [true, false]
feature-unrar: [true, false]

include:
# default runner
Expand Down Expand Up @@ -78,6 +79,7 @@ jobs:
FEATURES=()
if [[ ${{ matrix.feature-use-zlib }} == true ]]; then FEATURES+=(use_zlib); fi
if [[ ${{ matrix.feature-use-zstd-thin }} == true ]]; then FEATURES+=(use_zstd_thin); fi
if [[ ${{ matrix.feature-unrar }} == true ]]; then FEATURES+=(unrar); fi
IFS=','
echo "FEATURES=${FEATURES[*]}" >> $GITHUB_OUTPUT

Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ snap = "1.1.1"
tar = "0.4.40"
tempfile = "3.8.1"
time = { version = "0.3.30", default-features = false }
unrar = "0.5.2"
unrar = { version = "0.5.2", optional = true }
xz2 = "0.1.7"
zip = { version = "0.6.6", default-features = false, features = ["time"] }
zstd = { version = "0.13.0", default-features = false }
Expand All @@ -55,7 +55,7 @@ regex = "1.10.2"
test-strategy = "0.3.1"

[features]
default = ["use_zlib", "use_zstd_thin"]
default = ["use_zlib", "use_zstd_thin", "unrar"]
use_zlib = ["flate2/zlib", "gzp/deflate_zlib", "zip/deflate-zlib"]
use_zstd_thin = ["zstd/thin"]

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ Output:

✓²: Supported, and compression runs in parallel.

✓³: Due to Rar's restrictive license, only decompression and listing can be supported.
✓³: Due to RAR's restrictive license, only decompression and listing can be supported.
If you wish to exclude non-free code from your build, you can disable RAR support
by building without the `unrar` feature.

`tar` aliases are also supported: `tgz`, `tbz`, `tbz2`, `tlz4`, `txz`, `tlzma`, `tsz`, `tzst`.

Expand Down
3 changes: 3 additions & 0 deletions src/archive/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Archive compression algorithms

#[cfg(feature = "unrar")]
pub mod rar;
#[cfg(not(feature = "unrar"))]
pub mod rar_stub;
pub mod sevenz;
pub mod tar;
pub mod zip;
10 changes: 5 additions & 5 deletions src/archive/rar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::Path;

use unrar::{self, Archive};

use crate::{info, list::FileInArchive, warning};
use crate::{error::Error, info, list::FileInArchive};

/// Unpacks the archive given by `archive_path` into the folder given by `output_folder`.
/// Assumes that output_folder is empty
Expand Down Expand Up @@ -49,8 +49,8 @@ pub fn list_archive(archive_path: &Path) -> impl Iterator<Item = crate::Result<F
})
}

pub fn no_compression_notice() {
const MESSAGE: &str = "Creating '.rar' archives is not supported due to licensing restrictions";

warning!("{}", MESSAGE);
pub fn no_compression() -> Error {
Error::UnsupportedFormat {
reason: "Creating RAR archives is not allowed due to licensing restrictions.".into(),
}
}
7 changes: 7 additions & 0 deletions src/archive/rar_stub.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use crate::Error;

pub fn no_support() -> Error {
Error::UnsupportedFormat {
reason: "RAR support is disabled for this build, possibly due to licensing restrictions.".into(),
}
}
7 changes: 5 additions & 2 deletions src/commands/compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,11 @@ pub fn compress_files(
io::copy(&mut vec_buffer, &mut writer)?;
}
Rar => {
archive::rar::no_compression_notice();
return Ok(false);
#[cfg(feature = "unrar")]
return Err(archive::rar::no_compression());

#[cfg(not(feature = "unrar"))]
return Err(archive::rar_stub::no_support());
}
SevenZip => {
if !formats.is_empty() {
Expand Down
5 changes: 5 additions & 0 deletions src/commands/decompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub fn decompress_file(
return Ok(());
}
}
#[cfg(feature = "unrar")]
Rar => {
type UnpackResult = crate::Result<usize>;
let unpack_fn: Box<dyn FnOnce(&Path) -> UnpackResult> = if formats.len() > 1 {
Expand All @@ -164,6 +165,10 @@ pub fn decompress_file(
return Ok(());
}
}
#[cfg(not(feature = "unrar"))]
Rar => {
return Err(crate::archive::rar_stub::no_support());
}
SevenZip => {
if formats.len() > 1 {
warn_user_about_loading_sevenz_in_memory();
Expand Down
5 changes: 5 additions & 0 deletions src/commands/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub fn list_archive_contents(

Box::new(crate::archive::zip::list_archive(zip_archive))
}
#[cfg(feature = "unrar")]
Rar => {
if formats.len() > 1 {
let mut temp_file = tempfile::NamedTempFile::new()?;
Expand All @@ -87,6 +88,10 @@ pub fn list_archive_contents(
Box::new(crate::archive::rar::list_archive(archive_path))
}
}
#[cfg(not(feature = "unrar"))]
Rar => {
return Err(crate::archive::rar_stub::no_support());
}
SevenZip => {
if formats.len() > 1 {
warn_user_about_loading_zip_in_memory();
Expand Down
7 changes: 7 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub enum Error {
InvalidFormat { reason: String },
/// From sevenz_rust::Error
SevenzipError(sevenz_rust::Error),
/// Recognised but unsupported format
// currently only RAR when built without the `unrar` feature
UnsupportedFormat { reason: String },
}

/// Alias to std's Result with ouch's Error
Expand Down Expand Up @@ -142,6 +145,9 @@ impl fmt::Display for Error {
Error::InvalidFormat { reason } => FinalError::with_title("Invalid archive format").detail(reason.clone()),
Error::Custom { reason } => reason.clone(),
Error::SevenzipError(reason) => FinalError::with_title("7z error").detail(reason.to_string()),
Error::UnsupportedFormat { reason } => {
FinalError::with_title("Recognised but unsupported format").detail(reason.clone())
}
};

write!(f, "{err}")
Expand Down Expand Up @@ -181,6 +187,7 @@ impl From<zip::result::ZipError> for Error {
}
}

#[cfg(feature = "unrar")]
impl From<unrar::error::UnrarError> for Error {
fn from(err: unrar::error::UnrarError) -> Self {
Self::Custom {
Expand Down
23 changes: 21 additions & 2 deletions src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,28 @@ use self::CompressionFormat::*;
use crate::{error::Error, warning};

pub const SUPPORTED_EXTENSIONS: &[&str] = &[
"tar", "zip", "bz", "bz2", "gz", "lz4", "xz", "lzma", "sz", "zst", "rar", "7z",
"tar",
"zip",
"bz",
"bz2",
"gz",
"lz4",
"xz",
"lzma",
"sz",
"zst",
#[cfg(feature = "unrar")]
"rar",
"7z",
];

pub const SUPPORTED_ALIASES: &[&str] = &["tgz", "tbz", "tlz4", "txz", "tzlma", "tsz", "tzst"];
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar";

#[cfg(not(feature = "unrar"))]
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z";
#[cfg(feature = "unrar")]
pub const PRETTY_SUPPORTED_EXTENSIONS: &str = "tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z";

pub const PRETTY_SUPPORTED_ALIASES: &str = "tgz, tbz, tlz4, txz, tzlma, tsz, tzst";

/// A wrapper around `CompressionFormat` that allows combinations like `tgz`
Expand Down Expand Up @@ -74,6 +92,7 @@ pub enum CompressionFormat {
Zstd,
/// .zip
Zip,
// even if built without RAR support, we still want to recognise the format
/// .rar
Rar,
/// .7z
Expand Down
29 changes: 13 additions & 16 deletions tests/integration.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#[macro_use]
mod utils;

use std::{
iter::once,
path::{Path, PathBuf},
};
use std::{iter::once, path::PathBuf};

use fs_err as fs;
use parse_display::Display;
Expand Down Expand Up @@ -154,20 +151,20 @@ fn multiple_files(
assert_same_directory(before, after, !matches!(ext, DirectoryExtension::Zip));
}

// test .rar decompression
fn test_unpack_rar_single(input: &Path) -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let dirpath = dir.path();
let unpacked_path = &dirpath.join("testfile.txt");
ouch!("-A", "d", input, "-d", dirpath);
let content = fs::read_to_string(unpacked_path)?;
assert_eq!(content, "Testing 123\n");

Ok(())
}

#[cfg(feature = "unrar")]
#[test]
fn unpack_rar() -> Result<(), Box<dyn std::error::Error>> {
fn test_unpack_rar_single(input: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let dirpath = dir.path();
let unpacked_path = &dirpath.join("testfile.txt");
ouch!("-A", "d", input, "-d", dirpath);
let content = fs::read_to_string(unpacked_path)?;
assert_eq!(content, "Testing 123\n");

Ok(())
}

let mut datadir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
datadir.push("tests/data");
["testfile.rar3.rar.gz", "testfile.rar5.rar"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
source: tests/ui.rs
expression: "run_ouch(\"ouch decompress a\", dir)"
---
[ERROR] Cannot decompress files
- Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension

hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
hint:
hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/a --format tar.gz

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: tests/ui.rs
expression: "run_ouch(\"ouch decompress a b.unknown\", dir)"
---
[ERROR] Cannot decompress files
- Files with unsupported extensions: <TMP_DIR>/b.unknown
- Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension

hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
source: tests/ui.rs
expression: "run_ouch(\"ouch decompress b.unknown\", dir)"
---
[ERROR] Cannot decompress files
- Files with unsupported extensions: <TMP_DIR>/b.unknown
- Decompression formats are detected automatically from file extension

hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
hint:
hint: Alternatively, you can pass an extension to the '--format' flag:
hint: ouch decompress <TMP_DIR>/b.unknown --format tar.gz

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ expression: "run_ouch(\"ouch decompress a\", dir)"
- Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension

hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar
hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
hint:
hint: Alternatively, you can pass an extension to the '--format' flag:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ expression: "run_ouch(\"ouch decompress a b.unknown\", dir)"
- Files with missing extensions: <TMP_DIR>/a
- Decompression formats are detected automatically from file extension

hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar
hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ expression: "run_ouch(\"ouch decompress b.unknown\", dir)"
- Files with unsupported extensions: <TMP_DIR>/b.unknown
- Decompression formats are detected automatically from file extension

hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, rar
hint: Supported extensions are: tar, zip, bz, bz2, gz, lz4, xz, lzma, sz, zst, 7z
hint: Supported aliases are: tgz, tbz, tlz4, txz, tzlma, tsz, tzst
hint:
hint: Alternatively, you can pass an extension to the '--format' flag:
Expand Down
14 changes: 11 additions & 3 deletions tests/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,17 @@ fn ui_test_err_decompress_missing_extension() {

run_in(dir, "touch", "a b.unknown").unwrap();

ui!(run_ouch("ouch decompress a", dir));
ui!(run_ouch("ouch decompress a b.unknown", dir));
ui!(run_ouch("ouch decompress b.unknown", dir));
let name = {
let suffix = if cfg!(feature = "unrar") {
"with_rar"
} else {
"without_rar"
};
format!("ui_test_err_decompress_missing_extension_{suffix}")
};
ui!(format!("{name}-1"), run_ouch("ouch decompress a", dir));
ui!(format!("{name}-2"), run_ouch("ouch decompress a b.unknown", dir));
ui!(format!("{name}-3"), run_ouch("ouch decompress b.unknown", dir));
cyqsimon marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
Expand Down