From f8e2997d701ad0850768d258b1ac521984988e47 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 3 Dec 2024 21:48:55 -0500 Subject: [PATCH 1/5] fix(deno): Truncate BIDSFileDeno.readBytes() value on EOF --- src/files/deno.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/files/deno.ts b/src/files/deno.ts index 3895a0cd..ebae2dfb 100644 --- a/src/files/deno.ts +++ b/src/files/deno.ts @@ -93,14 +93,17 @@ export class BIDSFileDeno implements BIDSFile { /** * Read bytes in a range efficiently from a given file + * + * Reads up to size bytes, starting at offset. + * If EOF is encountered, the resulting array may be smaller. */ async readBytes(size: number, offset = 0): Promise { const handle = this.#openHandle() const buf = new Uint8Array(size) await handle.seek(offset, Deno.SeekMode.Start) - await handle.read(buf) + const nbytes = await handle.read(buf) ?? 0 handle.close() - return buf + return buf.subarray(0, nbytes) } /** From 07efb463d1349b2b9b2c511d34ccf907224a1c92 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 3 Dec 2024 21:49:48 -0500 Subject: [PATCH 2/5] test: Add a test file with a couple kB of header extensions --- src/files/nifti.test.ts | 19 +++++++++++++++++++ tests/data/big_header.nii.gz | Bin 0 -> 119 bytes 2 files changed, 19 insertions(+) create mode 100644 tests/data/big_header.nii.gz diff --git a/src/files/nifti.test.ts b/src/files/nifti.test.ts index 909c49b5..df80f759 100644 --- a/src/files/nifti.test.ts +++ b/src/files/nifti.test.ts @@ -53,4 +53,23 @@ Deno.test('Test loading nifti header', async (t) => { }) assertObjectMatch(error, { key: 'NIFTI_HEADER_UNREADABLE' }) }) + + await t.step('Tolerate big headers', async () => { + const path = 'big_header.nii.gz' + const root = './tests/data/' + const file = new BIDSFileDeno(root, path, ignore) + let error: any = undefined + const header = await loadHeader(file) + assert(header !== undefined) + assertObjectMatch(header, { + dim: [3, 1, 1, 1, 1, 1, 1], + pixdim: [1, 1, 1, 1, 1, 1, 1], + shape: [1, 1, 1], + voxel_sizes: [1, 1, 1], + dim_info: { freq: 0, phase: 0, slice: 0 }, + xyzt_units: { xyz: 'unknown', t: 'unknown' }, + qform_code: 0, + sform_code: 2, + }) + }) }) diff --git a/tests/data/big_header.nii.gz b/tests/data/big_header.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..9821d7f44c3a76492af80547b5ff516478a1166d GIT binary patch literal 119 zcmb2|=3oE;mjB&}DGFQ$#s-TVLVTHcxDp!Ks@d3Tc3c)!>R(m;pu(qO4ckv1k0Yh!=&BCY3>yK{mGiyNG1tK#Un_TJf+&Hb*u6w_iw11NFBX~97`hEtx? Jm{bHA7y!u0CG!9P literal 0 HcmV?d00001 From 34c15a6fad6c93705b6192f47bd9cfbc8937486c Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 3 Dec 2024 21:51:17 -0500 Subject: [PATCH 3/5] fix: Prevent gzip decompressor from hanging --- src/files/nifti.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/files/nifti.ts b/src/files/nifti.ts index 58a05895..edea1537 100644 --- a/src/files/nifti.ts +++ b/src/files/nifti.ts @@ -11,20 +11,24 @@ async function extract(buffer: Uint8Array, nbytes: number): Promise const stream = new ReadableStream({ start(controller) { controller.enqueue(buffer) + controller.close() }, }) const reader = stream.pipeThrough(new DecompressionStream('gzip')).getReader() let offset = 0 - while (offset < nbytes) { - const { value, done } = await reader.read() - if (done) { - break + try { + while (offset < nbytes) { + const { value, done } = await reader.read() + if (done || !value) { + break + } + result.set(value.subarray(0, Math.min(value.length, nbytes - offset)), offset) + offset += value.length } - result.set(value.subarray(0, Math.min(value.length, nbytes - offset)), offset) - offset += value.length + } finally { + await reader.cancel() } - await reader.cancel() - return result + return result.subarray(0, offset) } export async function loadHeader(file: BIDSFile): Promise { From 3c1db5bc48afc2561d44d75527f8f611d5e5bd06 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Tue, 3 Dec 2024 21:55:03 -0500 Subject: [PATCH 4/5] fix: Truncate NIfTI header data to avoid extensions --- src/files/nifti.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/files/nifti.ts b/src/files/nifti.ts index edea1537..e3d58b57 100644 --- a/src/files/nifti.ts +++ b/src/files/nifti.ts @@ -1,4 +1,4 @@ -import { isCompressed, readHeader } from '@mango/nifti' +import { isCompressed, isNIFTI1, isNIFTI2, NIFTI1, NIFTI2 } from '@mango/nifti' import type { BIDSFile } from '../types/filetree.ts' import { logger } from '../utils/logger.ts' import type { NiftiHeader } from '@bids/schema/context' @@ -34,8 +34,16 @@ async function extract(buffer: Uint8Array, nbytes: number): Promise export async function loadHeader(file: BIDSFile): Promise { try { const buf = await file.readBytes(1024) - const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf - const header = readHeader(data.buffer) + const data = isCompressed(buf.buffer) ? await extract(buf, 540) : buf.slice(0, 540) + let header + if (isNIFTI1(data.buffer)) { + header = new NIFTI1() + // Truncate to 348 bytes to avoid attempting to parse extensions + header.readHeader(data.buffer.slice(0, 348)) + } else if (isNIFTI2(data.buffer)) { + header = new NIFTI2() + header.readHeader(data.buffer) + } if (!header) { throw { key: 'NIFTI_HEADER_UNREADABLE' } } From 9963e7dc424c74bcd956e80c44494bf44d21f806 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Wed, 4 Dec 2024 11:09:45 -0500 Subject: [PATCH 5/5] Add changelog entry --- .../20241204_103403_effigies_nifti_headers.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 changelog.d/20241204_103403_effigies_nifti_headers.md diff --git a/changelog.d/20241204_103403_effigies_nifti_headers.md b/changelog.d/20241204_103403_effigies_nifti_headers.md new file mode 100644 index 00000000..e5f74285 --- /dev/null +++ b/changelog.d/20241204_103403_effigies_nifti_headers.md @@ -0,0 +1,49 @@ + + + + +### Fixed + +- Resolve issue with parsing headers of NIfTI files with large extensions. + Fixes [issue 126]. + +[issue 126]: https://github.com/bids-standard/bids-validator/issues/126 + + + + +