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
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,22 @@ public class CompressionFilter extends IoFilterAdapter {
private boolean compressOutbound = true;

private int compressionLevel;

/** The maximum decompressed size, to avoid an OOM. Default to 1Mb */
private int maxDecompressedSize;

/** Maximum decompression ratio **/
private final long maxDecompressRatio;

/** Grace size before decompression ratio check is enforced **/
private final long decompressRatioMinSize;

/**
* Creates a new instance which compresses outboud data and decompresses
* inbound data with default compression level.
*/
public CompressionFilter() {
this(true, true, COMPRESSION_DEFAULT, Zlib.MAX_DECOMPRESSED_SIZE);
this(true, true, COMPRESSION_DEFAULT, Zlib.MAX_DECOMPRESSED_SIZE, Zlib.MAX_DECOMPRESS_RATIO, Zlib.DECOMPRESS_RATIO_MIN_SIZE);
}

/**
Expand All @@ -127,7 +133,7 @@ public CompressionFilter() {
* {@link #COMPRESSION_NONE}.
*/
public CompressionFilter(final int compressionLevel) {
this(true, true, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE);
this(true, true, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE, Zlib.MAX_DECOMPRESS_RATIO, Zlib.DECOMPRESS_RATIO_MIN_SIZE);
}

/**
Expand All @@ -143,7 +149,7 @@ public CompressionFilter(final int compressionLevel) {
*/
public CompressionFilter(final boolean compressInbound, final boolean compressOutbound,
final int compressionLevel) {
this(true, true, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE);
this(compressInbound, compressOutbound, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE, Zlib.MAX_DECOMPRESS_RATIO, Zlib.DECOMPRESS_RATIO_MIN_SIZE);
}

/**
Expand All @@ -159,15 +165,43 @@ public CompressionFilter(final boolean compressInbound, final boolean compressOu
* {@link #COMPRESSION_MIN}, and
* {@link #COMPRESSION_NONE}.
* @param maxDecompressedSize The maximum size for a buffer when inflating some data
* @since 2.2.8
*/
public CompressionFilter(final boolean compressInbound, final boolean compressOutbound,
public CompressionFilter(final boolean compressInbound, final boolean compressOutbound,
final int compressionLevel, final int maxDecompressedSize) {
this(compressInbound, compressOutbound, compressionLevel, maxDecompressedSize, Zlib.MAX_DECOMPRESS_RATIO, Zlib.DECOMPRESS_RATIO_MIN_SIZE);
}

/**
* Creates a new instance with explicit zip-bomb protection parameters.
*
* @param compressInbound <code>true</code> if data read is to be decompressed
* @param compressOutbound <code>true</code> if data written is to be compressed
* @param compressionLevel the level of compression to be used. Must
* be one of {@link #COMPRESSION_DEFAULT},
* {@link #COMPRESSION_MAX},
* {@link #COMPRESSION_MIN}, and
* {@link #COMPRESSION_NONE}.
* @param maxDecompressedSize the maximum size for a buffer when inflating data
* @param maxDecompressRatio the maximum allowed cumulative ratio of
* decompressed to compressed bytes.
* A value &lt;= 0 disables the check.
* @param decompressRatioMinSize the minimum cumulative decompressed size
* below which the ratio check is skipped.
* @since 2.2.8
*/
public CompressionFilter(final boolean compressInbound, final boolean compressOutbound,
final int compressionLevel, final int maxDecompressedSize,
final long maxDecompressRatio, final long decompressRatioMinSize) {
this.compressionLevel = compressionLevel;
this.compressInbound = compressInbound;
this.compressOutbound = compressOutbound;
this.maxDecompressedSize = maxDecompressedSize;
this.maxDecompressRatio = maxDecompressRatio;
this.decompressRatioMinSize = decompressRatioMinSize;
}



/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -239,8 +273,8 @@ public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) t
throw new IllegalStateException("Only one " + CompressionFilter.class + " is permitted.");
}

Zlib deflater = new Zlib(compressionLevel, Zlib.MODE_DEFLATER);
Zlib inflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER, maxDecompressedSize);
Zlib deflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER, maxDecompressedSize, maxDecompressRatio, decompressRatioMinSize);
Zlib inflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER, maxDecompressedSize, maxDecompressRatio, decompressRatioMinSize);

IoSession session = parent.getSession();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,32 @@ class Zlib {

/** The requested compression level */
private int compressionLevel;

/** The maximum size of an inflated buffer. Default to 1Mb */
/* Package protected */
/* Package protected */
static final int MAX_DECOMPRESSED_SIZE = Integer.MAX_VALUE;

/**
* Default maximum decompression ratio (decompressed / compressed).
*/
/* Package protected */
static final long MAX_DECOMPRESS_RATIO = 100L;

/**
* Grace size before decompression ratio check is enforced.
*
* <p>Below this threshold the check is skipped to avoid false positives on small payloads where framing/header
* overhead dominates the ratio.</p>
*/
/* Package protected */
static final long DECOMPRESS_RATIO_MIN_SIZE = 1024L * 1024L;

private int maxDecompressedSize = MAX_DECOMPRESSED_SIZE;

private long maxDecompressRatio = MAX_DECOMPRESS_RATIO;

private long decompressRatioMinSize = DECOMPRESS_RATIO_MIN_SIZE;

/** The inner stream used to inflate or deflate the data */
private ZStream zStream = null;

Expand All @@ -78,47 +97,28 @@ class Zlib {
* @throws IllegalArgumentException if the mode is incorrect
*/
public Zlib(int compressionLevel, int mode) {
switch (compressionLevel) {
case COMPRESSION_MAX:
case COMPRESSION_MIN:
case COMPRESSION_NONE:
case COMPRESSION_DEFAULT:
this.compressionLevel = compressionLevel;
break;
default:
throw new IllegalArgumentException("invalid compression level specified");
}

// create a new instance of ZStream. This will be done only once.
zStream = new ZStream();

switch (mode) {
case MODE_DEFLATER:
zStream.deflateInit(this.compressionLevel);
break;
case MODE_INFLATER:
zStream.inflateInit();
break;
default:
throw new IllegalArgumentException("invalid mode specified");
}

this.mode = mode;
this(compressionLevel, mode, MAX_DECOMPRESSED_SIZE, MAX_DECOMPRESS_RATIO, DECOMPRESS_RATIO_MIN_SIZE);
}


/**
* Creates an instance of the ZLib class.
*
*
* @param compressionLevel the level of compression that should be used. One of
* <code>COMPRESSION_MAX</code>, <code>COMPRESSION_MIN</code>,
* <code>COMPRESSION_NONE</code> or <code>COMPRESSION_DEFAULT</code>
* @param mode the mode in which the instance will operate. Can be either
* of <code>MODE_DEFLATER</code> or <code>MODE_INFLATER</code>
* @param maxDecompressedSize The maximum inflation size for a buffer. Default to 1MB
* @param maxDecompressedSize the maximum inflation size for a buffer
* @param maxDecompressRatio the maximum allowed ratio of decompressed to
* compressed bytes, evaluated cumulatively over the lifetime of this
* inflater. A value &lt;= 0 disables the check.
* @param decompressRatioMinSize the minimum cumulative decompressed size
* (in bytes) below which the ratio check is skipped.
* @throws IllegalArgumentException if the mode is incorrect
*/
public Zlib(int compressionLevel, int mode, int maxDecompressedSize) {
public Zlib(int compressionLevel, int mode, int maxDecompressedSize,
long maxDecompressRatio, long decompressRatioMinSize) {
switch (compressionLevel) {
case COMPRESSION_MAX:
case COMPRESSION_MIN:
Expand All @@ -139,6 +139,8 @@ public Zlib(int compressionLevel, int mode, int maxDecompressedSize) {
break;
case MODE_INFLATER:
this.maxDecompressedSize = maxDecompressedSize;
this.maxDecompressRatio = maxDecompressRatio;
this.decompressRatioMinSize = decompressRatioMinSize;
zStream.inflateInit();
break;
default:
Expand All @@ -147,7 +149,7 @@ public Zlib(int compressionLevel, int mode, int maxDecompressedSize) {

this.mode = mode;
}


/**
* Uncompress the given buffer, returning it in a new buffer.
Expand Down Expand Up @@ -188,12 +190,14 @@ public IoBuffer inflate(IoBuffer inBuffer) throws IOException {
case JZlib.Z_OK:
// completed decompression, lets copy data and get out
case JZlib.Z_BUF_ERROR:
// Try to avoid exhausting the JVM memory by controling the resulting buffer
// Try to avoid exhausting the JVM memory by controling the resulting buffer
// size after inflation
if (outBuffer.position() + zStream.next_out_index > maxDecompressedSize) {
throw new IOException("decompressed size exceeds max " + maxDecompressedSize);
}


checkDecompressRatio();

// need more space for output. store current output and get more
outBuffer.put(outBytes, 0, zStream.next_out_index);
zStream.next_out_index = 0;
Expand Down Expand Up @@ -262,6 +266,22 @@ public IoBuffer deflate(IoBuffer inBuffer) throws IOException {
}
}

/**
* Checks the cumulative decompression ratio against the configured maximum.
*
* @throws IOException if the cumulative ratio exceeds {@code maxDecompressRatio}
*/
private void checkDecompressRatio() throws IOException {
if (maxDecompressRatio <= 0L) {
return;
}
long totalOut = zStream.getTotalOut();
long totalIn = zStream.getTotalIn();
if (totalIn > 0L && totalOut > decompressRatioMinSize && totalOut / totalIn > maxDecompressRatio) {
throw new IOException("decompression ratio " + (totalOut / totalIn) + " exceeds max " + maxDecompressRatio);
}
}

/**
* Cleans up the resources used by the compression library.
*/
Expand Down
Loading