Skip to content

Commit 48d0f73

Browse files
committed
fix: handle invalid tck files
1 parent feb2063 commit 48d0f73

File tree

4 files changed

+59
-18
lines changed

4 files changed

+59
-18
lines changed

nibabel/streamlines/tck.py

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ def is_correct_format(cls, fileobj):
9191
otherwise returns False.
9292
"""
9393
with Opener(fileobj) as f:
94-
magic_number = asstr(f.fobj.readline())
95-
f.seek(-len(magic_number), os.SEEK_CUR)
94+
magic_number = asstr(f.fobj.read(len(cls.MAGIC_NUMBER)))
95+
f.seek(-len(cls.MAGIC_NUMBER), os.SEEK_CUR)
9696

9797
return magic_number.strip() == cls.MAGIC_NUMBER
9898

@@ -287,8 +287,8 @@ def _write_header(fileobj, header):
287287
fileobj.write(asbytes(str(new_offset) + "\n"))
288288
fileobj.write(asbytes("END\n"))
289289

290-
@staticmethod
291-
def _read_header(fileobj):
290+
@classmethod
291+
def _read_header(cls, fileobj):
292292
""" Reads a TCK header from a file.
293293
294294
Parameters
@@ -304,23 +304,50 @@ def _read_header(fileobj):
304304
header : dict
305305
Metadata associated with this tractogram file.
306306
"""
307-
# Record start position if this is a file-like object
308-
start_position = fileobj.tell() if hasattr(fileobj, 'tell') else None
307+
308+
# Build header dictionary from the buffer
309+
hdr = {}
310+
offset_data = 0
309311

310312
with Opener(fileobj) as f:
313+
314+
# Record start position
315+
start_position = f.fobj.tell()
316+
317+
# Make sure we are at the beginning of the file
318+
f.fobj.seek(0, os.SEEK_SET)
319+
311320
# Read magic number
312-
magic_number = f.fobj.readline().strip()
321+
magic_number = asstr(f.fobj.read(len(cls.MAGIC_NUMBER)))
322+
323+
if magic_number != cls.MAGIC_NUMBER:
324+
raise HeaderError(f"Invalid magic number: {magic_number}")
313325

314-
# Read all key-value pairs contained in the header.
315-
buf = asstr(f.fobj.readline())
316-
while not buf.rstrip().endswith("END"):
317-
buf += asstr(f.fobj.readline())
326+
hdr[Field.MAGIC_NUMBER] = magic_number
327+
328+
f.fobj.seek(1, os.SEEK_CUR) # Skip \n
329+
330+
# Read all key-value pairs contained in the header
331+
for n_line, line in enumerate(f.fobj, 1):
332+
line = asstr(line).strip()
333+
334+
if not line: # Skip empty lines
335+
continue
336+
337+
if line == "END": # End of the header
338+
break
339+
340+
if ':' not in line: # Invalid header line
341+
raise HeaderError(f"Invalid header (line {n_line}): {line}")
342+
343+
key, value = line.split(":", 1)
344+
hdr[key.strip()] = value.strip()
318345

319346
offset_data = f.tell()
320347

321-
# Build header dictionary from the buffer.
322-
hdr = dict(item.split(': ') for item in buf.rstrip().split('\n')[:-1])
323-
hdr[Field.MAGIC_NUMBER] = magic_number
348+
# Set the file position where it was, in case it was previously open
349+
if start_position is not None:
350+
f.fobj.seek(start_position, os.SEEK_SET)
324351

325352
# Check integrity of TCK header.
326353
if 'datatype' not in hdr:
@@ -352,10 +379,6 @@ def _read_header(fileobj):
352379
# Keep the file position where the data begin.
353380
hdr['_offset_data'] = int(hdr['file'].split()[1])
354381

355-
# Set the file position where it was, if it was previously open.
356-
if start_position is not None:
357-
fileobj.seek(start_position, os.SEEK_SET)
358-
359382
return hdr
360383

361384
@classmethod

nibabel/streamlines/tests/test_tck.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def setup_module():
2525
global DATA
2626

2727
DATA['empty_tck_fname'] = pjoin(data_path, "empty.tck")
28+
DATA['no-magic-number_tck_fname'] = pjoin(data_path, "no-magic-number.tck")
29+
DATA['no-header-end_tck_fname'] = pjoin(data_path, "no-header-end.tck")
2830
# simple.tck contains only streamlines
2931
DATA['simple_tck_fname'] = pjoin(data_path, "simple.tck")
3032
DATA['simple_tck_big_endian_fname'] = pjoin(data_path,
@@ -50,6 +52,22 @@ def test_load_empty_file(self):
5052
with pytest.warns(Warning) if lazy_load else error_warnings():
5153
assert_tractogram_equal(tck.tractogram, DATA['empty_tractogram'])
5254

55+
def test_load_no_magic_number_file(self):
56+
for lazy_load in [False, True]:
57+
with pytest.raises(HeaderError):
58+
TckFile.load(
59+
DATA['no-magic-number_tck_fname'],
60+
lazy_load=lazy_load
61+
)
62+
63+
def test_load_no_header_end_file(self):
64+
for lazy_load in [False, True]:
65+
with pytest.raises(HeaderError):
66+
TckFile.load(
67+
DATA['no-header-end_tck_fname'],
68+
lazy_load=lazy_load
69+
)
70+
5371
def test_load_simple_file(self):
5472
for lazy_load in [False, True]:
5573
tck = TckFile.load(DATA['simple_tck_fname'], lazy_load=lazy_load)
81 Bytes
Binary file not shown.
71 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)