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