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