View Javadoc
1   /*
2    * Copyright 2014 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.spdy;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.buffer.ByteBufAllocator;
20  
21  import java.util.zip.DataFormatException;
22  import java.util.zip.Inflater;
23  
24  import static io.netty.handler.codec.spdy.SpdyCodecUtil.*;
25  
26  final class SpdyHeaderBlockZlibDecoder extends SpdyHeaderBlockRawDecoder {
27  
28      private static final int DEFAULT_BUFFER_CAPACITY = 4096;
29      private static final SpdyProtocolException INVALID_HEADER_BLOCK =
30              new SpdyProtocolException("Invalid Header Block");
31  
32      // Legitimate header blocks are bounded by maxHeaderSize, but framing overhead (the header
33      // count and the per-header name/value length prefixes) means the decompressed size of a
34      // wire-legitimate header block can exceed maxHeaderSize itself. A generous multiplier is used
35      // instead of an equal cap so this never rejects well-formed header blocks, while still
36      // preventing a maliciously compressible header block from forcing an effectively unbounded
37      // number of Inflater#inflate() calls on the calling (event-loop) thread.
38      private static final int MAX_DECOMPRESSED_SIZE_MULTIPLIER = 16;
39  
40      private final Inflater decompressor = new Inflater();
41      private final long maxDecompressedSize;
42  
43      private ByteBuf decompressed;
44      private long totalInflated;
45  
46      SpdyHeaderBlockZlibDecoder(SpdyVersion spdyVersion, int maxHeaderSize) {
47          super(spdyVersion, maxHeaderSize);
48          maxDecompressedSize = calculateMaxDecompressedSizePerBlock(maxHeaderSize);
49      }
50  
51      static long calculateMaxDecompressedSizePerBlock(int maxHeaderSize) {
52          return (long) maxHeaderSize * MAX_DECOMPRESSED_SIZE_MULTIPLIER;
53      }
54  
55      @Override
56      void decode(ByteBufAllocator alloc, ByteBuf headerBlock, SpdyHeadersFrame frame) throws Exception {
57          int len = setInput(headerBlock);
58  
59          int numBytes;
60          do {
61              numBytes = decompress(alloc, frame);
62              totalInflated += numBytes;
63              if (totalInflated > maxDecompressedSize) {
64                  throw new SpdyProtocolException(
65                          "Decompressed header block exceeds " + maxDecompressedSize + " bytes");
66              }
67          } while (numBytes > 0);
68  
69          // z_stream has an internal 64-bit hold buffer
70          // it is always capable of consuming the entire input
71          if (decompressor.getRemaining() != 0) {
72              // we reached the end of the deflate stream
73              throw INVALID_HEADER_BLOCK;
74          }
75  
76          headerBlock.skipBytes(len);
77      }
78  
79      private int setInput(ByteBuf compressed) {
80          int len = compressed.readableBytes();
81  
82          if (compressed.hasArray()) {
83              decompressor.setInput(compressed.array(), compressed.arrayOffset() + compressed.readerIndex(), len);
84          } else {
85              byte[] in = new byte[len];
86              compressed.getBytes(compressed.readerIndex(), in);
87              decompressor.setInput(in, 0, in.length);
88          }
89  
90          return len;
91      }
92  
93      private int decompress(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception {
94          ensureBuffer(alloc);
95          byte[] out = decompressed.array();
96          int off = decompressed.arrayOffset() + decompressed.writerIndex();
97          try {
98              int numBytes = decompressor.inflate(out, off, decompressed.writableBytes());
99              if (numBytes == 0 && decompressor.needsDictionary()) {
100                 try {
101                     decompressor.setDictionary(SPDY_DICT);
102                 } catch (IllegalArgumentException ignored) {
103                     throw INVALID_HEADER_BLOCK;
104                 }
105                 numBytes = decompressor.inflate(out, off, decompressed.writableBytes());
106             }
107             if (frame != null) {
108                 decompressed.writerIndex(decompressed.writerIndex() + numBytes);
109                 decodeHeaderBlock(decompressed, frame);
110                 decompressed.discardReadBytes();
111             }
112 
113             return numBytes;
114         } catch (DataFormatException e) {
115             throw new SpdyProtocolException("Received invalid header block", e);
116         }
117     }
118 
119     private void ensureBuffer(ByteBufAllocator alloc) {
120         if (decompressed == null) {
121             decompressed = alloc.heapBuffer(DEFAULT_BUFFER_CAPACITY);
122         }
123         decompressed.ensureWritable(1);
124     }
125 
126     @Override
127     void endHeaderBlock(SpdyHeadersFrame frame) throws Exception {
128         super.endHeaderBlock(frame);
129         releaseBuffer();
130         totalInflated = 0;
131     }
132 
133     @Override
134     public void end() {
135         super.end();
136         releaseBuffer();
137         decompressor.end();
138     }
139 
140     private void releaseBuffer() {
141         if (decompressed != null) {
142             decompressed.release();
143             decompressed = null;
144         }
145     }
146 }