View Javadoc
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.http;
17  
18  import io.netty.buffer.ByteBuf;
19  import io.netty.channel.ChannelFutureListener;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.channel.ChannelPromise;
22  import io.netty.channel.CombinedChannelDuplexHandler;
23  import io.netty.util.ReferenceCountUtil;
24  import io.netty.util.internal.ObjectUtil;
25  
26  import java.util.ArrayDeque;
27  import java.util.List;
28  import java.util.Queue;
29  
30  import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_CHUNK_SIZE;
31  import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_HEADER_SIZE;
32  import static io.netty.handler.codec.http.HttpObjectDecoder.DEFAULT_MAX_INITIAL_LINE_LENGTH;
33  
34  /**
35   * A combination of {@link HttpRequestDecoder} and {@link HttpResponseEncoder}
36   * which enables easier server side HTTP implementation.
37   *
38   * <h3>Header Validation</h3>
39   *
40   * It is recommended to always enable header validation.
41   * <p>
42   * Without header validation, your system can become vulnerable to
43   * <a href="https://cwe.mitre.org/data/definitions/113.html">
44   *     CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
45   * </a>.
46   * <p>
47   * This recommendation stands even when both peers in the HTTP exchange are trusted,
48   * as it helps with defence-in-depth.
49   *
50   * @see HttpClientCodec
51   */
52  public final class HttpServerCodec extends CombinedChannelDuplexHandler<HttpRequestDecoder, HttpResponseEncoder>
53          implements HttpServerUpgradeHandler.SourceCodec {
54  
55      /**
56       * The maximum number of pipelined requests we allow to be awaiting a response by default, before
57       * decoding of further requests is rejected. This bounds the memory a single connection can force us
58       * to hold onto if the peer pipelines requests without reading the corresponding responses.
59       */
60      static final int DEFAULT_MAX_PIPELINE_DEPTH = 128;
61  
62      /** A queue that is used for correlating a request and a response. */
63      private final Queue<HttpMethod> queue = new ArrayDeque<HttpMethod>();
64      private final int maxPipelineDepth;
65  
66      /**
67       * When set, the connection will be closed after the next response is written.
68       */
69      private boolean mustCloseAfterResponse;
70  
71      /**
72       * Creates a new instance with the default decoder options
73       * ({@code maxInitialLineLength (4096)}, {@code maxHeaderSize (8192)}, and
74       * {@code maxChunkSize (8192)}).
75       */
76      public HttpServerCodec() {
77          this(DEFAULT_MAX_INITIAL_LINE_LENGTH, DEFAULT_MAX_HEADER_SIZE, DEFAULT_MAX_CHUNK_SIZE);
78      }
79  
80      /**
81       * Creates a new instance with the specified decoder options.
82       */
83      public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
84          this(new HttpDecoderConfig()
85                  .setMaxInitialLineLength(maxInitialLineLength)
86                  .setMaxHeaderSize(maxHeaderSize)
87                  .setMaxChunkSize(maxChunkSize));
88      }
89  
90      /**
91       * Creates a new instance with the specified decoder options.
92       *
93       * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor,
94       * to always enable header validation.
95       */
96      @Deprecated
97      public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
98          this(new HttpDecoderConfig()
99                  .setMaxInitialLineLength(maxInitialLineLength)
100                 .setMaxHeaderSize(maxHeaderSize)
101                 .setMaxChunkSize(maxChunkSize)
102                 .setValidateHeaders(validateHeaders));
103     }
104 
105     /**
106      * Creates a new instance with the specified decoder options.
107      *
108      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor, to always enable header
109      * validation.
110      */
111     @Deprecated
112     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
113                            int initialBufferSize) {
114         this(new HttpDecoderConfig()
115                 .setMaxInitialLineLength(maxInitialLineLength)
116                 .setMaxHeaderSize(maxHeaderSize)
117                 .setMaxChunkSize(maxChunkSize)
118                 .setValidateHeaders(validateHeaders)
119                 .setInitialBufferSize(initialBufferSize));
120     }
121 
122     /**
123      * Creates a new instance with the specified decoder options.
124      *
125      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor,
126      * to always enable header validation.
127      */
128     @Deprecated
129     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
130                            int initialBufferSize, boolean allowDuplicateContentLengths) {
131         this(new HttpDecoderConfig()
132                 .setMaxInitialLineLength(maxInitialLineLength)
133                 .setMaxHeaderSize(maxHeaderSize)
134                 .setMaxChunkSize(maxChunkSize)
135                 .setValidateHeaders(validateHeaders)
136                 .setInitialBufferSize(initialBufferSize)
137                 .setAllowDuplicateContentLengths(allowDuplicateContentLengths));
138     }
139 
140     /**
141      * Creates a new instance with the specified decoder options.
142      *
143      * @deprecated Prefer the {@link #HttpServerCodec(HttpDecoderConfig)} constructor,
144      * to always enable header validation.
145      */
146     @Deprecated
147     public HttpServerCodec(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
148                            int initialBufferSize, boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
149         this(new HttpDecoderConfig()
150                 .setMaxInitialLineLength(maxInitialLineLength)
151                 .setMaxHeaderSize(maxHeaderSize)
152                 .setMaxChunkSize(maxChunkSize)
153                 .setValidateHeaders(validateHeaders)
154                 .setInitialBufferSize(initialBufferSize)
155                 .setAllowDuplicateContentLengths(allowDuplicateContentLengths)
156                 .setAllowPartialChunks(allowPartialChunks));
157     }
158 
159     /**
160      * Creates a new instance with the specified decoder configuration.
161      */
162     public HttpServerCodec(HttpDecoderConfig config) {
163         this(config, DEFAULT_MAX_PIPELINE_DEPTH);
164     }
165 
166     /**
167      * Creates a new instance with the specified decoder configuration.
168      *
169      * @param config the decoder configuration.
170      * @param maxPipelineDepth the maximum number of requests that may be decoded while awaiting the
171      *                         corresponding responses to be written, before decoding of further requests
172      *                         is rejected with an {@link IllegalStateException}.
173      */
174     public HttpServerCodec(HttpDecoderConfig config, int maxPipelineDepth) {
175         this.maxPipelineDepth = ObjectUtil.checkPositive(maxPipelineDepth, "maxPipelineDepth");
176         init(new HttpServerRequestDecoder(config), new HttpServerResponseEncoder());
177     }
178 
179     /**
180      * Upgrades to another protocol from HTTP. Removes the {@link HttpRequestDecoder} and
181      * {@link HttpResponseEncoder} from the pipeline.
182      */
183     @Override
184     public void upgradeFrom(ChannelHandlerContext ctx) {
185         ctx.pipeline().remove(this);
186     }
187 
188     private boolean enqueueMethod(HttpMethod method) {
189         int currentDepth = queue.size();
190         if (currentDepth >= maxPipelineDepth) {
191             return false;
192         }
193 
194         queue.add(method);
195         return true;
196     }
197 
198     private final class HttpServerRequestDecoder extends HttpRequestDecoder {
199         private boolean discard;
200 
201         HttpServerRequestDecoder(HttpDecoderConfig config) {
202             super(config);
203         }
204 
205         @Override
206         protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
207             if (discard) {
208                 buffer.skipBytes(buffer.readableBytes());
209                 return;
210             }
211             int oldSize = out.size();
212             super.decode(ctx, buffer, out);
213             int size = out.size();
214             for (int i = oldSize; i < size; i++) {
215                 Object obj = out.get(i);
216                 if (obj instanceof HttpRequest) {
217                     if (!enqueueMethod(((HttpRequest) obj).method())) {
218                         // We hit the limit, let's discard everything and release everything and also ensure
219                         // we close the connection once the first response is written back.
220                         mustCloseAfterResponse = true;
221                         discard = true;
222                         ReferenceCountUtil.release(obj);
223                         while (++i < size) {
224                             ReferenceCountUtil.release(out.get(i));
225                         }
226                         out.clear();
227                         throw new IllegalStateException("maxPipelineDepth exceeded: " + maxPipelineDepth);
228                     }
229                 }
230             }
231         }
232 
233         @Override
234         protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) {
235             super.handleTransferEncodingChunkedWithContentLength(message);
236             mustCloseAfterResponse = true;
237         }
238     }
239 
240     private final class HttpServerResponseEncoder extends HttpResponseEncoder {
241 
242         private HttpMethod method;
243 
244         @Override
245         public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
246             if (mustCloseAfterResponse && msg instanceof LastHttpContent) {
247                 mustCloseAfterResponse = false;
248                 promise = promise.unvoid().addListener(ChannelFutureListener.CLOSE);
249             }
250             super.write(ctx, msg, promise);
251         }
252 
253         @Override
254         protected void sanitizeHeadersBeforeEncode(HttpResponse msg, boolean isAlwaysEmpty) {
255             if (!isAlwaysEmpty && HttpMethod.CONNECT.equals(method)
256                     && msg.status().codeClass() == HttpStatusClass.SUCCESS) {
257                 // Stripping Transfer-Encoding:
258                 // See https://tools.ietf.org/html/rfc7230#section-3.3.1
259                 msg.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
260                 return;
261             }
262 
263             super.sanitizeHeadersBeforeEncode(msg, isAlwaysEmpty);
264         }
265 
266         @Override
267         protected boolean isContentAlwaysEmpty(@SuppressWarnings("unused") HttpResponse msg) {
268             if (msg.status().codeClass() == HttpStatusClass.INFORMATIONAL) {
269                 // An informational response should be excluded from paired comparison. This covers 101 as well:
270                 // once the protocol is switched this handler is removed from the pipeline, so the entry that is
271                 // left behind goes away with it. Just delegate to super method which has all the needed handling.
272                 return super.isContentAlwaysEmpty(msg);
273             }
274             method = queue.poll();
275             return HttpMethod.HEAD.equals(method) || super.isContentAlwaysEmpty(msg);
276         }
277     }
278 }