Repository: dtrx-py/dtrx (language: python)

Issue:
non-interactive mode shouldn't prompt for a password
As discussed in #5 , when using --noninteractive, dtrx shouldn't prompt for a password.


Discussion / hints:
When dealing with a single passworded archive ("foo.zip"), "dtrx -n foo.zip" should exit with a warning or error instead of waiting for user input.

When dealing with nested archives, (an unpassworded "bar.tar.gz" containing a passworded "foo.zip"), "dtrx -n bar.tar.gz" behavior is currently correct because -n assumes that nested archives should not be unpacked. When told to unpack recursively however (-r), "dtrx -n -r bar.tar.gz" does the wrong thing and prompts the user. So it needs to be able to handle both cases.
Thank you very much for creating this feature request. I think the way you describe it is correct. Maybe I should look also into this and contribute something when I am already using this. I will check if I can spare some time this weekend and have a look at the code and see how might implement this.

Fix patch:
diff --git a/scripts/dtrx b/scripts/dtrx
index 5512fa1..1a0205a 100755
--- a/scripts/dtrx
+++ b/scripts/dtrx
@@ -223,6 +223,7 @@ class BaseExtractor(object):
             raise ValueError("unrecognized encoding %s" % (encoding,))
         self.filename = os.path.realpath(filename)
         self.encoding = encoding
+        self.ignore_pw = False
         self.file_count = 0
         self.included_archives = []
         self.target = None
@@ -267,6 +268,17 @@ class BaseExtractor(object):
             except subprocess.TimeoutExpired:
                 logging.debug("timeout hit..")
                 self.timeout_check(pipe)
+                # Verify that we're not trying to extract password-protected
+                # archives in non-interactive mode
+                if self.pw_prompted and self.ignore_pw:
+                    pipe.kill()
+                    # Whatever extractor we're using probably left the
+                    # terminal hiding output..
+                    os.system("stty echo")
+                    # Clean up the error output
+                    self.stderr = ""
+                    raise ExtractorError("cannot extract encrypted archive '%s' in non-interactive mode"
+                        % (self.filename))
 
     def run_pipes(self, final_stdout=None):
         has_output_target = True if final_stdout else False
@@ -385,7 +397,8 @@ class BaseExtractor(object):
         self.pipe(self.extract_pipe)
         self.run_pipes()
 
-    def extract(self):
+    def extract(self, ignore_passwd = False):
+        self.ignore_pw = ignore_passwd
         try:
             self.target = tempfile.mkdtemp(prefix=".dtrx-", dir=".")
         except (OSError, IOError) as error:
@@ -451,7 +464,8 @@ class CompressionExtractor(BaseExtractor):
             raise ExtractorError("doesn't look like a compressed file")
         yield self.basename()
 
-    def extract(self):
+    def extract(self, ignore_passwd = False):
+        self.ignore_pw = ignore_passwd
         self.content_type = ONE_ENTRY_KNOWN
         self.content_name = self.basename()
         self.contents = None
@@ -1427,7 +1441,7 @@ class ExtractionAction(BaseAction):
     def run(self, filename, extractor):
         self.current_filename = filename
         error = (
-            self.report(extractor.extract)
+            self.report(extractor.extract, self.options.batch)
             or self.report(self.get_handler, extractor)
             or self.report(self.current_handler.handle)
             or self.report(self.show_extraction, extractor)


Test patch:
diff --git a/tests/compare.py b/tests/compare.py
index 4c5a750..8637e18 100755
--- a/tests/compare.py
+++ b/tests/compare.py
@@ -23,7 +23,12 @@
 import os
 import re
 import struct
-import subprocess
+
+try:
+    import subprocess32 as subprocess
+except ImportError:
+    import subprocess
+
 import sys
 import tempfile
 import termios
@@ -120,14 +125,13 @@ def start_proc(self, command, stdin=None, output=None):
     def get_results(self, command, stdin=None):
         print("Output from %s:" % (" ".join(command),), file=self.outbuffer)
         self.outbuffer.flush()
-        # on python3+, we can just use .wait(5). python2.7 doesn't have that.
-        # instead use the timeout utility to kill the process if it hangs
-        status = self.start_proc(["timeout", "5",] + command, stdin, self.outbuffer).wait()
+        status = self.start_proc(command, stdin, self.outbuffer).wait(5)
         process = subprocess.Popen(["find"], stdout=subprocess.PIPE)
         output = process.stdout.read(-1).decode("ascii", errors="ignore")
         process.stdout.close()
         process.wait()
         return status, set(output.split("\n"))
+
     def run_script(self, key):
         commands = getattr(self, key)
         if commands is not None:
@@ -297,7 +301,7 @@ def __init__(self):
     def wanted_test(self, data):
         if not self.name_regexps:
             return True
-        return filter(None, [r.search(data["name"]) for r in self.name_regexps])
+        return any([r.search(data["name"]) for r in self.name_regexps])
 
     def add_subdir_tests(self):
         for odata in self.test_data:
diff --git a/tests/test-pw.zip b/tests/test-pw.zip
new file mode 100644
index 0000000..e5ef9ee
Binary files /dev/null and b/tests/test-pw.zip differ
diff --git a/tests/tests.yml b/tests/tests.yml
index 76196ab..5bf6ca3 100644
--- a/tests/tests.yml
+++ b/tests/tests.yml
@@ -837,3 +837,21 @@
     mkdir test-cpio
     cd test-cpio
     tar -zxf ../$1
+
+# this has issues because zip uses a pty to read the password without echo;
+# see https://pexpect.readthedocs.io/en/latest/FAQ.html#whynotpipe
+# disabled for now
+# - name: password zip interactive
+#   filenames: test-pw.zip
+#   input: yolo
+#   baseline: |
+#     mkdir test-pw
+#     cd test-pw
+#     unzip -P yolo -q ../$1
+
+- name: password zip noninteractive
+  filenames: test-pw.zip
+  options: "-n"
+  error: true
+  grep: "cannot extract encrypted archive"
+  cleanup: stty -F /dev/stdout echo