From af72fabbf38461ae9b369f113406c1af20f30019 Mon Sep 17 00:00:00 2001 From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com> Date: Sat, 13 Oct 2018 16:42:37 -0400 Subject: [PATCH] Refactored DeflateCompressionCodec to eliminate memory leak. Refactored GzipCompressionCodec and AbstractCompressionCodec to utilize consistent logic across compression algorithms. Resolves #392. --- CHANGELOG.md | 8 +++- .../compression/AbstractCompressionCodec.java | 42 ++++++++++++++++ .../compression/DeflateCompressionCodec.java | 48 ++++++------------- .../compression/GzipCompressionCodec.java | 45 +++++------------ 4 files changed, 75 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f04e3669..f96310ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ ## Release Notes +### 0.10.7 + +This patch release fixes a [memory leak](https://github.com/jwtk/jjwt/issues/392) found in the DEFLATE compression +codec implementation. + ### 0.10.6 This patch release updates the jackson-databind version to 2.9.8 to address a critical security vulnerability in that -library. It also updates .travis.yml to remove deprecated builds and to "trick" travis into building with Oracle -JDK 1.7 as this library still supports that Java version even if Travis no longer does. +library. ### 0.10.5 diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java index e1b2e653..b8b2b577 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java @@ -18,8 +18,12 @@ package io.jsonwebtoken.impl.compression; import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionException; import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; /** * Abstract class that asserts arguments and wraps IOException with CompressionException. @@ -28,6 +32,44 @@ import java.io.IOException; */ public abstract class AbstractCompressionCodec implements CompressionCodec { + //package-protected for a point release. This can be made protected on a minor release (0.11.0, 0.12.0, 1.0, etc). + //TODO: make protected on a minor release + interface StreamWrapper { + OutputStream wrap(OutputStream out) throws IOException; + } + + //package-protected for a point release. This can be made protected on a minor release (0.11.0, 0.12.0, 1.0, etc). + //TODO: make protected on a minor release + byte[] readAndClose(InputStream input) throws IOException { + byte[] buffer = new byte[512]; + ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length); + int read; + try { + read = input.read(buffer); //assignment separate from loop invariant check for code coverage checks + while (read != -1) { + out.write(buffer, 0, read); + read = input.read(buffer); + } + } finally { + Objects.nullSafeClose(input); + } + return out.toByteArray(); + } + + //package-protected for a point release. This can be made protected on a minor release (0.11.0, 0.12.0, 1.0, etc). + //TODO: make protected on a minor release + byte[] writeAndClose(byte[] payload, StreamWrapper wrapper) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(512); + OutputStream compressionStream = wrapper.wrap(outputStream); + try { + compressionStream.write(payload); + compressionStream.flush(); + } finally { + Objects.nullSafeClose(compressionStream); + } + return outputStream.toByteArray(); + } + /** * Implement this method to do the actual work of compressing the payload * diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java index 9f76970b..f31e6c0d 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java @@ -15,13 +15,11 @@ */ package io.jsonwebtoken.impl.compression; -import io.jsonwebtoken.lang.Objects; - -import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.zip.Deflater; +import java.io.OutputStream; import java.util.zip.DeflaterOutputStream; -import java.util.zip.InflaterOutputStream; +import java.util.zip.InflaterInputStream; /** * Codec implementing the deflate compression algorithm. @@ -32,43 +30,25 @@ public class DeflateCompressionCodec extends AbstractCompressionCodec { private static final String DEFLATE = "DEF"; + private static final StreamWrapper WRAPPER = new StreamWrapper() { + @Override + public OutputStream wrap(OutputStream out) { + return new DeflaterOutputStream(out); + } + }; + @Override public String getAlgorithmName() { return DEFLATE; } @Override - public byte[] doCompress(byte[] payload) throws IOException { - - Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); - - ByteArrayOutputStream outputStream = null; - DeflaterOutputStream deflaterOutputStream = null; - try { - outputStream = new ByteArrayOutputStream(); - deflaterOutputStream = new DeflaterOutputStream(outputStream, deflater, true); - - deflaterOutputStream.write(payload, 0, payload.length); - deflaterOutputStream.flush(); - return outputStream.toByteArray(); - } finally { - Objects.nullSafeClose(outputStream, deflaterOutputStream); - } + protected byte[] doCompress(byte[] payload) throws IOException { + return writeAndClose(payload, WRAPPER); } @Override - public byte[] doDecompress(byte[] compressed) throws IOException { - InflaterOutputStream inflaterOutputStream = null; - ByteArrayOutputStream decompressedOutputStream = null; - - try { - decompressedOutputStream = new ByteArrayOutputStream(); - inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream); - inflaterOutputStream.write(compressed); - inflaterOutputStream.flush(); - return decompressedOutputStream.toByteArray(); - } finally { - Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream); - } + protected byte[] doDecompress(byte[] compressed) throws IOException { + return readAndClose(new InflaterInputStream(new ByteArrayInputStream(compressed))); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java b/impl/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java index 0355a76a..978f1dc0 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java @@ -16,11 +16,10 @@ package io.jsonwebtoken.impl.compression; import io.jsonwebtoken.CompressionCodec; -import io.jsonwebtoken.lang.Objects; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -33,43 +32,25 @@ public class GzipCompressionCodec extends AbstractCompressionCodec implements Co private static final String GZIP = "GZIP"; + private static final StreamWrapper WRAPPER = new StreamWrapper() { + @Override + public OutputStream wrap(OutputStream out) throws IOException { + return new GZIPOutputStream(out); + } + }; + @Override public String getAlgorithmName() { return GZIP; } @Override - protected byte[] doDecompress(byte[] compressed) throws IOException { - byte[] buffer = new byte[512]; - - ByteArrayOutputStream outputStream = null; - GZIPInputStream gzipInputStream = null; - ByteArrayInputStream inputStream = null; - - try { - inputStream = new ByteArrayInputStream(compressed); - gzipInputStream = new GZIPInputStream(inputStream); - outputStream = new ByteArrayOutputStream(); - int read = gzipInputStream.read(buffer); - while (read != -1) { - outputStream.write(buffer, 0, read); - read = gzipInputStream.read(buffer); - } - return outputStream.toByteArray(); - } finally { - Objects.nullSafeClose(inputStream, gzipInputStream, outputStream); - } + protected byte[] doCompress(byte[] payload) throws IOException { + return writeAndClose(payload, WRAPPER); } - protected byte[] doCompress(byte[] payload) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - GZIPOutputStream compressorOutputStream = new GZIPOutputStream(outputStream, true); - try { - compressorOutputStream.write(payload, 0, payload.length); - compressorOutputStream.finish(); - return outputStream.toByteArray(); - } finally { - Objects.nullSafeClose(compressorOutputStream, outputStream); - } + @Override + protected byte[] doDecompress(byte[] compressed) throws IOException { + return readAndClose(new GZIPInputStream(new ByteArrayInputStream(compressed))); } }