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                 HttpVersion.valueOf(initialLine[2]),
201                 HttpMethod.valueOf(initialLine[0]), initialLine[1], headersFactory);
202     }
203 
204     @Override
205     protected AsciiString splitHeaderName(final byte[] sb, final int start, final int length) {
206         final byte firstChar = sb[start];
207         if (firstChar == 'H') {
208             if (length == 4 && isHost(sb, start)) {
209                 return Host;
210             }
211         } else if (firstChar == 'A') {
212             if (length == 6 && isAccept(sb, start)) {
213                 return Accept;
214             }
215         } else if (firstChar == 'C') {
216             if (length == 10) {
217                 if (isConnection(sb, start)) {
218                     return Connection;
219                 }
220             } else if (length == 12) {
221                 if (isContentType(sb, start)) {
222                     return ContentType;
223                 }
224             } else if (length == 14) {
225                 if (isContentLength(sb, start)) {
226                     return ContentLength;
227                 }
228             }
229         }
230         return super.splitHeaderName(sb, start, length);
231     }
232 
233     private static boolean isAccept(byte[] sb, int start) {
234         final long maybeAccept = sb[start] |
235                 sb[start + 1] << 8 |
236                 sb[start + 2] << 16 |
237                 sb[start + 3] << 24 |
238                 (long) sb[start + 4] << 32 |
239                 (long) sb[start + 5] << 40;
240         return maybeAccept == ACCEPT_AS_LONG;
241     }
242 
243     private static boolean isHost(byte[] sb, int start) {
244         final int maybeHost = sb[start] |
245                 sb[start + 1] << 8 |
246                 sb[start + 2] << 16 |
247                 sb[start + 3] << 24;
248         return maybeHost == HOST_AS_INT;
249     }
250 
251     private static boolean isConnection(byte[] sb, int start) {
252         final long maybeConnecti = sb[start] |
253                 sb[start + 1] << 8 |
254                 sb[start + 2] << 16 |
255                 sb[start + 3] << 24 |
256                 (long) sb[start + 4] << 32 |
257                 (long) sb[start + 5] << 40 |
258                 (long) sb[start + 6] << 48 |
259                 (long) sb[start + 7] << 56;
260         if (maybeConnecti != CONNECTION_AS_LONG_0) {
261             return false;
262         }
263         final short maybeOn = (short) (sb[start + 8] | sb[start + 9] << 8);
264         return maybeOn == CONNECTION_AS_SHORT_1;
265     }
266 
267     private static boolean isContentType(byte[] sb, int start) {
268         final long maybeContent = sb[start] |
269                 sb[start + 1] << 8 |
270                 sb[start + 2] << 16 |
271                 sb[start + 3] << 24 |
272                 (long) sb[start + 4] << 32 |
273                 (long) sb[start + 5] << 40 |
274                 (long) sb[start + 6] << 48 |
275                 (long) sb[start + 7] << 56;
276         if (maybeContent != CONTENT_AS_LONG) {
277             return false;
278         }
279         final int maybeType = sb[start + 8] |
280                 sb[start + 9] << 8 |
281                 sb[start + 10] << 16 |
282                 sb[start + 11] << 24;
283         return maybeType == TYPE_AS_INT;
284     }
285 
286     private static boolean isContentLength(byte[] sb, int start) {
287         final long maybeContent = sb[start] |
288                 sb[start + 1] << 8 |
289                 sb[start + 2] << 16 |
290                 sb[start + 3] << 24 |
291                 (long) sb[start + 4] << 32 |
292                 (long) sb[start + 5] << 40 |
293                 (long) sb[start + 6] << 48 |
294                 (long) sb[start + 7] << 56;
295         if (maybeContent != CONTENT_AS_LONG) {
296             return false;
297         }
298         final long maybeLength = sb[start + 8] |
299                 sb[start + 9] << 8 |
300                 sb[start + 10] << 16 |
301                 sb[start + 11] << 24 |
302                 (long) sb[start + 12] << 32 |
303                 (long) sb[start + 13] << 40;
304         return maybeLength == LENGTH_AS_LONG;
305     }
306 
307     private static boolean isGetMethod(final byte[] sb, int start) {
308         final int maybeGet = sb[start] |
309                 sb[start + 1] << 8 |
310                 sb[start + 2] << 16;
311         return maybeGet == GET_AS_INT;
312     }
313 
314     private static boolean isPostMethod(final byte[] sb, int start) {
315         final int maybePost = sb[start] |
316                 sb[start + 1] << 8 |
317                 sb[start + 2] << 16 |
318                 sb[start + 3] << 24;
319         return maybePost == POST_AS_INT;
320     }
321 
322     @Override
323     protected String splitFirstWordInitialLine(final byte[] sb, final int start, final int length) {
324         if (length == 3) {
325             if (isGetMethod(sb, start)) {
326                 return HttpMethod.GET.name();
327             }
328         } else if (length == 4) {
329             if (isPostMethod(sb, start)) {
330                 return HttpMethod.POST.name();
331             }
332         }
333         return super.splitFirstWordInitialLine(sb, start, length);
334     }
335 
336     @Override
337     protected String splitThirdWordInitialLine(final byte[] sb, final int start, final int length) {
338         if (length == 8) {
339             final long maybeHttp1_x = sb[start] |
340                     sb[start + 1] << 8 |
341                     sb[start + 2] << 16 |
342                     sb[start + 3] << 24 |
343                     (long) sb[start + 4] << 32 |
344                     (long) sb[start + 5] << 40 |
345                     (long) sb[start + 6] << 48 |
346                     (long) sb[start + 7] << 56;
347             if (maybeHttp1_x == HTTP_1_1_AS_LONG) {
348                 return HttpVersion.HTTP_1_1_STRING;
349             } else if (maybeHttp1_x == HTTP_1_0_AS_LONG) {
350                 return HttpVersion.HTTP_1_0_STRING;
351             }
352         }
353         return super.splitThirdWordInitialLine(sb, start, length);
354     }
355 
356     @Override
357     protected HttpMessage createInvalidMessage() {
358         return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request",
359                 Unpooled.buffer(0), headersFactory, trailersFactory);
360     }
361 
362     @Override
363     protected boolean isDecodingRequest() {
364         return true;
365     }
366 
367     @Override
368     protected boolean isContentAlwaysEmpty(final HttpMessage msg) {
369         // fast-path to save expensive O(n) checks; users can override createMessage
370         // and extends DefaultHttpRequest making implementing HttpResponse:
371         // this is why we cannot use instanceof DefaultHttpRequest here :(
372         if (msg.getClass() == DefaultHttpRequest.class) {
373             return false;
374         }
375         return super.isContentAlwaysEmpty(msg);
376     }
377 }