Skip to content
Open
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
22 changes: 21 additions & 1 deletion src/extractcode/libarchive2.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,26 @@ def __iter__(self):
return self.iter()


def as_unicode_path(path):
"""
Return a unicode string decoded from a path bytes `path` of an archive
entry.

Archive entry paths are bytes. Decode them as UTF-8 first: this is the
encoding used by most archives and decoding is deterministic. A charset
detector can mistake plain ASCII path segments such as ``+dfsg/`` for
UTF-7 and corrupt directory names, extracting files in the wrong
directory. Fall back to the historical charset detection for paths that
are not valid UTF-8.
"""
if not isinstance(path, bytes):
return path
try:
return path.decode("utf-8")
except UnicodeDecodeError:
return text.as_unicode(path)


@attr.attrs
class Entry(object):
"""
Expand Down Expand Up @@ -416,7 +436,7 @@ def get_path(self, func, func_w):
if not path:
path = func_w(self.entry_struct)
if not isinstance(path, str):
path = text.as_unicode(path)
path = as_unicode_path(path)

return path

Expand Down
42 changes: 42 additions & 0 deletions tests/test_libarchive2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
# See https://aboutcode.org for more information about nexB OSS projects.
#

import io
import os
import tarfile

from commoncode import fileutils

Expand All @@ -21,6 +23,46 @@
"""


def test_as_unicode_path_does_not_decode_plain_ascii_as_utf7():
from extractcode.libarchive2 import as_unicode_path

path = b"qt6-webengine-6.8.2+dfsg/LICENSES/Qt-GPL-exception-1.0.txt"
assert as_unicode_path(path) == ("qt6-webengine-6.8.2+dfsg/LICENSES/Qt-GPL-exception-1.0.txt")


def test_as_unicode_path_decodes_utf8():
from extractcode.libarchive2 import as_unicode_path

path = "some/path/äöü.txt".encode("utf-8")
assert as_unicode_path(path) == "some/path/äöü.txt"


def test_as_unicode_path_handles_missing_paths():
from extractcode.libarchive2 import as_unicode_path

assert as_unicode_path(None) is None
assert as_unicode_path(b"") == ""


def test_extract_tar_keeps_directories_with_a_plus_sign(tmp_path):
from extractcode.libarchive2 import extract

archive = str(tmp_path / "test.tar")
name = "qt6-webengine-6.8.2+dfsg/LICENSES/Qt-GPL-exception-1.0.txt"
with tarfile.open(archive, "w") as tar:
info = tarfile.TarInfo(name)
info.size = 5
tar.addfile(info, io.BytesIO(b"hello"))

target_dir = tmp_path / "target"
target_dir.mkdir()

result = list(extract(archive, str(target_dir)))

assert result == []
assert (target_dir / name).exists()


class TestExtractorTest(BaseArchiveTestCase):
def test_libarchive_extract_can_extract_to_relative_paths(self):
# The setup is a tad complex because we want to have a relative dir
Expand Down
Loading