1 /* 2 * Copyright 2012 The Netty Project 3 * 4 * The Netty Project licenses this file to you under the Apache License, 5 * version 2.0 (the "License"); you may not use this file except in compliance 6 * with the License. You may obtain a copy of the License at: 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package io.netty.handler.codec.compression; 17 18 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; 19 20 import io.netty.buffer.ByteBuf; 21 import io.netty.buffer.ByteBufAllocator; 22 import io.netty.channel.ChannelHandlerContext; 23 import io.netty.handler.codec.ByteToMessageDecoder; 24 25 /** 26 * Decompresses a {@link ByteBuf} using the deflate algorithm. 27 */ 28 public abstract class ZlibDecoder extends ByteToMessageDecoder { 29 30 /** 31 * Maximum allowed size of the decompression buffer. 32 */ 33 protected final int maxAllocation; 34 35 /** 36 * Same as {@link #ZlibDecoder(int)} with maxAllocation = 0. 37 */ 38 public ZlibDecoder() { 39 this(0); 40 } 41 42 /** 43 * Construct a new ZlibDecoder. 44 * @param maxAllocation 45 * Maximum size of the decompression buffer. Must be >= 0. 46 * If zero, maximum size is decided by the {@link ByteBufAllocator}. 47 */ 48 public ZlibDecoder(int maxAllocation) { 49 this.maxAllocation = checkPositiveOrZero(maxAllocation, "maxAllocation"); 50 } 51 52 /** 53 * Returns {@code true} if and only if the end of the compressed stream 54 * has been reached. 55 */ 56 public abstract boolean isClosed(); 57 58 /** 59 * Allocate or expand the decompression buffer, without exceeding the maximum allocation. 60 * Calls {@link #decompressionBufferExhausted(ByteBuf)} if the buffer is full and cannot be expanded further. 61 */ 62 protected ByteBuf prepareDecompressBuffer(ChannelHandlerContext ctx, ByteBuf buffer, int preferredSize) { 63 if (buffer == null) { 64 if (maxAllocation == 0) { 65 return ctx.alloc().heapBuffer(preferredSize); 66 } 67 68 return ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), maxAllocation); 69 } 70 71 // this always expands the buffer if possible, even if the expansion is less than preferredSize 72 // we throw the exception only if the buffer could not be expanded at all 73 // this means that one final attempt to deserialize will always be made with the buffer at maxAllocation 74 if (buffer.ensureWritable(preferredSize, true) == 1) { 75 // buffer must be consumed so subclasses don't add it to output 76 // we therefore duplicate it when calling decompressionBufferExhausted() to guarantee non-interference 77 // but wait until after to consume it so the subclass can tell how much output is really in the buffer 78 decompressionBufferExhausted(buffer.duplicate()); 79 buffer.skipBytes(buffer.readableBytes()); 80 throw new DecompressionException("Decompression buffer has reached maximum size: " + buffer.maxCapacity()); 81 } 82 83 return buffer; 84 } 85 86 /** 87 * Called when the decompression buffer cannot be expanded further. 88 * Default implementation is a no-op, but subclasses can override in case they want to 89 * do something before the {@link DecompressionException} is thrown, such as log the 90 * data that was decompressed so far. 91 */ 92 protected void decompressionBufferExhausted(ByteBuf buffer) { 93 } 94 95 }