Skip to content
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: 1 addition & 1 deletion nibabel/filebasedimages.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ def _sniff_meta_for(klass, filename, sniff_nbytes, sniff=None):
try:
with ImageOpener(meta_fname, 'rb') as fobj:
binaryblock = fobj.read(sniff_nbytes)
except IOError:
except (IOError, EOFError):
return None
return (binaryblock, meta_fname)

Expand Down
2 changes: 1 addition & 1 deletion nibabel/filename_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def _iendswith(whole, end):


def splitext_addext(filename,
addexts=('.gz', '.bz2'),
addexts=('.gz', '.bz2', '.zst'),
match_case=False):
""" Split ``/pth/fname.ext.gz`` into ``/pth/fname, .ext, .gz``

Expand Down
22 changes: 9 additions & 13 deletions nibabel/loadsave.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,14 @@
_compressed_suffixes = ('.gz', '.bz2', '.zst')


def _signature_matches_extension(filename, sniff):
def _signature_matches_extension(filename):
"""Check if signature aka magic number matches filename extension.

Parameters
----------
filename : str or os.PathLike
Path to the file to check

sniff : bytes or None
First bytes of the file. If not `None` and long enough to contain the
signature, avoids having to read the start of the file.

Returns
-------
matches : bool
Expand All @@ -48,20 +44,20 @@ def _signature_matches_extension(filename, sniff):
"""
signatures = {
".gz": {"signature": b"\x1f\x8b", "format_name": "gzip"},
".bz2": {"signature": b"BZh", "format_name": "bzip2"}
".bz2": {"signature": b"BZh", "format_name": "bzip2"},
".zst": {"signature": b"\x28\xb5\x2f\xfd", "format_name": "ztsd"},
}
filename = _stringify_path(filename)
*_, ext = splitext_addext(filename)
ext = ext.lower()
if ext not in signatures:
return True, ""
expected_signature = signatures[ext]["signature"]
if sniff is None or len(sniff) < len(expected_signature):
try:
with open(filename, "rb") as fh:
sniff = fh.read(len(expected_signature))
except OSError:
return False, f"Could not read file: {filename}"
try:
with open(filename, "rb") as fh:
sniff = fh.read(len(expected_signature))
except OSError:
return False, f"Could not read file: {filename}"
if sniff.startswith(expected_signature):
return True, ""
format_name = signatures[ext]["format_name"]
Expand Down Expand Up @@ -100,7 +96,7 @@ def load(filename, **kwargs):
img = image_klass.from_filename(filename, **kwargs)
return img

matches, msg = _signature_matches_extension(filename, sniff)
matches, msg = _signature_matches_extension(filename)
if not matches:
raise ImageFileError(msg)

Expand Down
35 changes: 21 additions & 14 deletions nibabel/tests/test_loadsave.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
from ..loadsave import load, read_img_data, _signature_matches_extension
from ..filebasedimages import ImageFileError
from ..tmpdirs import InTemporaryDirectory, TemporaryDirectory
from ..openers import Opener

from ..optpkg import optional_package
_, have_scipy, _ = optional_package('scipy')
_, have_pyzstd, _ = optional_package('pyzstd')

from numpy.testing import (assert_almost_equal,
assert_array_equal)
Expand Down Expand Up @@ -74,41 +76,46 @@ def test_load_empty_image():
assert str(err.value).startswith('Empty file: ')


@pytest.mark.parametrize("extension", [".gz", ".bz2"])
@pytest.mark.parametrize("extension", [".gz", ".bz2", ".zst"])
def test_load_bad_compressed_extension(tmp_path, extension):
if extension == ".zst" and not have_pyzstd:
pytest.skip()
file_path = tmp_path / f"img.nii{extension}"
file_path.write_bytes(b"bad")
with pytest.raises(ImageFileError, match=".*is not a .* file"):
load(file_path)


@pytest.mark.parametrize("extension", [".gz", ".bz2", ".zst"])
def test_load_good_extension_with_bad_data(tmp_path, extension):
if extension == ".zst" and not have_pyzstd:
pytest.skip()
file_path = tmp_path / f"img.nii{extension}"
with Opener(file_path, "wb") as fobj:
fobj.write(b"bad")
with pytest.raises(ImageFileError, match="Cannot work out file type of .*"):
load(file_path)


def test_signature_matches_extension(tmp_path):
gz_signature = b"\x1f\x8b"
good_file = tmp_path / "good.gz"
good_file.write_bytes(gz_signature)
bad_file = tmp_path / "bad.gz"
bad_file.write_bytes(b"bad")
matches, msg = _signature_matches_extension(
tmp_path / "uncompressed.nii", None)
matches, msg = _signature_matches_extension(tmp_path / "uncompressed.nii")
assert matches
assert msg == ""
matches, msg = _signature_matches_extension(tmp_path / "missing.gz", None)
matches, msg = _signature_matches_extension(tmp_path / "missing.gz")
assert not matches
assert msg.startswith("Could not read")
matches, msg = _signature_matches_extension(bad_file, None)
matches, msg = _signature_matches_extension(bad_file)
assert not matches
assert "is not a" in msg
matches, msg = _signature_matches_extension(bad_file, gz_signature + b"abc")
assert matches
assert msg == ""
matches, msg = _signature_matches_extension(
good_file, gz_signature + b"abc")
assert matches
assert msg == ""
matches, msg = _signature_matches_extension(good_file, gz_signature[:1])
matches, msg = _signature_matches_extension(good_file)
assert matches
assert msg == ""
matches, msg = _signature_matches_extension(good_file, None)
matches, msg = _signature_matches_extension(tmp_path / "missing.nii")
assert matches
assert msg == ""

Expand Down