diff --git a/Dockerfile b/Dockerfile index 02a6579..4e762ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python2.7 \ python3-pip \ python3.8 \ + rpm2cpio \ unrar \ unzip \ wget \ diff --git a/dtrx/dtrx.py b/dtrx/dtrx.py index f80c63f..38574fd 100755 --- a/dtrx/dtrx.py +++ b/dtrx/dtrx.py @@ -218,6 +218,7 @@ class BaseExtractor(object): "br": ["br", "--decompress"], } name_checker = DirectoryChecker + noprompt_value = "" def __init__(self, filename, encoding): if encoding and (encoding not in self.decoders): @@ -619,6 +620,9 @@ def extract_archive(self): # that will be replaced here extract_fmt_args = { "OUTPUT_FILE": os.path.splitext(os.path.basename(self.filename))[0], + # if we are in "batch" (--noninteractive) mode, the extractor might + # have extra fields that should be included + "NOPROMPT_FIELD": self.noprompt_value if self.ignore_pw else "", } formatted_extract_commands = [ x.format(**extract_fmt_args) for x in self.extract_command @@ -634,8 +638,9 @@ def get_filenames(self): class ZipExtractor(NoPipeExtractor): file_type = "Zip file" - extract_command = ["unzip", "-q"] + extract_command = ["unzip", "-q", "{NOPROMPT_FIELD}"] list_command = ["zipinfo", "-1"] + noprompt_value = "-o" def is_fatal_error(self, status): return (status or 0) > 1 @@ -685,9 +690,10 @@ def get_filenames(self): class SevenExtractor(NoPipeExtractor): file_type = "7z file" - extract_command = ["7z", "x"] + extract_command = ["7z", "x", "{NOPROMPT_FIELD}"] list_command = ["7z", "l"] border_re = re.compile("^[- ]+$") + noprompt_value = "-y" def get_filenames(self): fn_index = None @@ -707,11 +713,18 @@ def timeout_check(self, pipe): self.stderr += "".join(errs) - # pass through the password prompt, if 7z sent one - if errs and "password" in errs[-1]: - sys.stdout.write("\n" + errs[-1]) - sys.stdout.flush() - self.pw_prompted = True + # handle some common 7z interactive prompts + if errs: + # pass through the password prompt, if 7z sent one + if "password" in errs[-1]: + sys.stdout.write("\n" + errs[-1]) + sys.stdout.flush() + self.pw_prompted = True + elif "replace the existing file" in self.stderr: + # junk archive with duplicate contents. just crash out. + raise RuntimeError( + "Error, duplicate entries in archive:\n" + "".join(self.stderr) + ) class ZstandardExtractor(NoPipeExtractor): @@ -1617,7 +1630,11 @@ def parse_options(self, arguments): dest="batch", action="store_true", default=False, - help="don't ask how to handle special cases", + help=( + "don't ask how to handle special cases. note: this option can be" + " dangerous! it sets the extraction provider to overwrite duplicate" + " files without prompting" + ), ) parser.add_option( "-o", diff --git a/tests/test.jar b/tests/test.jar new file mode 100644 index 0000000..5ff7cc7 Binary files /dev/null and b/tests/test.jar differ diff --git a/tests/test.rpm b/tests/test.rpm new file mode 100644 index 0000000..96dc7ea Binary files /dev/null and b/tests/test.rpm differ diff --git a/tests/tests.yml b/tests/tests.yml index 879651a..dd810cc 100644 --- a/tests/tests.yml +++ b/tests/tests.yml @@ -908,3 +908,24 @@ zip zst zstd + +- name: rpm + filenames: test.rpm + baseline: | + mkdir test + cd test + rpm2cpio ../$1 | cpio -i --make-directories --quiet --no-absolute-filenames + +- name: jar + filenames: test.jar + baseline: | + mkdir test + cd test + rpm2cpio ../$1 | cpio -i --make-directories --quiet --no-absolute-filenames + +- name: duplicate-overwrite + filenames: test-1.23.zip + baseline: | + mkdir test-1.23 + cd test-1.23 + unzip -q ../$1 diff --git a/tools/jar_duplicate_entries.py b/tools/jar_duplicate_entries.py new file mode 100644 index 0000000..0a0d936 --- /dev/null +++ b/tools/jar_duplicate_entries.py @@ -0,0 +1,12 @@ +""" +hacky script to insert duplicate entries to a .jar file +""" + +import zipfile + +# First run these commands +# $ echo hello > hello.txt +# $ jar --create --file test.jar hello.txt + +with zipfile.ZipFile("test.jar", "a") as zf: + zf.write("hello.txt")