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      */
130     public HttpRequestDecoder() {
131     }
132 
133     /**
134      * Creates a new instance with the specified parameters.
135      */
136     public HttpRequestDecoder(
137             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) {
138         this(new HttpDecoderConfig()
139                 .setMaxInitialLineLength(maxInitialLineLength)
140                 .setMaxHeaderSize(maxHeaderSize)
141                 .setMaxChunkSize(maxChunkSize));
142     }
143 
144     /**
145      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
146      * to always have header validation enabled.
147      */
148     @Deprecated
149     public HttpRequestDecoder(
150             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) {
151         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders);
152     }
153 
154     /**
155      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
156      * to always have header validation enabled.
157      */
158     @Deprecated
159     public HttpRequestDecoder(
160             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
161             int initialBufferSize) {
162         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders,
163               initialBufferSize);
164     }
165 
166     /**
167      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
168      * to always have header validation enabled.
169      */
170     @Deprecated
171     public HttpRequestDecoder(
172             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
173             int initialBufferSize, boolean allowDuplicateContentLengths) {
174         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders,
175               initialBufferSize, allowDuplicateContentLengths);
176     }
177 
178     /**
179      * @deprecated Prefer the {@link #HttpRequestDecoder(HttpDecoderConfig)} constructor,
180      * to always have header validation enabled.
181      */
182     @Deprecated
183     public HttpRequestDecoder(
184             int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
185             int initialBufferSize, boolean allowDuplicateContentLengths, boolean allowPartialChunks) {
186         super(maxInitialLineLength, maxHeaderSize, maxChunkSize, DEFAULT_CHUNKED_SUPPORTED, validateHeaders,
187               initialBufferSize, allowDuplicateContentLengths, allowPartialChunks);
188     }
189 
190     /**
191      * Creates a new instance with the specified configuration.
192      */
193     public HttpRequestDecoder(HttpDecoderConfig config) {
194         super(config);
195     }
196 
197     @Override
198     protected HttpMessage createMessage(String[] initialLine) throws Exception {
199         return new DefaultHttpRequest(
200                 // Do strict version checking
201                 HttpVersion.valueOf(initialLine[2], true),
202                 HttpMethod.valueOf(initialLine[0]), initialLine[1], headersFactory);
203     }
204 
205     @Override
206     protected AsciiString splitHeaderName(final byte[] sb, final int start, final int length) {
207         final byte firstChar = sb[start];
208         if (firstChar == 'H') {
209             if (length == 4 && isHost(sb, start)) {
210                 return Host;
211             }
212         } else if (firstChar == 'A') {
213             if (length == 6 && isAccept(sb, start)) {
214                 return Accept;
215             }
216         } else if (firstChar == 'C') {
217             if (length == 10) {
218                 if (isConnection(sb, start)) {
219                     return Connection;
220                 }
221             } else if (length == 12) {
222                 if (isContentType(sb, start)) {
223                     return ContentType;
224                 }
225             } else if (length == 14) {
226                 if (isContentLength(sb, start)) {
227                     return ContentLength;
228                 }
229             }
230         }
231         return super.splitHeaderName(sb, start, length);
232     }
233 
234     private static boolean isAccept(byte[] sb, int start) {
235         final long maybeAccept = sb[start] |
236                 sb[start + 1] << 8 |
237                 sb[start + 2] << 16 |
238                 sb[start + 3] << 24 |
239                 (long) sb[start + 4] << 32 |
240                 (long) sb[start + 5] << 40;
241         return maybeAccept == ACCEPT_AS_LONG;
242     }
243 
244     private static boolean isHost(byte[] sb, int start) {
245         final int maybeHost = sb[start] |
246                 sb[start + 1] << 8 |
247                 sb[start + 2] << 16 |
248                 sb[start + 3] << 24;
249         return maybeHost == HOST_AS_INT;
250     }
251 
252     private static boolean isConnection(byte[] sb, int start) {
253         final long maybeConnecti = sb[start] |
254                 sb[start + 1] << 8 |
255                 sb[start + 2] << 16 |
256                 sb[start + 3] << 24 |
257                 (long) sb[start + 4] << 32 |
258                 (long) sb[start + 5] << 40 |
259                 (long) sb[start + 6] << 48 |
260                 (long) sb[start + 7] << 56;
261         if (maybeConnecti != CONNECTION_AS_LONG_0) {
262             return false;
263         }
264         final short maybeOn = (short) (sb[start + 8] | sb[start + 9] << 8);
265         return maybeOn == CONNECTION_AS_SHORT_1;
266     }
267 
268     private static boolean isContentType(byte[] sb, int start) {
269         final long maybeContent = sb[start] |
270                 sb[start + 1] << 8 |
271                 sb[start + 2] << 16 |
272                 sb[start + 3] << 24 |
273                 (long) sb[start + 4] << 32 |
274                 (long) sb[start + 5] << 40 |
275                 (long) sb[start + 6] << 48 |
276                 (long) sb[start + 7] << 56;
277         if (maybeContent != CONTENT_AS_LONG) {
278             return false;
279         }
280         final int maybeType = sb[start + 8] |
281                 sb[start + 9] << 8 |
282                 sb[start + 10] << 16 |
283                 sb[start + 11] << 24;
284         return maybeType == TYPE_AS_INT;
285     }
286 
287     private static boolean isContentLength(byte[] sb, int start) {
288         final long maybeContent = sb[start] |
289                 sb[start + 1] << 8 |
290                 sb[start + 2] << 16 |
291                 sb[start + 3] << 24 |
292                 (long) sb[start + 4] << 32 |
293                 (long) sb[start + 5] << 40 |
294                 (long) sb[start + 6] << 48 |
295                 (long) sb[start + 7] << 56;
296         if (maybeContent != CONTENT_AS_LONG) {
297             return false;
298         }
299         final long maybeLength = sb[start + 8] |
300                 sb[start + 9] << 8 |
301                 sb[start + 10] << 16 |
302                 sb[start + 11] << 24 |
303                 (long) sb[start + 12] << 32 |
304                 (long) sb[start + 13] << 40;
305         return maybeLength == LENGTH_AS_LONG;
306     }
307 
308     private static boolean isGetMethod(final byte[] sb, int start) {
309         final int maybeGet = sb[start] |
310                 sb[start + 1] << 8 |
311                 sb[start + 2] << 16;
312         return maybeGet == GET_AS_INT;
313     }
314 
315     private static boolean isPostMethod(final byte[] sb, int start) {
316         final int maybePost = sb[start] |
317                 sb[start + 1] << 8 |
318                 sb[start + 2] << 16 |
319                 sb[start + 3] << 24;
320         return maybePost == POST_AS_INT;
321     }
322 
323     @Override
324     protected String splitFirstWordInitialLine(final byte[] sb, final int start, final int length) {
325         if (length == 3) {
326             if (isGetMethod(sb, start)) {
327                 return HttpMethod.GET.name();
328             }
329         } else if (length == 4) {
330             if (isPostMethod(sb, start)) {
331                 return HttpMethod.POST.name();
332             }
333         }
334         return super.splitFirstWordInitialLine(sb, start, length);
335     }
336 
337     @Override
338     protected String splitThirdWordInitialLine(final byte[] sb, final int start, final int length) {
339         if (length == 8) {
340             final long maybeHttp1_x = sb[start] |
341                     sb[start + 1] << 8 |
342                     sb[start + 2] << 16 |
343                     sb[start + 3] << 24 |
344                     (long) sb[start + 4] << 32 |
345                     (long) sb[start + 5] << 40 |
346                     (long) sb[start + 6] << 48 |
347                     (long) sb[start + 7] << 56;
348             if (maybeHttp1_x == HTTP_1_1_AS_LONG) {
349                 return HttpVersion.HTTP_1_1_STRING;
350             } else if (maybeHttp1_x == HTTP_1_0_AS_LONG) {
351                 return HttpVersion.HTTP_1_0_STRING;
352             }
353         }
354         return super.splitThirdWordInitialLine(sb, start, length);
355     }
356 
357     @Override
358     protected HttpMessage createInvalidMessage() {
359         return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request",
360                 Unpooled.buffer(0), headersFactory, trailersFactory);
361     }
362 
363     @Override
364     protected boolean isDecodingRequest() {
365         return true;
366     }
367 
368     @Override
369     protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
370         // fast-path to save expensive O(n) checks; users can override createMessage
371         // and extends DefaultHttpRequest making implementing HttpResponse:
372         // this is why we cannot use instanceof DefaultHttpRequest here :(
373         if (msg.getClass() == DefaultHttpRequest.class) {
374             return false;
375         }
376         return super.isContentAlwaysEmpty(msg);
377     }
378 }