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.buffer.Unpooled;
20  import io.netty.channel.ChannelPipeline;
21  import io.netty.util.AsciiString;
22  
23  /**
24   * Decodes {@link ByteBuf}s into {@link HttpRequest}s and {@link HttpContent}s.
25   *
26   * <h3>Parameters that prevents excessive memory consumption</h3>
27   * <table border="1">
28   * <tr>
29   * <th>Name</th><th>Meaning</th>
30   * </tr>
31   * <tr>
32   * <td>{@code maxInitialLineLength}</td>
33   * <td>The maximum length of the initial line (e.g. {@code "GET / HTTP/1.0"})
34   *     If the length of the initial line exceeds this value, a
35   *     {@link TooLongHttpLineException} will be raised.</td>
36   * </tr>
37   * <tr>
38   * <td>{@code maxHeaderSize}</td>
39   * <td>The maximum length of all headers.  If the sum of the length of each
40   *     header exceeds this value, a {@link TooLongHttpHeaderException} will be raised.</td>
41   * </tr>
42   * <tr>
43   * <td>{@code maxChunkSize}</td>
44   * <td>The maximum length of the content or each chunk.  If the content length
45   *     exceeds this value, the transfer encoding of the decoded request will be
46   *     converted to 'chunked' and the content will be split into multiple
47   *     {@link HttpContent}s.  If the transfer encoding of the HTTP request is
48   *     'chunked' already, each chunk will be split into smaller chunks if the
49   *     length of the chunk exceeds this value.  If you prefer not to handle
50   *     {@link HttpContent}s in your handler, insert {@link HttpObjectAggregator}
51   *     after this decoder in the {@link ChannelPipeline}.</td>
52   * </tr>
53   * </table>
54   *
55   * <h3>Parameters that control parsing behavior</h3>
56   * <table border="1">
57   * <tr>
58   * <th>Name</th><th>Default value</th><th>Meaning</th>
59   * </tr>
60   * <tr>
61   * <td>{@code allowDuplicateContentLengths}</td>
62   * <td>{@value #DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS}</td>
63   * <td>When set to {@code false}, will reject any messages that contain multiple Content-Length header fields.
64   *     When set to {@code true}, will allow multiple Content-Length headers only if they are all the same decimal value.
65   *     The duplicated field-values will be replaced with a single valid Content-Length field.
66   *     See <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">RFC 7230, Section 3.3.2</a>.</td>
67   * </tr>
68   * <tr>
69   * <td>{@code allowPartialChunks}</td>
70   * <td>{@value #DEFAULT_ALLOW_PARTIAL_CHUNKS}</td>
71   * <td>If the length of a chunk exceeds the {@link ByteBuf}s readable bytes and {@code allowPartialChunks}
72   *     is set to {@code true}, the chunk will be split into multiple {@link HttpContent}s.
73   *     Otherwise, if the chunk size does not exceed {@code maxChunkSize} and {@code allowPartialChunks}
74   *     is set to {@code false}, the {@link ByteBuf} is not decoded into an {@link HttpContent} until
75   *     the readable bytes are greater or equal to the chunk size.</td>
76   * </tr>
77   * </table>
78   *
79   * <h3>Header Validation</h3>
80   *
81   * It is recommended to always enable header validation.
82   * <p>
83   * Without header validation, your system can become vulnerable to
84   * <a href="https://cwe.mitre.org/data/definitions/113.html">
85   *     CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Response Splitting')
86   * </a>.
87   * <p>
88   * This recommendation stands even when both peers in the HTTP exchange are trusted,
89   * as it helps with defence-in-depth.
90   */
91  public class HttpRequestDecoder extends HttpObjectDecoder {
92  
93      private static final AsciiString Accept = AsciiString.cached("Accept");
94      private static final AsciiString Host = AsciiString.cached("Host");
95      private static final AsciiString Connection = AsciiString.cached("Connection");
96      private static final AsciiString ContentType = AsciiString.cached("Content-Type");
97      private static final AsciiString ContentLength = AsciiString.cached("Content-Length");
98  
99      private static final int GET_AS_INT = 'G' | 'E' << 8 | 'T' << 16;
100     private static final int POST_AS_INT = 'P' | 'O' << 8 | 'S' << 16 | 'T' << 24;
101     private static final long HTTP_1_1_AS_LONG = 'H' | 'T' << 8 | 'T' << 16 | 'P' << 24 | (long) '/' << 32 |
102             (long) '1' << 40 | (long) '.' << 48 | (long) '1' << 56;
103 
104     private static final long HTTP_1_0_AS_LONG = 'H' | 'T' << 8 | 'T' << 16 | 'P' << 24 | (long) '/' << 32 |
105             (long) '1' << 40 | (long) '.' << 48 | (long) '0' << 56;
106 
107     private static final int HOST_AS_INT = 'H' | 'o' << 8 | 's' << 16 | 't' << 24;
108 
109     private static final long CONNECTION_AS_LONG_0 = 'C' | 'o' << 8 | 'n' << 16 | 'n' << 24 |
110             (long) 'e' << 32 | (long) 'c' << 40 | (long) 't' << 48 | (long) 'i' << 56;
111 
112     private static final short CONNECTION_AS_SHORT_1 = 'o' | 'n' << 8;
113 
114     private static final long CONTENT_AS_LONG = 'C' | 'o' << 8 | 'n' << 16 | 't' << 24 |
115             (long) 'e' << 32 | (long) 'n' << 40 | (long) 't' << 48 | (long) '-' << 56;
116 
117     private static final int TYPE_AS_INT = 'T' | 'y' << 8 | 'p' << 16 | 'e' << 24;
118 
119     private static final long LENGTH_AS_LONG = 'L' | 'e' << 8 | 'n' << 16 | 'g' << 24 |
120             (long) 't' << 32 | (long) 'h' << 40;
121 
122     private static final long ACCEPT_AS_LONG = 'A' | 'c' << 8 | 'c' << 16 | 'e' << 24 |
123             (long) 'p' << 32 | (long) 't' << 40;
124 
125     /**
126      * Creates a new instance with the default
127      * {@code maxInitialLineLength (4096)}, {@code maxHeaderSize (8192)}, and
128      * {@code maxChunkSize (8192)}.
129      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
130      * the configuration parameters.
131      */
132     public HttpRequestDecoder() {
133     }
134 
135     /**
136      * Creates a new instance with the specified parameters.
137      *
138      * @param maxInitialLineLength the initial size of the temporary buffer used when parsing the lines of the
139      * HTTP headers.
140      * @param maxHeaderSize the maximum permitted combined size of all headers in any one request.
141      * @param maxChunkSize The maximum amount of data that the decoder will buffer
142      * before sending chunks down the pipeline.
143      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
144      * the configuration parameters.
145      */
146     public HttpRequestDecoder(
147             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
148         this(new HttpDecoderConfig()
149                 .setMaxInitialLineLength(maxInitialLineLength)
150                 .setMaxHeaderSize(maxHeaderSize)
151                 .setMaxChunkSize(maxChunkSize));
152     }
153 
154     /**
155      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
156      * to always have header validation enabled.
157      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
158      * the configuration parameters.
159      */
160     @Deprecated
161     public HttpRequestDecoder(
162             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
163         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders);
164     }
165 
166     /**
167      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
168      * to always have header validation enabled.
169      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
170      * the configuration parameters.
171      */
172     @Deprecated
173     public HttpRequestDecoder(
174             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
175             int initialBufferSize) {
176         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders,
177               initialBufferSize);
178     }
179 
180     /**
181      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
182      * to always have header validation enabled.
183      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
184      * the configuration parameters.
185      */
186     @Deprecated
187     public HttpRequestDecoder(
188             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
189             int initialBufferSize, boolean allowDuplicateContentLengths) {
190         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders,
191               initialBufferSize, allowDuplicateContentLengths);
192     }
193 
194     /**
195      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
196      * to always have header validation enabled.
197      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
198      * the configuration parameters.
199      */
200     @Deprecated
201     public HttpRequestDecoder(
202             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
203             int initialBufferSize, boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
204         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders,
205               initialBufferSize, allowDuplicateContentLengths, allowPartialChunks);
206     }
207 
208     /**
209      * Creates a new instance with the specified configuration.
210      * @see HttpDecoderConfig HttpDecoderConfig API documentation for detailed descriptions of
211      * the configuration parameters.
212      */
213     public HttpRequestDecoder(HttpDecoderConfig config) {
214         super(config);
215     }
216 
217     @Override
218     protected HttpMessage createMessage(String[] initialLine) throws Exception {
219         return new DefaultHttpRequest(
220                 // Do strict version checking
221                 HttpVersion.valueOf(initialLine[2], true),
222                 HttpMethod.valueOf(initialLine[0]), initialLine[1], headersFactory);
223     }
224 
225     @Override
226     protected AsciiString splitHeaderName(final byte[] sb, final int start, final int length) {
227         final byte firstChar = sb[start];
228         if (firstChar == 'H') {
229             if (length == 4 && isHost(sb, start)) {
230                 return Host;
231             }
232         } else if (firstChar == 'A') {
233             if (length == 6 && isAccept(sb, start)) {
234                 return Accept;
235             }
236         } else if (firstChar == 'C') {
237             if (length == 10) {
238                 if (isConnection(sb, start)) {
239                     return Connection;
240                 }
241             } else if (length == 12) {
242                 if (isContentType(sb, start)) {
243                     return ContentType;
244                 }
245             } else if (length == 14) {
246                 if (isContentLength(sb, start)) {
247                     return ContentLength;
248                 }
249             }
250         }
251         return super.splitHeaderName(sb, start, length);
252     }
253 
254     private static boolean isAccept(byte[] sb, int start) {
255         final long maybeAccept = sb[start] |
256                 sb[start + 1] << 8 |
257                 sb[start + 2] << 16 |
258                 sb[start + 3] << 24 |
259                 (long) sb[start + 4] << 32 |
260                 (long) sb[start + 5] << 40;
261         return maybeAccept == ACCEPT_AS_LONG;
262     }
263 
264     private static boolean isHost(byte[] sb, int start) {
265         final int maybeHost = sb[start] |
266                 sb[start + 1] << 8 |
267                 sb[start + 2] << 16 |
268                 sb[start + 3] << 24;
269         return maybeHost == HOST_AS_INT;
270     }
271 
272     private static boolean isConnection(byte[] sb, int start) {
273         final long maybeConnecti = sb[start] |
274                 sb[start + 1] << 8 |
275                 sb[start + 2] << 16 |
276                 sb[start + 3] << 24 |
277                 (long) sb[start + 4] << 32 |
278                 (long) sb[start + 5] << 40 |
279                 (long) sb[start + 6] << 48 |
280                 (long) sb[start + 7] << 56;
281         if (maybeConnecti != CONNECTION_AS_LONG_0) {
282             return false;
283         }
284         final short maybeOn = (short) (sb[start + 8] | sb[start + 9] << 8);
285         return maybeOn == CONNECTION_AS_SHORT_1;
286     }
287 
288     private static boolean isContentType(byte[] sb, int start) {
289         final long maybeContent = sb[start] |
290                 sb[start + 1] << 8 |
291                 sb[start + 2] << 16 |
292                 sb[start + 3] << 24 |
293                 (long) sb[start + 4] << 32 |
294                 (long) sb[start + 5] << 40 |
295                 (long) sb[start + 6] << 48 |
296                 (long) sb[start + 7] << 56;
297         if (maybeContent != CONTENT_AS_LONG) {
298             return false;
299         }
300         final int maybeType = sb[start + 8] |
301                 sb[start + 9] << 8 |
302                 sb[start + 10] << 16 |
303                 sb[start + 11] << 24;
304         return maybeType == TYPE_AS_INT;
305     }
306 
307     private static boolean isContentLength(byte[] sb, int start) {
308         final long maybeContent = sb[start] |
309                 sb[start + 1] << 8 |
310                 sb[start + 2] << 16 |
311                 sb[start + 3] << 24 |
312                 (long) sb[start + 4] << 32 |
313                 (long) sb[start + 5] << 40 |
314                 (long) sb[start + 6] << 48 |
315                 (long) sb[start + 7] << 56;
316         if (maybeContent != CONTENT_AS_LONG) {
317             return false;
318         }
319         final long maybeLength = sb[start + 8] |
320                 sb[start + 9] << 8 |
321                 sb[start + 10] << 16 |
322                 sb[start + 11] << 24 |
323                 (long) sb[start + 12] << 32 |
324                 (long) sb[start + 13] << 40;
325         return maybeLength == LENGTH_AS_LONG;
326     }
327 
328     private static boolean isGetMethod(final byte[] sb, int start) {
329         final int maybeGet = sb[start] |
330                 sb[start + 1] << 8 |
331                 sb[start + 2] << 16;
332         return maybeGet == GET_AS_INT;
333     }
334 
335     private static boolean isPostMethod(final byte[] sb, int start) {
336         final int maybePost = sb[start] |
337                 sb[start + 1] << 8 |
338                 sb[start + 2] << 16 |
339                 sb[start + 3] << 24;
340         return maybePost == POST_AS_INT;
341     }
342 
343     @Override
344     protected String splitFirstWordInitialLine(final byte[] sb, final int start, final int length) {
345         if (length == 3) {
346             if (isGetMethod(sb, start)) {
347                 return HttpMethod.GET.name();
348             }
349         } else if (length == 4) {
350             if (isPostMethod(sb, start)) {
351                 return HttpMethod.POST.name();
352             }
353         }
354         return super.splitFirstWordInitialLine(sb, start, length);
355     }
356 
357     @Override
358     protected String splitThirdWordInitialLine(final byte[] sb, final int start, final int length) {
359         if (length == 8) {
360             final long maybeHttp1_x = sb[start] |
361                     sb[start + 1] << 8 |
362                     sb[start + 2] << 16 |
363                     sb[start + 3] << 24 |
364                     (long) sb[start + 4] << 32 |
365                     (long) sb[start + 5] << 40 |
366                     (long) sb[start + 6] << 48 |
367                     (long) sb[start + 7] << 56;
368             if (maybeHttp1_x == HTTP_1_1_AS_LONG) {
369                 return HttpVersion.HTTP_1_1_STRING;
370             } else if (maybeHttp1_x == HTTP_1_0_AS_LONG) {
371                 return HttpVersion.HTTP_1_0_STRING;
372             }
373         }
374         return super.splitThirdWordInitialLine(sb, start, length);
375     }
376 
377     @Override
378     protected HttpMessage createInvalidMessage() {
379         return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request",
380                 Unpooled.buffer(0), headersFactory, trailersFactory);
381     }
382 
383     @Override
384     protected boolean isDecodingRequest() {
385         return true;
386     }
387 
388     @Override
389     protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
390         // fast-path to save expensive O(n) checks; users can override createMessage
391         // and extends DefaultHttpRequest making implementing HttpResponse:
392         // this is why we cannot use instanceof DefaultHttpRequest here :(
393         if (msg.getClass() == DefaultHttpRequest.class) {
394             return false;
395         }
396         return super.isContentAlwaysEmpty(msg);
397     }
398 }