UnzipFile
Unzip a file using fileName or entryID to specify a file. Unzipped files will be loaded to the War Room and names will be put into the context.
python · Common Scripts
Details
| ID | UnzipFile |
|---|---|
| Language | python |
| From Version | 5.0.0 |
| Docker Image | demisto/unzip:1.0.0.11009641 |
| Tags | Utility file |
README
Unzip a file using fileName or entryID to specify a file. Unzipped files will be loaded to the War Room and names will be put into the context.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | Utility, file |
| Cortex XSOAR Version | 5.0.0 |
Used In
Sample usage of this script can be found in the following playbooks and scripts.
- Comprehensive PAN-OS Best Practice Assessment
- Cortex XDR - Retrieve File by sha256
- CrowdStrike Falcon - Retrieve File
- Get File Sample By Hash - Cylance Protect
- Local Analysis alert Investigation
- MDE - Retrieve File
- PS Remote Get File Sample From Path
- PS-Remote Get MFT
- PS-Remote Get Registry
- Pull Request Creation - Generic
Inputs
| Argument Name | Description |
|---|---|
| fileName | The file name. |
| password | Password to protect the ZIP file. |
| nonsensitive_password | Password to protect the ZIP file, inserted as a non sensative argument. |
| entryID | The entry ID of the attached ZIPp file in the War Room. |
| lastZipFileInWarroom | Enter ‘yes’ (or any other value) if the ZIP file is last ZIP file in the War Room. |
| zipTool | Tool to extract zip |
Outputs
| Path | Description | Type |
|---|---|---|
| ExtractedFiles | A list of file names that were extracted from the ZIP file. | Unknown |
import os from tempfile import mkdtemp from unittest.mock import patch, MagicMock import pytest from UnzipFile import * data_test_unzip_no_password = ["testZip.yml", "ScanSummary.txt", "item.png"] @pytest.mark.parametrize("file_name", data_test_unzip_no_password) def test_unzip_no_password(file_name): """ Given - valid zip file - no password required - empty folder _dir When - run extract on that zip file and export the internal files to _dir Then - ensure zip file content have be saved at _dir directory with the original filename - ensure that the saved file has expected content """ # Given # - valid zip file - no password required main_dir = "/".join(__file__.split("/")[0:-1]) expected_file_unzipped = os.path.join(main_dir + "/test_data", file_name) zipped_file_path = expected_file_unzipped + ".zip" # Creation of file object zipped_file_object = {"name": "testFile", "path": zipped_file_path} # - empty folder _di _dir = mkdtemp() # When # - run extract on that zip file and export the internal files to _dir extract(zipped_file_object, _dir) # Then # - ensure zip file content have been saved at _dir directory with the original filename with open(_dir + "/" + file_name, "rb") as f: actual_file_data = f.read() with open(expected_file_unzipped, "rb") as f: expected_data = f.read() shutil.rmtree(_dir) # - ensure that the saved file has expected content data assert expected_data.splitlines() == actual_file_data.splitlines(), f"failed extracting {zipped_file_path}" @pytest.mark.parametrize("zip_tool", ("7z", "zipfile")) def test_unzip_with_password(zip_tool: str): """ Given - valid zip file - with password required - empty folder _dir - the tool to extract files When - run extract on that zip file and export the internal files to _dir Then - ensure zip file content have be saved at _dir directory with the original filename - ensure that the saved file has expected content """ # Given # - valid zip file - no password required file_name = "fix_unzip.png" password = "demisto" main_dir = "/".join(__file__.split("/")[0:-1]) expected_file_unzipped = os.path.join(main_dir + "/test_data", file_name) zipped_file_path = expected_file_unzipped + ".zip" # Creation of file object zipped_file_object = {"name": "testFile", "path": zipped_file_path} # - empty folder _dir _dir = mkdtemp() # When # - run extract on that zip file and export the internal files to _dir extract(zipped_file_object, _dir, password=password, zip_tool=zip_tool) # Then # - ensure zip file content have been saved at _dir directory with the original filename with open(_dir + "/" + file_name, "rb") as f: actual_file_data = f.read() with open(expected_file_unzipped, "rb") as f: expected_data = f.read() shutil.rmtree(_dir) # - ensure that the saved file has expected content data assert expected_data == actual_file_data, "failed unzipping file: " + zipped_file_path + " with password: " + password long_file_name = os.urandom(256) data_test_unzip_long_file_name = ["long_filename_zip.zip"] @pytest.mark.parametrize("file_name", data_test_unzip_long_file_name) def test_unzip_long_filename(file_name, mocker): """ Given - valid zip file - includes a file with long filename - empty folder _dir When - run extract on that zip file and export the internal files to _dir Then - ensure zip file content have be saved at _dir directory with the new filename """ import UnzipFile as unzip # Given # - valid zip file - includes a file with long filename main_dir = "/".join(__file__.split("/")[0:-1]) zip_file_path = os.path.join(main_dir + "/test_data", file_name) # Creation of file object zipped_file_object = {"name": "testFile", "path": zip_file_path} # - empty folder _dir _dir = mkdtemp() # When # - run extract on that zip file and export the internal files to _dir mocker.patch.object(unzip, "SLICE_FILENAME_SIZE_BYTES", return_value=100) extract(zipped_file_object, _dir, zip_tool="zipfile") # Then # - ensure zip file content have been saved at _dir directory with the new filename files_list = os.listdir(_dir) shutil.rmtree(_dir) assert files_list[0].endswith("_shortened_.rtf") is True def test_unrar_no_password(): """ Given - valid rar file - no password required - empty folder _dir When - run extract on the rar file and export the internal files to _dir Then - ensure rar file content has been saved at _dir directory with the original filename - ensure that the saved file has expected content """ file_name = "Untitled_document.pdf" main_dir = "/".join(__file__.split("/")[0:-1]) expected_file_unzipped = os.path.join(main_dir + "/test_data", file_name) zipped_file_path = expected_file_unzipped + ".rar" # Creation of file object zipped_file_object = {"name": "Untitled_document.pdf.rar", "path": zipped_file_path} # - empty folder _di _dir = mkdtemp() # When # - run extract on that zip file and export the internal files to _dir extract(zipped_file_object, _dir) # Then # - ensure rar file content have been saved at _dir directory with the original filename with open(_dir + "/" + file_name, "rb") as f: actual_file_data = f.read() with open(expected_file_unzipped, "rb") as f: expected_data = f.read() shutil.rmtree(_dir) # - ensure that the saved file has expected content data assert expected_data == actual_file_data, "failed extracting " + zipped_file_path def test_extract_tarfile(): """ Given - valid tar.gz file - empty folder _dir When - run extract on the tar file and export the internal files to _dir Then - ensure tar file content has been saved at _dir directory with the original filename - ensure that the saved file has expected content """ file_name = "test_file.txt" main_dir = "/".join(__file__.split("/")[0:-1]) expected_file_unzipped = os.path.join(main_dir + "/test_data", file_name) zipped_file_path = expected_file_unzipped + ".tar.gz" # Creation of file object zipped_file_object = {"name": "test_file.tar.gz", "path": zipped_file_path} # - empty folder _di _dir = mkdtemp() # When # - run extract on that zip file and export the internal files to _dir extract(zipped_file_object, _dir) # Then # - ensure tar file content have been saved at _dir directory with the original filename with open(_dir + "/" + file_name, "rb") as f: actual_file_data = f.read() with open(expected_file_unzipped, "rb") as f: expected_data = f.read() shutil.rmtree(_dir) # - ensure that the saved file has expected content data assert expected_data == actual_file_data, "failed extracting " + zipped_file_path ARGS_BOTH_PASSWORDS_IDENTICAL = {"password": "aa", "nonsensitive_password": "aa"} ARGS_BOTH_PASSWORDS_NOT_IDENTICAL = {"password": "aa", "nonsensitive_password": "bb"} ARGS_ONLY_PASSWORD = {"password": "aa"} ARGS_ONLY_NONSENSITIVE_PASSWORD = {"nonsensitive_password": "aa"} @pytest.mark.parametrize("args", [ARGS_BOTH_PASSWORDS_IDENTICAL, ARGS_ONLY_NONSENSITIVE_PASSWORD, ARGS_ONLY_PASSWORD]) def test_get_password_valid(args): """ Given - arguments for the script When - running the script on a password locked file Then - ensure that only one of the arguments 'password' or 'nonsensitive_password' is given or if they are identical. """ assert get_password(args) == "aa" def test_get_password_invalid(): """ Given - arguments for the script When - running the script on a password locked file Then - ensure that only one of the arguments 'password' or 'nonsensitive_password' is given or if they are identical. """ with pytest.raises(ValueError) as e: get_password(ARGS_BOTH_PASSWORDS_NOT_IDENTICAL) if not e: raise AssertionError def test_archive_with_slash_in_path(): """ Given - valid tar.gz file with slash in path - empty folder _dir When - run extract on the tar file and export the internal files to _dir Then - ensure no error was returned """ zipped_file_object = {"name": "Archive_with_slash_in_path.tar.gz", "path": "test_data/Archive_with_slash_in_path.tar.gz"} # - empty folder _dir _dir = mkdtemp() # When # - run extract on that zip file and export the internal files to _dir excluded_dirs, excluded_files = extract(zipped_file_object, _dir) # Then assert excluded_dirs @pytest.fixture def mock_popen(): with patch("UnzipFile.Popen") as mock: yield mock def test_extract_with_errors_in_stdout(mock_popen): """ Given: - A valid tar.gz file with a file name that contains the word "Errors". - A temporary directory for extraction. When: - Extracting the tar file into the directory. Then: - Ensure extraction completes successfully. - Ensure extracted files exist in the directory. - Ensure no unexpected errors occur. """ # Prepare the mock to simulate the command's output mock_process = MagicMock() mock_process.communicate.return_value = ( b"Hello_World_Errors.yml\nHello_World.yml", # stdout b"", # stderr (no error) ) # Mock the Popen constructor to return our mock process mock_popen.return_value = mock_process # Setup test inputs file_path = "/test_data/Archive_with_Errors.tar.gz" dir_path = "/tmp/extracted_files" file_name = "Archive_with_Errors.tar.gz" # Run the extraction function and assert it raises the expected exception result = extract_using_tarfile(file_path, dir_path, file_name) # Assert the stdout contains both filenames assert "Hello_World_Errors.yml" in result assert "Hello_World.yml" in result def test_unzip_with_space_in_path(mocker): """ Given - A zip file path with spaces - empty folder _dir When - run extract on that zip file Then - ensure the command passed to Popen is a list and contains the path correctly (not split) """ import UnzipFile as unzip # Given zip_path = "/path/to/directory with spaces/test.zip" zipped_file_object = {"name": "test.zip", "path": zip_path} _dir = "/tmp/extract_dir" # Mock Popen mock_popen = mocker.patch.object(unzip, "Popen") mock_process = MagicMock() mock_process.communicate.return_value = (b"", b"") mock_popen.return_value = mock_process # When extract(zipped_file_object, _dir, zip_tool="7z") # Then args, _ = mock_popen.call_args cmd_list = args[0] assert isinstance(cmd_list, list) assert zip_path in cmd_list class TestUploadFilesSafeHandling: """Tests for safe file handling in upload_files.""" def test_symlinks_are_skipped(self, mocker): """ Given - An extracted directory containing a symbolic link When - upload_files processes the directory Then - The symbolic link is skipped and not uploaded """ dir_path = mkdtemp() try: # Create a real file and a symlink to it real_file = os.path.join(dir_path, "real_file.txt") link_file = os.path.join(dir_path, "link_file.txt") with open(real_file, "w") as f: f.write("real content") os.symlink(real_file, link_file) mock_debug = mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "results") upload_files([], [], dir_path) # The symlink should be skipped - check debug was called with skip message debug_messages = [str(c) for c in mock_debug.call_args_list] assert any("Skipping" in msg for msg in debug_messages) finally: shutil.rmtree(dir_path) def test_files_outside_extraction_dir_are_skipped(self, mocker): """ Given - An extracted file whose real path resolves outside the extraction directory When - upload_files processes the directory Then - The file is skipped and not uploaded """ dir_path = mkdtemp() outside_dir = mkdtemp() try: # Create a file outside the extraction directory outside_file = os.path.join(outside_dir, "outside.txt") with open(outside_file, "w") as f: f.write("outside content") # Create a symlink inside dir_path pointing to the outside file link_path = os.path.join(dir_path, "escape_link.txt") os.symlink(outside_file, link_path) mock_debug = mocker.patch.object(demisto, "debug") mocker.patch.object(demisto, "results") upload_files([], [], dir_path) # The out-of-tree symlink should be skipped debug_messages = [str(c) for c in mock_debug.call_args_list] assert any("Skipping" in msg for msg in debug_messages) finally: shutil.rmtree(dir_path) shutil.rmtree(outside_dir) def test_normal_files_within_dir_are_processed(self, mocker): """ Given - A normal file within the extraction directory When - upload_files processes the directory Then - The file is read and results are returned """ dir_path = mkdtemp() try: # Create a normal file inside the extraction directory normal_file = os.path.join(dir_path, "normal_file.txt") with open(normal_file, "wb") as f: f.write(b"file content") mock_results = mocker.patch.object(demisto, "results") upload_files([], [], dir_path) # demisto.results should have been called at least twice: # once for fileResult and once for the summary assert mock_results.call_count >= 1 # Verify the file was included in the extracted files list all_calls_str = str(mock_results.call_args_list) assert "normal_file.txt" in all_calls_str finally: shutil.rmtree(dir_path)