1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.server.protocol.http;
20
21 import java.io.UnsupportedEncodingException;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.Field;
24 import java.nio.ByteBuffer;
25 import java.nio.charset.StandardCharsets;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import io.undertow.UndertowMessages;
30 import io.undertow.UndertowOptions;
31 import io.undertow.annotationprocessor.HttpParserConfig;
32 import io.undertow.server.HttpServerExchange;
33 import io.undertow.util.Headers;
34 import io.undertow.util.HttpString;
35 import io.undertow.util.Methods;
36 import io.undertow.util.Protocols;
37 import io.undertow.util.URLUtils;
38 import io.undertow.util.BadRequestException;
39 import org.xnio.OptionMap;
40
41 import static io.undertow.util.Headers.ACCEPT_CHARSET_STRING;
42 import static io.undertow.util.Headers.ACCEPT_ENCODING_STRING;
43 import static io.undertow.util.Headers.ACCEPT_LANGUAGE_STRING;
44 import static io.undertow.util.Headers.ACCEPT_RANGES_STRING;
45 import static io.undertow.util.Headers.ACCEPT_STRING;
46 import static io.undertow.util.Headers.AUTHORIZATION_STRING;
47 import static io.undertow.util.Headers.CACHE_CONTROL_STRING;
48 import static io.undertow.util.Headers.CONNECTION_STRING;
49 import static io.undertow.util.Headers.CONTENT_LENGTH_STRING;
50 import static io.undertow.util.Headers.CONTENT_TYPE_STRING;
51 import static io.undertow.util.Headers.COOKIE_STRING;
52 import static io.undertow.util.Headers.EXPECT_STRING;
53 import static io.undertow.util.Headers.FROM_STRING;
54 import static io.undertow.util.Headers.HOST_STRING;
55 import static io.undertow.util.Headers.IF_MATCH_STRING;
56 import static io.undertow.util.Headers.IF_MODIFIED_SINCE_STRING;
57 import static io.undertow.util.Headers.IF_NONE_MATCH_STRING;
58 import static io.undertow.util.Headers.IF_RANGE_STRING;
59 import static io.undertow.util.Headers.IF_UNMODIFIED_SINCE_STRING;
60 import static io.undertow.util.Headers.MAX_FORWARDS_STRING;
61 import static io.undertow.util.Headers.ORIGIN_STRING;
62 import static io.undertow.util.Headers.PRAGMA_STRING;
63 import static io.undertow.util.Headers.PROXY_AUTHORIZATION_STRING;
64 import static io.undertow.util.Headers.RANGE_STRING;
65 import static io.undertow.util.Headers.REFERER_STRING;
66 import static io.undertow.util.Headers.REFRESH_STRING;
67 import static io.undertow.util.Headers.SEC_WEB_SOCKET_KEY_STRING;
68 import static io.undertow.util.Headers.SEC_WEB_SOCKET_VERSION_STRING;
69 import static io.undertow.util.Headers.SERVER_STRING;
70 import static io.undertow.util.Headers.SSL_CIPHER_STRING;
71 import static io.undertow.util.Headers.SSL_CIPHER_USEKEYSIZE_STRING;
72 import static io.undertow.util.Headers.SSL_CLIENT_CERT_STRING;
73 import static io.undertow.util.Headers.SSL_SESSION_ID_STRING;
74 import static io.undertow.util.Headers.STRICT_TRANSPORT_SECURITY_STRING;
75 import static io.undertow.util.Headers.TRAILER_STRING;
76 import static io.undertow.util.Headers.TRANSFER_ENCODING_STRING;
77 import static io.undertow.util.Headers.UPGRADE_STRING;
78 import static io.undertow.util.Headers.USER_AGENT_STRING;
79 import static io.undertow.util.Headers.VIA_STRING;
80 import static io.undertow.util.Headers.WARNING_STRING;
81 import static io.undertow.util.Methods.CONNECT_STRING;
82 import static io.undertow.util.Methods.DELETE_STRING;
83 import static io.undertow.util.Methods.GET_STRING;
84 import static io.undertow.util.Methods.HEAD_STRING;
85 import static io.undertow.util.Methods.OPTIONS_STRING;
86 import static io.undertow.util.Methods.POST_STRING;
87 import static io.undertow.util.Methods.PUT_STRING;
88 import static io.undertow.util.Methods.TRACE_STRING;
89 import static io.undertow.util.Protocols.HTTP_0_9_STRING;
90 import static io.undertow.util.Protocols.HTTP_1_0_STRING;
91 import static io.undertow.util.Protocols.HTTP_1_1_STRING;
92 import static io.undertow.util.Protocols.HTTP_2_0_STRING;
93
94 /**
95  * The basic HTTP parser. The actual parser is a sub class of this class that is generated as part of
96  * the build process by the {@link io.undertow.annotationprocessor.AbstractParserGenerator} annotation processor.
97  * <p>
98  * The actual processor is a state machine, that means that for common header, method, protocol values
99  * it will return an interned string, rather than creating a new string for each one.
100  * <p>
101  *
102  * @author Stuart Douglas
103  */

104 @HttpParserConfig(methods = {
105         OPTIONS_STRING,
106         GET_STRING,
107         HEAD_STRING,
108         POST_STRING,
109         PUT_STRING,
110         DELETE_STRING,
111         TRACE_STRING,
112         CONNECT_STRING},
113         protocols = {
114                 HTTP_0_9_STRING, HTTP_1_0_STRING, HTTP_1_1_STRING, HTTP_2_0_STRING
115         },
116         headers = {
117                 ACCEPT_STRING,
118                 ACCEPT_CHARSET_STRING,
119                 ACCEPT_ENCODING_STRING,
120                 ACCEPT_LANGUAGE_STRING,
121                 ACCEPT_RANGES_STRING,
122                 AUTHORIZATION_STRING,
123                 CACHE_CONTROL_STRING,
124                 COOKIE_STRING,
125                 CONNECTION_STRING,
126                 CONTENT_LENGTH_STRING,
127                 CONTENT_TYPE_STRING,
128                 EXPECT_STRING,
129                 FROM_STRING,
130                 HOST_STRING,
131                 IF_MATCH_STRING,
132                 IF_MODIFIED_SINCE_STRING,
133                 IF_NONE_MATCH_STRING,
134                 IF_RANGE_STRING,
135                 IF_UNMODIFIED_SINCE_STRING,
136                 MAX_FORWARDS_STRING,
137                 ORIGIN_STRING,
138                 PRAGMA_STRING,
139                 PROXY_AUTHORIZATION_STRING,
140                 RANGE_STRING,
141                 REFERER_STRING,
142                 REFRESH_STRING,
143                 SEC_WEB_SOCKET_KEY_STRING,
144                 SEC_WEB_SOCKET_VERSION_STRING,
145                 SERVER_STRING,
146                 SSL_CLIENT_CERT_STRING,
147                 SSL_CIPHER_STRING,
148                 SSL_SESSION_ID_STRING,
149                 SSL_CIPHER_USEKEYSIZE_STRING,
150                 STRICT_TRANSPORT_SECURITY_STRING,
151                 TRAILER_STRING,
152                 TRANSFER_ENCODING_STRING,
153                 UPGRADE_STRING,
154                 USER_AGENT_STRING,
155                 VIA_STRING,
156                 WARNING_STRING
157         })
158 public abstract class HttpRequestParser {
159
160     private static final byte[] HTTP;
161     public static final int HTTP_LENGTH;
162
163     private final int maxParameters;
164     private final int maxHeaders;
165     private final boolean allowEncodedSlash;
166     private final boolean decode;
167     private final String charset;
168     private final int maxCachedHeaderSize;
169     private final boolean allowUnescapedCharactersInUrl;
170
171     private static final boolean[] ALLOWED_TARGET_CHARACTER = new boolean[256];
172
173     static {
174         try {
175             HTTP = "HTTP/1.".getBytes("ASCII");
176             HTTP_LENGTH = HTTP.length;
177         } catch (UnsupportedEncodingException e) {
178             throw new RuntimeException(e);
179         }
180         for(int i = 0; i < 256; ++i) {
181             if(i < 32 || i > 126) {
182                 ALLOWED_TARGET_CHARACTER[i] = false;
183             } else {
184                 switch ((char)i) {
185                     case '\"':
186                     case '#':
187                     case '<':
188                     case '>':
189                     case '\\':
190                     case '^':
191                     case '`':
192                     case '{':
193                     case '|':
194                     case '}':
195                         ALLOWED_TARGET_CHARACTER[i] = false;
196                         break;
197                     default:
198                         ALLOWED_TARGET_CHARACTER[i] = true;
199                 }
200             }
201         }
202     }
203
204     public HttpRequestParser(OptionMap options) {
205         maxParameters = options.get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS);
206         maxHeaders = options.get(UndertowOptions.MAX_HEADERS, UndertowOptions.DEFAULT_MAX_HEADERS);
207         allowEncodedSlash = options.get(UndertowOptions.ALLOW_ENCODED_SLASH, false);
208         decode = options.get(UndertowOptions.DECODE_URL, true);
209         charset = options.get(UndertowOptions.URL_CHARSET, StandardCharsets.UTF_8.name());
210         maxCachedHeaderSize = options.get(UndertowOptions.MAX_CACHED_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_CACHED_HEADER_SIZE);
211         this.allowUnescapedCharactersInUrl = options.get(UndertowOptions.ALLOW_UNESCAPED_CHARACTERS_IN_URL, false);
212     }
213
214     public static final HttpRequestParser instance(final OptionMap options) {
215         try {
216             final Class<?> cls = Class.forName(HttpRequestParser.class.getName() + "$$generated"false, HttpRequestParser.class.getClassLoader());
217
218             Constructor<?> ctor = cls.getConstructor(OptionMap.class);
219             return (HttpRequestParser) ctor.newInstance(options);
220         } catch (Exception e) {
221             throw new RuntimeException(e);
222         }
223     }
224
225
226     public void handle(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException {
227         if (currentState.state == ParseState.VERB) {
228             //fast path, we assume that it will parse fully so we avoid all the if statements
229
230             //fast path HTTP GET requests, basically just assume all requests are get
231             //and fall out to the state machine if it is not
232             final int position = buffer.position();
233             if (buffer.remaining() > 3
234                     && buffer.get(position) == 'G'
235                     && buffer.get(position + 1) == 'E'
236                     && buffer.get(position + 2) == 'T'
237                     && buffer.get(position + 3) == ' ') {
238                 buffer.position(position + 4);
239                 builder.setRequestMethod(Methods.GET);
240                 currentState.state = ParseState.PATH;
241             } else {
242                 try {
243                     handleHttpVerb(buffer, currentState, builder);
244                 } catch (IllegalArgumentException e) {
245                     throw new BadRequestException(e);
246                 }
247             }
248             handlePath(buffer, currentState, builder);
249             boolean failed = false;
250             if (buffer.remaining() > HTTP_LENGTH + 3) {
251                 int pos = buffer.position();
252                 for (int i = 0; i < HTTP_LENGTH; ++i) {
253                     if (HTTP[i] != buffer.get(pos + i)) {
254                         failed = true;
255                         break;
256                     }
257                 }
258                 if (!failed) {
259                     final byte b = buffer.get(pos + HTTP_LENGTH);
260                     final byte b2 = buffer.get(pos + HTTP_LENGTH + 1);
261                     final byte b3 = buffer.get(pos + HTTP_LENGTH + 2);
262                     if (b2 == '\r' && b3 == '\n') {
263                         if (b == '1') {
264                             builder.setProtocol(Protocols.HTTP_1_1);
265                             buffer.position(pos + HTTP_LENGTH + 3);
266                             currentState.state = ParseState.HEADER;
267                         } else if (b == '0') {
268                             builder.setProtocol(Protocols.HTTP_1_0);
269                             buffer.position(pos + HTTP_LENGTH + 3);
270                             currentState.state = ParseState.HEADER;
271                         } else {
272                             failed = true;
273                         }
274                     } else {
275                         failed = true;
276                     }
277                 }
278             } else {
279                 failed = true;
280             }
281             if (failed) {
282                 handleHttpVersion(buffer, currentState, builder);
283                 handleAfterVersion(buffer, currentState);
284             }
285
286             while (currentState.state != ParseState.PARSE_COMPLETE && buffer.hasRemaining()) {
287                 handleHeader(buffer, currentState, builder);
288                 if (currentState.state == ParseState.HEADER_VALUE) {
289                     handleHeaderValue(buffer, currentState, builder);
290                 }
291             }
292             return;
293         }
294         handleStateful(buffer, currentState, builder);
295     }
296
297     private void handleStateful(ByteBuffer buffer, ParseState currentState, HttpServerExchange builder) throws BadRequestException {
298         if (currentState.state == ParseState.PATH) {
299             handlePath(buffer, currentState, builder);
300             if (!buffer.hasRemaining()) {
301                 return;
302             }
303         }
304
305         if (currentState.state == ParseState.QUERY_PARAMETERS) {
306             handleQueryParameters(buffer, currentState, builder);
307             if (!buffer.hasRemaining()) {
308                 return;
309             }
310         }
311
312         if (currentState.state == ParseState.PATH_PARAMETERS) {
313             handlePathParameters(buffer, currentState, builder);
314             if (!buffer.hasRemaining()) {
315                 return;
316             }
317             // we could go back to PATH after PATH_PARAMETERS
318             if (currentState.state == ParseState.PATH) {
319                 handlePath(buffer, currentState, builder);
320                 if (!buffer.hasRemaining()) {
321                     return;
322                 }
323             }
324         }
325
326         if (currentState.state == ParseState.VERSION) {
327             handleHttpVersion(buffer, currentState, builder);
328             if (!buffer.hasRemaining()) {
329                 return;
330             }
331         }
332         if (currentState.state == ParseState.AFTER_VERSION) {
333             handleAfterVersion(buffer, currentState);
334             if (!buffer.hasRemaining()) {
335                 return;
336             }
337         }
338         while (currentState.state != ParseState.PARSE_COMPLETE) {
339             if (currentState.state == ParseState.HEADER) {
340                 handleHeader(buffer, currentState, builder);
341                 if (!buffer.hasRemaining()) {
342                     return;
343                 }
344             }
345             if (currentState.state == ParseState.HEADER_VALUE) {
346                 handleHeaderValue(buffer, currentState, builder);
347                 if (!buffer.hasRemaining()) {
348                     return;
349                 }
350             }
351         }
352     }
353
354
355     abstract void handleHttpVerb(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException;
356
357     abstract void handleHttpVersion(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException;
358
359     abstract void handleHeader(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException;
360
361     /**
362      * The parse states for parsing the path.
363      */

364     private static final int START = 0;
365     private static final int FIRST_COLON = 1;
366     private static final int FIRST_SLASH = 2;
367     private static final int SECOND_SLASH = 3;
368     private static final int IN_PATH = 4;
369     private static final int HOST_DONE = 5;
370
371     /**
372      * Parses a path value
373      *
374      * @param buffer   The buffer
375      * @param state    The current state
376      * @param exchange The exchange builder
377      * @return The number of bytes remaining
378      */

379     @SuppressWarnings("unused")
380     final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange exchange) throws BadRequestException {
381         StringBuilder stringBuilder = state.stringBuilder;
382         int parseState = state.parseState;
383         int canonicalPathStart = state.pos;
384         boolean urlDecodeRequired = state.urlDecodeRequired;
385
386         while (buffer.hasRemaining()) {
387             char next = (char) (buffer.get() & 0xFF);
388             if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) {
389                 throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next));
390             }
391             if (next == ' ' || next == '\t') {
392                 if (stringBuilder.length() != 0) {
393                     final String path = stringBuilder.toString();
394                     parsePathComplete(state, exchange, canonicalPathStart, parseState, urlDecodeRequired, path);
395                     exchange.setQueryString("");
396                     state.state = ParseState.VERSION;
397                     return;
398                 }
399             } else if (next == '\r' || next == '\n') {
400                 throw UndertowMessages.MESSAGES.failedToParsePath();
401             } else if (next == '?' && (parseState == START || parseState == HOST_DONE || parseState == IN_PATH)) {
402                 beginQueryParameters(buffer, state, exchange, stringBuilder, parseState, canonicalPathStart, urlDecodeRequired);
403                 return;
404             } else if (next == ';') {
405                 state.parseState = parseState;
406                 state.urlDecodeRequired = urlDecodeRequired;
407                 state.pos = canonicalPathStart;
408                 // store at canonical path the partial path parsed up until here
409                 state.canonicalPath.append(stringBuilder.substring(canonicalPathStart));
410                 // handle the path parameters
411                 handlePathParameters(buffer, state, exchange);
412                 // if state is PATH, it means that handlePathParameters found a / after parsing path parameters
413                 // so, we expect a next chunk of path in the next bytes, mark this with canonicalPathStart
414                 // and append the '/' read by handlePathParameters
415                 if(state.state == ParseState.PATH) {
416                     canonicalPathStart = stringBuilder.length();
417                     stringBuilder.append('/');
418                 } else {
419                     // path has been entirely parsed, just return
420                     return;
421                 }
422             } else {
423
424                 if (decode && (next == '%' || next > 127)) {
425                     urlDecodeRequired = true;
426                 } else if (next == ':' && parseState == START) {
427                     parseState = FIRST_COLON;
428                 } else if (next == '/' && parseState == FIRST_COLON) {
429                     parseState = FIRST_SLASH;
430                 } else if (next == '/' && parseState == FIRST_SLASH) {
431                     parseState = SECOND_SLASH;
432                 } else if (next == '/' && parseState == SECOND_SLASH) {
433                     parseState = HOST_DONE;
434                     canonicalPathStart = stringBuilder.length();
435                 } else if (parseState == FIRST_COLON || parseState == FIRST_SLASH) {
436                     parseState = IN_PATH;
437                 } else if (next == '/' && parseState != HOST_DONE) {
438                     parseState = IN_PATH;
439                 }
440                 stringBuilder.append(next);
441             }
442
443         }
444         state.parseState = parseState;
445         state.pos = canonicalPathStart;
446         state.urlDecodeRequired = urlDecodeRequired;
447     }
448
449     private void parsePathComplete(ParseState state, HttpServerExchange exchange, int canonicalPathStart, int parseState, boolean urlDecodeRequired, String path) {
450         if (parseState == SECOND_SLASH) {
451             exchange.setRequestPath("/");
452             exchange.setRelativePath("/");
453             exchange.setRequestURI(path, true);
454         } else if (parseState < HOST_DONE && state.canonicalPath.length() == 0) {
455             String decodedPath = decode(path, urlDecodeRequired, state, allowEncodedSlash, false);
456             exchange.setRequestPath(decodedPath);
457             exchange.setRelativePath(decodedPath);
458             exchange.setRequestURI(path, false);
459         } else {
460             handleFullUrl(state, exchange, canonicalPathStart, urlDecodeRequired, path, parseState);
461         }
462         state.stringBuilder.setLength(0);
463         state.canonicalPath.setLength(0);
464         state.parseState = 0;
465         state.pos = 0;
466         state.urlDecodeRequired = false;
467     }
468
469     private void beginQueryParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange, StringBuilder stringBuilder, int parseState, int canonicalPathStart, boolean urlDecodeRequired) throws BadRequestException {
470         final String path = stringBuilder.toString();
471         parsePathComplete(state, exchange, canonicalPathStart, parseState, urlDecodeRequired, path);
472         state.state = ParseState.QUERY_PARAMETERS;
473         handleQueryParameters(buffer, state, exchange);
474     }
475
476     private void handleFullUrl(ParseState state, HttpServerExchange exchange, int canonicalPathStart, boolean urlDecodeRequired, String path, int parseState) {
477         state.canonicalPath.append(path.substring(canonicalPathStart));
478         String thePath = decode(state.canonicalPath.toString(), urlDecodeRequired, state, allowEncodedSlash, false);
479         exchange.setRequestPath(thePath);
480         exchange.setRelativePath(thePath);
481         exchange.setRequestURI(path, parseState == HOST_DONE);
482     }
483
484
485     /**
486      * Parses query string parameters
487      *
488      * @param buffer   The buffer
489      * @param state    The current state
490      * @param exchange The exchange builder
491      * @return The number of bytes remaining
492      */

493     @SuppressWarnings("unused")
494     final void handleQueryParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange) throws BadRequestException {
495         StringBuilder stringBuilder = state.stringBuilder;
496         int queryParamPos = state.pos;
497         int mapCount = state.mapCount;
498         boolean urlDecodeRequired = state.urlDecodeRequired;
499         String nextQueryParam = state.nextQueryParam;
500
501         //so this is a bit funky, because it not only deals with parsing, but
502         //also deals with URL decoding the query parameters as well, while also
503         //maintaining a non-decoded version to use as the query string
504         //In most cases these string will be the same, and as we do not want to
505         //build up two separate strings we don't use encodedStringBuilder unless
506         //we encounter an encoded character
507
508         while (buffer.hasRemaining()) {
509             char next = (char) (buffer.get() & 0xFF);
510             if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) {
511                 throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next));
512             }
513             if (next == ' ' || next == '\t') {
514                 final String queryString = stringBuilder.toString();
515                 exchange.setQueryString(queryString);
516                 if (nextQueryParam == null) {
517                     if (queryParamPos != stringBuilder.length()) {
518                         exchange.addQueryParam(decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, truetrue), "");
519                     }
520                 } else {
521                     exchange.addQueryParam(nextQueryParam, decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, truetrue));
522                 }
523                 state.state = ParseState.VERSION;
524                 state.stringBuilder.setLength(0);
525                 state.pos = 0;
526                 state.nextQueryParam = null;
527                 state.urlDecodeRequired = false;
528                 state.mapCount = 0;
529                 return;
530             } else if (next == '\r' || next == '\n') {
531                 throw UndertowMessages.MESSAGES.failedToParsePath();
532             } else {
533                 if (decode && (next == '+' || next == '%' || next > 127)) { //+ is only a whitespace substitute in the query part of the URL
534                     urlDecodeRequired = true;
535                 } else if (next == '=' && nextQueryParam == null) {
536                     nextQueryParam = decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, truetrue);
537                     urlDecodeRequired = false;
538                     queryParamPos = stringBuilder.length() + 1;
539                 } else if (next == '&' && nextQueryParam == null) {
540                     if (++mapCount >= maxParameters) {
541                         throw UndertowMessages.MESSAGES.tooManyQueryParameters(maxParameters);
542                     }
543                     if (queryParamPos != stringBuilder.length()) {
544                         exchange.addQueryParam(decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, truetrue), "");
545                     }
546                     urlDecodeRequired = false;
547                     queryParamPos = stringBuilder.length() + 1;
548                 } else if (next == '&') {
549                     if (++mapCount >= maxParameters) {
550                         throw UndertowMessages.MESSAGES.tooManyQueryParameters(maxParameters);
551                     }
552                     exchange.addQueryParam(nextQueryParam, decode(stringBuilder.substring(queryParamPos), urlDecodeRequired, state, truetrue));
553                     urlDecodeRequired = false;
554                     queryParamPos = stringBuilder.length() + 1;
555                     nextQueryParam = null;
556                 }
557                 stringBuilder.append(next);
558
559             }
560
561         }
562         state.pos = queryParamPos;
563         state.nextQueryParam = nextQueryParam;
564         state.urlDecodeRequired = urlDecodeRequired;
565         state.mapCount = mapCount;
566     }
567
568     private String decode(final String value, boolean urlDecodeRequired, ParseState state, final boolean allowEncodedSlash, final boolean formEncoded) {
569         if (urlDecodeRequired) {
570             return URLUtils.decode(value, charset, allowEncodedSlash, formEncoded, state.decodeBuffer);
571         } else {
572             return value;
573         }
574     }
575
576     /**
577      * This method should only ever be called if the path contains am non-decoded semi colon character ';'
578      */

579     final void handlePathParameters(ByteBuffer buffer, ParseState state, HttpServerExchange exchange) throws BadRequestException {
580
581         state.state = ParseState.PATH_PARAMETERS;
582         boolean urlDecodeRequired = state.urlDecodeRequired;
583         String param = state.nextQueryParam;
584         final StringBuilder stringBuilder = state.stringBuilder;
585         stringBuilder.append(";");
586         int pos = stringBuilder.length();
587
588         //so this is a bit funky, because it not only deals with parsing, but
589         //also deals with URL decoding the query parameters as well, while also
590         //maintaining a non-decoded version to use as the query string
591         //In most cases these string will be the same, and as we do not want to
592         //build up two separate strings we don't use encodedStringBuilder unless
593         //we encounter an encoded character
594
595         while (buffer.hasRemaining()) {
596             char next = (char) (buffer.get() & 0xFF);
597             if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) {
598                 throw new BadRequestException(UndertowMessages.MESSAGES.invalidCharacterInRequestTarget(next));
599             }
600             // end of HTTP URI
601             if (next == ' ' || next == '\t' || next == '?') {
602                 handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state);
603                 final String path = stringBuilder.toString();
604                 // the canonicalPathStart should be the current length to not add anything to it
605                 parsePathComplete(state, exchange, path.length(), state.parseState, urlDecodeRequired, path);
606                 state.state = ParseState.VERSION;
607                 state.nextQueryParam = null;
608                 if (next == '?') {
609                     state.state = ParseState.QUERY_PARAMETERS;
610                     handleQueryParameters(buffer, state, exchange);
611                 } else
612                     exchange.setQueryString("");
613                 return;
614             } else if (next == '\r' || next == '\n') {
615                 throw UndertowMessages.MESSAGES.failedToParsePath();
616             } else if (next == '/') {
617                 handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state);
618                 state.pos = stringBuilder.length();
619                 state.state = ParseState.PATH;
620                 state.nextQueryParam = null;
621                 return;
622             } else {
623                 if (decode && (next == '+' || next == '%' || next > 127)) {
624                     urlDecodeRequired = true;
625                 }
626                 if (next == '=' && param == null) {
627                     param = decode(stringBuilder.substring(pos), urlDecodeRequired, state, truetrue);
628                     urlDecodeRequired = false;
629                     pos = stringBuilder.length() + 1;
630                 } else if (next == ';') {
631                     handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state);
632                     param = null;
633                     pos = stringBuilder.length() + 1;
634                 } else if (next == ',') {
635                     if(param == null) {
636                         throw UndertowMessages.MESSAGES.failedToParsePath();
637                     } else {
638                         handleParsedParam(param, stringBuilder.substring(pos), exchange, urlDecodeRequired, state);
639                         pos = stringBuilder.length() + 1;
640                     }
641                 }
642                 stringBuilder.append(next);
643             }
644         }
645
646         state.urlDecodeRequired = urlDecodeRequired;
647         state.pos = pos;
648         state.urlDecodeRequired = urlDecodeRequired;
649         state.nextQueryParam = param;
650     }
651
652     private void handleParsedParam(String previouslyParsedParam, String parsedParam, HttpServerExchange exchange, boolean urlDecodeRequired, ParseState state) throws BadRequestException {
653         if (previouslyParsedParam == null) {
654             exchange.addPathParam(decode(parsedParam, urlDecodeRequired, state, truetrue), "");
655         } else {  // path param already parsed so parse and add the value
656             exchange.addPathParam(previouslyParsedParam, decode(parsedParam, urlDecodeRequired, state, truetrue));
657         }
658     }
659
660     /**
661      * The parse states for parsing heading values
662      */

663     private static final int NORMAL = 0;
664     private static final int WHITESPACE = 1;
665     private static final int BEGIN_LINE_END = 2;
666     private static final int LINE_END = 3;
667     private static final int AWAIT_DATA_END = 4;
668
669     /**
670      * Parses a header value. This is called from the generated bytecode.
671      *
672      * @param buffer  The buffer
673      * @param state   The current state
674      * @param builder The exchange builder
675      * @return The number of bytes remaining
676      */

677     @SuppressWarnings("unused")
678     final void handleHeaderValue(ByteBuffer buffer, ParseState state, HttpServerExchange builder) throws BadRequestException {
679         HttpString headerName = state.nextHeader;
680         StringBuilder stringBuilder = state.stringBuilder;
681         CacheMap<HttpString, String> headerValuesCache = state.headerValuesCache;
682         if (headerName != null && stringBuilder.length() == 0 && headerValuesCache != null) {
683             String existing = headerValuesCache.get(headerName);
684             if (existing != null) {
685                 if (handleCachedHeader(existing, buffer, state, builder)) {
686                     return;
687                 }
688             }
689         }
690
691         handleHeaderValueCacheMiss(buffer, state, builder, headerName, headerValuesCache, stringBuilder);
692     }
693
694     private void handleHeaderValueCacheMiss(ByteBuffer buffer, ParseState state, HttpServerExchange builder, HttpString headerName, CacheMap<HttpString, String> headerValuesCache, StringBuilder stringBuilder) throws BadRequestException {
695
696         int parseState = state.parseState;
697         while (buffer.hasRemaining() && parseState == NORMAL) {
698             final byte next = buffer.get();
699             if (next == '\r') {
700                 parseState = BEGIN_LINE_END;
701             } else if (next == '\n') {
702                 parseState = LINE_END;
703             } else if (next == ' ' || next == '\t') {
704                 parseState = WHITESPACE;
705             } else {
706                 stringBuilder.append((char) (next & 0xFF));
707             }
708         }
709
710         while (buffer.hasRemaining()) {
711             final byte next = buffer.get();
712             switch (parseState) {
713                 case NORMAL: {
714                     if (next == '\r') {
715                         parseState = BEGIN_LINE_END;
716                     } else if (next == '\n') {
717                         parseState = LINE_END;
718                     } else if (next == ' ' || next == '\t') {
719                         parseState = WHITESPACE;
720                     } else {
721                         stringBuilder.append((char) (next & 0xFF));
722                     }
723                     break;
724                 }
725                 case WHITESPACE: {
726                     if (next == '\r') {
727                         parseState = BEGIN_LINE_END;
728                     } else if (next == '\n') {
729                         parseState = LINE_END;
730                     } else if (next == ' ' || next == '\t') {
731                     } else {
732                         if (stringBuilder.length() > 0) {
733                             stringBuilder.append(' ');
734                         }
735                         stringBuilder.append((char) (next & 0xFF));
736                         parseState = NORMAL;
737                     }
738                     break;
739                 }
740                 case LINE_END:
741                 case BEGIN_LINE_END: {
742                     if (next == '\n' && parseState == BEGIN_LINE_END) {
743                         parseState = LINE_END;
744                     } else if (next == '\t' ||
745                             next == ' ') {
746                         //this is a continuation
747                         parseState = WHITESPACE;
748                     } else {
749                         //we have a header
750                         String headerValue = stringBuilder.toString();
751
752
753                         if (++state.mapCount > maxHeaders) {
754                             throw new BadRequestException(UndertowMessages.MESSAGES.tooManyHeaders(maxHeaders));
755                         }
756                         //TODO: we need to decode this according to RFC-2047 if we have seen a =? symbol
757                         builder.getRequestHeaders().add(headerName, headerValue);
758                         if(headerValuesCache != null && headerName.length() + headerValue.length() < maxCachedHeaderSize) {
759                             headerValuesCache.put(headerName, headerValue);
760                         }
761
762                         state.nextHeader = null;
763
764                         state.leftOver = next;
765                         state.stringBuilder.setLength(0);
766                         if (next == '\r') {
767                             parseState = AWAIT_DATA_END;
768                         } else if (next == '\n') {
769                             state.state = ParseState.PARSE_COMPLETE;
770                             return;
771                         } else {
772                             state.state = ParseState.HEADER;
773                             state.parseState = 0;
774                             return;
775                         }
776                     }
777                     break;
778                 }
779                 case AWAIT_DATA_END: {
780                     state.state = ParseState.PARSE_COMPLETE;
781                     return;
782                 }
783             }
784         }
785         //we only write to the state if we did not finish parsing
786         state.parseState = parseState;
787     }
788
789     protected boolean handleCachedHeader(String existing, ByteBuffer buffer, ParseState state, HttpServerExchange builder) throws BadRequestException {
790         int pos = buffer.position();
791         while (pos < buffer.limit() && buffer.get(pos) == ' ') {
792             pos++;
793         }
794         if (existing.length() + 3 + pos > buffer.limit()) {
795             return false;
796         }
797         int i = 0;
798         while (i < existing.length()) {
799             byte b = buffer.get(pos + i);
800             if (b != existing.charAt(i)) {
801                 return false;
802             }
803             ++i;
804         }
805         if (buffer.get(pos + i++) != '\r') {
806             return false;
807         }
808         if (buffer.get(pos + i++) != '\n') {
809             return false;
810         }
811         int next = buffer.get(pos + i);
812         if (next == '\t' || next == ' ') {
813             //continuation
814             return false;
815         }
816         buffer.position(pos + i);
817         if (++state.mapCount > maxHeaders) {
818             throw new BadRequestException(UndertowMessages.MESSAGES.tooManyHeaders(maxHeaders));
819         }
820         //TODO: we need to decode this according to RFC-2047 if we have seen a =? symbol
821         builder.getRequestHeaders().add(state.nextHeader, existing);
822
823         state.nextHeader = null;
824
825         state.state = ParseState.HEADER;
826         state.parseState = 0;
827         return true;
828     }
829
830     protected void handleAfterVersion(ByteBuffer buffer, ParseState state) throws BadRequestException {
831         boolean newLine = state.leftOver == '\n';
832         while (buffer.hasRemaining()) {
833             final byte next = buffer.get();
834             if (newLine) {
835                 if (next == '\n') {
836                     state.state = ParseState.PARSE_COMPLETE;
837                     return;
838                 } else {
839                     state.state = ParseState.HEADER;
840                     state.leftOver = next;
841                     return;
842                 }
843             } else {
844                 if (next == '\n') {
845                     newLine = true;
846                 } else if (next != '\r' && next != ' ' && next != '\t') {
847                     state.state = ParseState.HEADER;
848                     state.leftOver = next;
849                     return;
850                 } else {
851                     throw UndertowMessages.MESSAGES.badRequest();
852                 }
853             }
854         }
855         if (newLine) {
856             state.leftOver = '\n';
857         }
858     }
859
860     /**
861      * This is a bit of hack to enable the parser to get access to the HttpString's that are sorted
862      * in the static fields of the relevant classes. This means that in most cases a HttpString comparison
863      * will take the fast path == route, as they will be the same object
864      *
865      * @return
866      */

867     @SuppressWarnings("unused")
868     protected static Map<String, HttpString> httpStrings() {
869         final Map<String, HttpString> results = new HashMap<>();
870         final Class[] classs = {Headers.class, Methods.class, Protocols.class};
871
872         for (Class<?> c : classs) {
873             for (Field field : c.getDeclaredFields()) {
874                 if (field.getType().equals(HttpString.class)) {
875                     HttpString result = null;
876                     try {
877                         result = (HttpString) field.get(null);
878                         results.put(result.toString(), result);
879                     } catch (IllegalAccessException e) {
880                         throw new RuntimeException(e);
881                     }
882                 }
883             }
884         }
885         return results;
886
887     }
888
889 }
890