From 752387e0aabab708c57bf5308861558316cd0d3c Mon Sep 17 00:00:00 2001 From: CooperBigFoot Date: Wed, 11 Nov 2026 15:17:07 +0100 Subject: [PATCH] Fail promptly when compressed input cannot progress --- src/main/java/org/kopi/ebics/utils/Utils.java | 36 ++++++------ .../java/org/kopi/ebics/utils/UtilsTest.java | 57 +++++++++++++++++++ 2 files changed, 73 insertions(+), 20 deletions(-) create mode 100644 src/test/java/org/kopi/ebics/utils/UtilsTest.java diff --git a/src/main/java/org/kopi/ebics/utils/Utils.java b/src/main/java/org/kopi/ebics/utils/Utils.java index bb3ea44e..804be634 100644 --- a/src/main/java/org/kopi/ebics/utils/Utils.java +++ b/src/main/java/org/kopi/ebics/utils/Utils.java @@ -142,37 +142,33 @@ private static byte[] nextRandomBytes(int n) { * * @param zip the zipped input. * @return the uncompressed data. + * @throws EbicsException if the input is invalid, incomplete, or requires a preset dictionary. */ public static byte[] unzip(byte[] zip) throws EbicsException { Inflater decompressor; ByteArrayOutputStream output; byte[] buf; - decompressor = new Inflater(); output = new ByteArrayOutputStream(zip.length); - decompressor.setInput(zip); buf = new byte[1024]; - - while (!decompressor.finished()) { - int count; - - try { - count = decompressor.inflate(buf); - } catch (DataFormatException e) { - throw new EbicsException(e.getMessage()); - } - output.write(buf, 0, count); - } + decompressor = new Inflater(); try { - output.close(); - } catch (IOException e) { - throw new EbicsException(e.getMessage()); + decompressor.setInput(zip); + while (!decompressor.finished()) { + int count = decompressor.inflate(buf); + if (count == 0 && !decompressor.finished() + && (decompressor.needsInput() || decompressor.needsDictionary())) { + throw new EbicsException("Incomplete compressed data or preset dictionary required"); + } + output.write(buf, 0, count); + } + return output.toByteArray(); + } catch (DataFormatException e) { + throw new EbicsException("Invalid compressed data", e); + } finally { + decompressor.end(); } - - decompressor.end(); - - return output.toByteArray(); } /** diff --git a/src/test/java/org/kopi/ebics/utils/UtilsTest.java b/src/test/java/org/kopi/ebics/utils/UtilsTest.java new file mode 100644 index 00000000..01ed518b --- /dev/null +++ b/src/test/java/org/kopi/ebics/utils/UtilsTest.java @@ -0,0 +1,57 @@ +package org.kopi.ebics.utils; + +import org.junit.jupiter.api.Test; +import org.kopi.ebics.exception.EbicsException; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Arrays; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; + +class UtilsTest { + + @Test + void rejectsTruncatedZlibStream() throws EbicsException { + byte[] compressed = Utils.zip("download payload".getBytes(StandardCharsets.UTF_8)); + byte[] truncated = Arrays.copyOf(compressed, compressed.length - 1); + + assertTimeoutPreemptively(Duration.ofSeconds(1), () -> + assertThrows(EbicsException.class, () -> Utils.unzip(truncated))); + } + + @Test + void rejectsPresetDictionary() throws Exception { + byte[] payload = "download payload".getBytes(StandardCharsets.UTF_8); + ByteArrayOutputStream compressed = new ByteArrayOutputStream(); + Deflater compressor = new Deflater(); + try { + compressor.setDictionary(payload); + try (DeflaterOutputStream stream = new DeflaterOutputStream(compressed, compressor)) { + stream.write(payload); + } + } finally { + compressor.end(); + } + + assertTimeoutPreemptively(Duration.ofSeconds(1), () -> + assertThrows(EbicsException.class, () -> Utils.unzip(compressed.toByteArray()))); + } + + @Test + void roundTripsPayloadAcrossBuffers() throws EbicsException { + byte[] payload = "download payload".repeat(200).getBytes(StandardCharsets.UTF_8); + + assertArrayEquals(payload, Utils.unzip(Utils.zip(payload))); + } + + @Test + void roundTripsEmptyPayload() throws EbicsException { + assertArrayEquals(new byte[0], Utils.unzip(Utils.zip(new byte[0]))); + } +}