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