1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.http;
20
21 import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN;
22 import static org.eclipse.jetty.http.HttpTokens.LINE_FEED;
23 import static org.eclipse.jetty.http.HttpTokens.SPACE;
24 import static org.eclipse.jetty.http.HttpTokens.TAB;
25
26 import java.nio.ByteBuffer;
27 import java.nio.charset.StandardCharsets;
28 import java.util.Arrays;
29 import java.util.EnumSet;
30
31 import org.eclipse.jetty.http.HttpTokens.EndOfContent;
32 import org.eclipse.jetty.util.ArrayTernaryTrie;
33 import org.eclipse.jetty.util.ArrayTrie;
34 import org.eclipse.jetty.util.BufferUtil;
35 import org.eclipse.jetty.util.Trie;
36 import org.eclipse.jetty.util.TypeUtil;
37 import org.eclipse.jetty.util.Utf8StringBuilder;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40
41
42 /* ------------------------------------------------------------ */
43 /** A Parser for 1.0 and 1.1 as defined by RFC7230
44 * <p>
45 * This parser parses HTTP client and server messages from buffers
46 * passed in the {@link #parseNext(ByteBuffer)} method. The parsed
47 * elements of the HTTP message are passed as event calls to the
48 * {@link HttpHandler} instance the parser is constructed with.
49 * If the passed handler is a {@link RequestHandler} then server side
50 * parsing is performed and if it is a {@link ResponseHandler}, then
51 * client side parsing is done.
52 * </p>
53 * <p>
54 * The contract of the {@link HttpHandler} API is that if a call returns
55 * true then the call to {@link #parseNext(ByteBuffer)} will return as
56 * soon as possible also with a true response. Typically this indicates
57 * that the parsing has reached a stage where the caller should process
58 * the events accumulated by the handler. It is the preferred calling
59 * style that handling such as calling a servlet to process a request,
60 * should be done after a true return from {@link #parseNext(ByteBuffer)}
61 * rather than from within the scope of a call like
62 * {@link RequestHandler#messageComplete()}
63 * </p>
64 * <p>
65 * For performance, the parse is heavily dependent on the
66 * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
67 * single pass for both the structure ( : and CRLF ) and semantic (which
68 * header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
69 * is used to lookup common combinations of headers and values
70 * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
71 * For headers who's value is not known statically (eg. Host, COOKIE) then a
72 * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
73 * is used to help the parsing of subsequent messages.
74 * </p>
75 * <p>
76 * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
77 * then the parser will strictly pass on the exact strings received for methods and header
78 * fields. Otherwise a fast case insensitive string lookup is used that may alter the
79 * case of the method and/or headers
80 * </p>
81 * <p>
82 * @see <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a>
83 */
84 public class HttpParser
85 {
86 public static final Logger LOG = Log.getLogger(HttpParser.class);
87 public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
88 public final static int INITIAL_URI_LENGTH=256;
89
90 /**
91 * Cache of common {@link HttpField}s including: <UL>
92 * <LI>Common static combinations such as:<UL>
93 * <li>Connection: close
94 * <li>Accept-Encoding: gzip
95 * <li>Content-Length: 0
96 * </ul>
97 * <li>Combinations of Content-Type header for common mime types by common charsets
98 * <li>Most common headers with null values so that a lookup will at least
99 * determine the header name even if the name:value combination is not cached
100 * </ul>
101 */
102 public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
103
104 // States
105 public enum State
106 {
107 START,
108 METHOD,
109 RESPONSE_VERSION,
110 SPACE1,
111 STATUS,
112 URI,
113 SPACE2,
114 REQUEST_VERSION,
115 REASON,
116 PROXY,
117 HEADER,
118 HEADER_IN_NAME,
119 HEADER_VALUE,
120 HEADER_IN_VALUE,
121 CONTENT,
122 EOF_CONTENT,
123 CHUNKED_CONTENT,
124 CHUNK_SIZE,
125 CHUNK_PARAMS,
126 CHUNK,
127 CHUNK_END,
128 END,
129 CLOSE, // The associated stream/endpoint should be closed
130 CLOSED // The associated stream/endpoint is at EOF
131 }
132
133 private final static EnumSet<State> __idleStates = EnumSet.of(State.START,State.END,State.CLOSE,State.CLOSED);
134 private final static EnumSet<State> __completeStates = EnumSet.of(State.END,State.CLOSE,State.CLOSED);
135
136 private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
137 private final HttpHandler _handler;
138 private final RequestHandler _requestHandler;
139 private final ResponseHandler _responseHandler;
140 private final int _maxHeaderBytes;
141 private final boolean _strict;
142 private HttpField _field;
143 private HttpHeader _header;
144 private String _headerString;
145 private HttpHeaderValue _value;
146 private String _valueString;
147 private int _responseStatus;
148 private int _headerBytes;
149 private boolean _host;
150
151 /* ------------------------------------------------------------------------------- */
152 private volatile State _state=State.START;
153 private volatile boolean _eof;
154 private HttpMethod _method;
155 private String _methodString;
156 private HttpVersion _version;
157 private Utf8StringBuilder _uri=new Utf8StringBuilder(INITIAL_URI_LENGTH); // Tune?
158 private EndOfContent _endOfContent;
159 private long _contentLength;
160 private long _contentPosition;
161 private int _chunkLength;
162 private int _chunkPosition;
163 private boolean _headResponse;
164 private boolean _cr;
165 private ByteBuffer _contentChunk;
166 private Trie<HttpField> _connectionFields;
167
168 private int _length;
169 private final StringBuilder _string=new StringBuilder();
170
171 static
172 {
173 CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
174 CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
175 CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
176 CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
177 CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
178 CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
179 CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
180 CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
181 CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
182 CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
183 CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
184 CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
185 CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
186 CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
187 CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
188 CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
189 CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
190 CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
191 CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
192 CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
193
194 // Add common Content types as fields
195 for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
196 {
197 HttpField field=new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type);
198 CACHE.put(field);
199
200 for (String charset : new String[]{"utf-8","iso-8859-1"})
201 {
202 CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
203 CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
204 CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset.toUpperCase()));
205 CACHE.put(new PreEncodedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset.toUpperCase()));
206 }
207 }
208
209 // Add headers with null values so HttpParser can avoid looking up name again for unknown values
210 for (HttpHeader h:HttpHeader.values())
211 if (!CACHE.put(new HttpField(h,(String)null)))
212 throw new IllegalStateException("CACHE FULL");
213 // Add some more common headers
214 CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
215 CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
216 CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
217 CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
218 CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
219 }
220
221 /* ------------------------------------------------------------------------------- */
222 public HttpParser(RequestHandler handler)
223 {
224 this(handler,-1,__STRICT);
225 }
226
227 /* ------------------------------------------------------------------------------- */
228 public HttpParser(ResponseHandler handler)
229 {
230 this(handler,-1,__STRICT);
231 }
232
233 /* ------------------------------------------------------------------------------- */
234 public HttpParser(RequestHandler handler,int maxHeaderBytes)
235 {
236 this(handler,maxHeaderBytes,__STRICT);
237 }
238
239 /* ------------------------------------------------------------------------------- */
240 public HttpParser(ResponseHandler handler,int maxHeaderBytes)
241 {
242 this(handler,maxHeaderBytes,__STRICT);
243 }
244
245 /* ------------------------------------------------------------------------------- */
246 public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict)
247 {
248 _handler=handler;
249 _requestHandler=handler;
250 _responseHandler=null;
251 _maxHeaderBytes=maxHeaderBytes;
252 _strict=strict;
253 }
254
255 /* ------------------------------------------------------------------------------- */
256 public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
257 {
258 _handler=handler;
259 _requestHandler=null;
260 _responseHandler=handler;
261 _maxHeaderBytes=maxHeaderBytes;
262 _strict=strict;
263 }
264
265 /* ------------------------------------------------------------------------------- */
266 public long getContentLength()
267 {
268 return _contentLength;
269 }
270
271 /* ------------------------------------------------------------ */
272 public long getContentRead()
273 {
274 return _contentPosition;
275 }
276
277 /* ------------------------------------------------------------ */
278 /** Set if a HEAD response is expected
279 * @param head true if head response is expected
280 */
281 public void setHeadResponse(boolean head)
282 {
283 _headResponse=head;
284 }
285
286 /* ------------------------------------------------------------------------------- */
287 protected void setResponseStatus(int status)
288 {
289 _responseStatus=status;
290 }
291
292 /* ------------------------------------------------------------------------------- */
293 public State getState()
294 {
295 return _state;
296 }
297
298 /* ------------------------------------------------------------------------------- */
299 public boolean inContentState()
300 {
301 return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
302 }
303
304 /* ------------------------------------------------------------------------------- */
305 public boolean inHeaderState()
306 {
307 return _state.ordinal() < State.CONTENT.ordinal();
308 }
309
310 /* ------------------------------------------------------------------------------- */
311 public boolean isChunking()
312 {
313 return _endOfContent==EndOfContent.CHUNKED_CONTENT;
314 }
315
316 /* ------------------------------------------------------------ */
317 public boolean isStart()
318 {
319 return isState(State.START);
320 }
321
322 /* ------------------------------------------------------------ */
323 public boolean isClose()
324 {
325 return isState(State.CLOSE);
326 }
327
328 /* ------------------------------------------------------------ */
329 public boolean isClosed()
330 {
331 return isState(State.CLOSED);
332 }
333
334 /* ------------------------------------------------------------ */
335 public boolean isIdle()
336 {
337 return __idleStates.contains(_state);
338 }
339
340 /* ------------------------------------------------------------ */
341 public boolean isComplete()
342 {
343 return __completeStates.contains(_state);
344 }
345
346 /* ------------------------------------------------------------------------------- */
347 public boolean isState(State state)
348 {
349 return _state == state;
350 }
351
352 /* ------------------------------------------------------------------------------- */
353 enum CharState { ILLEGAL, CR, LF, LEGAL }
354 private final static CharState[] __charState;
355 static
356 {
357 // token = 1*tchar
358 // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
359 // / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
360 // / DIGIT / ALPHA
361 // ; any VCHAR, except delimiters
362 // quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
363 // qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
364 // obs-text = %x80-FF
365 // comment = "(" *( ctext / quoted-pair / comment ) ")"
366 // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
367 // quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
368
369 __charState=new CharState[256];
370 Arrays.fill(__charState,CharState.ILLEGAL);
371 __charState[LINE_FEED]=CharState.LF;
372 __charState[CARRIAGE_RETURN]=CharState.CR;
373 __charState[TAB]=CharState.LEGAL;
374 __charState[SPACE]=CharState.LEGAL;
375
376 __charState['!']=CharState.LEGAL;
377 __charState['#']=CharState.LEGAL;
378 __charState['$']=CharState.LEGAL;
379 __charState['%']=CharState.LEGAL;
380 __charState['&']=CharState.LEGAL;
381 __charState['\'']=CharState.LEGAL;
382 __charState['*']=CharState.LEGAL;
383 __charState['+']=CharState.LEGAL;
384 __charState['-']=CharState.LEGAL;
385 __charState['.']=CharState.LEGAL;
386 __charState['^']=CharState.LEGAL;
387 __charState['_']=CharState.LEGAL;
388 __charState['`']=CharState.LEGAL;
389 __charState['|']=CharState.LEGAL;
390 __charState['~']=CharState.LEGAL;
391
392 __charState['"']=CharState.LEGAL;
393
394 __charState['\\']=CharState.LEGAL;
395 __charState['(']=CharState.LEGAL;
396 __charState[')']=CharState.LEGAL;
397 Arrays.fill(__charState,0x21,0x27+1,CharState.LEGAL);
398 Arrays.fill(__charState,0x2A,0x5B+1,CharState.LEGAL);
399 Arrays.fill(__charState,0x5D,0x7E+1,CharState.LEGAL);
400 Arrays.fill(__charState,0x80,0xFF+1,CharState.LEGAL);
401
402 }
403
404 /* ------------------------------------------------------------------------------- */
405 private byte next(ByteBuffer buffer)
406 {
407 byte ch = buffer.get();
408
409 CharState s = __charState[0xff & ch];
410 switch(s)
411 {
412 case ILLEGAL:
413 throw new IllegalCharacterException(_state,ch,buffer);
414
415 case LF:
416 _cr=false;
417 break;
418
419 case CR:
420 if (_cr)
421 throw new BadMessageException("Bad EOL");
422
423 _cr=true;
424 if (buffer.hasRemaining())
425 {
426 if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
427 _headerBytes++;
428 return next(buffer);
429 }
430
431 // Can return 0 here to indicate the need for more characters,
432 // because a real 0 in the buffer would cause a BadMessage below
433 return 0;
434
435 case LEGAL:
436 if (_cr)
437 throw new BadMessageException("Bad EOL");
438
439 }
440
441 return ch;
442 }
443
444 /* ------------------------------------------------------------------------------- */
445 /* Quick lookahead for the start state looking for a request method or a HTTP version,
446 * otherwise skip white space until something else to parse.
447 */
448 private boolean quickStart(ByteBuffer buffer)
449 {
450 if (_requestHandler!=null)
451 {
452 _method = HttpMethod.lookAheadGet(buffer);
453 if (_method!=null)
454 {
455 _methodString = _method.asString();
456 buffer.position(buffer.position()+_methodString.length()+1);
457
458 setState(State.SPACE1);
459 return false;
460 }
461 }
462 else if (_responseHandler!=null)
463 {
464 _version = HttpVersion.lookAheadGet(buffer);
465 if (_version!=null)
466 {
467 buffer.position(buffer.position()+_version.asString().length()+1);
468 setState(State.SPACE1);
469 return false;
470 }
471 }
472
473 // Quick start look
474 while (_state==State.START && buffer.hasRemaining())
475 {
476 int ch=next(buffer);
477
478 if (ch > SPACE)
479 {
480 _string.setLength(0);
481 _string.append((char)ch);
482 setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
483 return false;
484 }
485 else if (ch==0)
486 break;
487 else if (ch<0)
488 throw new BadMessageException();
489
490 // count this white space as a header byte to avoid DOS
491 if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
492 {
493 LOG.warn("padding is too large >"+_maxHeaderBytes);
494 throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
495 }
496 }
497 return false;
498 }
499
500 /* ------------------------------------------------------------------------------- */
501 private void setString(String s)
502 {
503 _string.setLength(0);
504 _string.append(s);
505 _length=s.length();
506 }
507
508 /* ------------------------------------------------------------------------------- */
509 private String takeString()
510 {
511 _string.setLength(_length);
512 String s =_string.toString();
513 _string.setLength(0);
514 _length=-1;
515 return s;
516 }
517
518 /* ------------------------------------------------------------------------------- */
519 /* Parse a request or response line
520 */
521 private boolean parseLine(ByteBuffer buffer)
522 {
523 boolean handle=false;
524
525 // Process headers
526 while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
527 {
528 // process each character
529 byte ch=next(buffer);
530 if (ch==0)
531 break;
532
533 if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
534 {
535 if (_state==State.URI)
536 {
537 LOG.warn("URI is too large >"+_maxHeaderBytes);
538 throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
539 }
540 else
541 {
542 if (_requestHandler!=null)
543 LOG.warn("request is too large >"+_maxHeaderBytes);
544 else
545 LOG.warn("response is too large >"+_maxHeaderBytes);
546 throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
547 }
548 }
549
550 switch (_state)
551 {
552 case METHOD:
553 if (ch == SPACE)
554 {
555 _length=_string.length();
556 _methodString=takeString();
557 HttpMethod method=HttpMethod.CACHE.get(_methodString);
558 if (method!=null && !_strict)
559 _methodString=method.asString();
560 setState(State.SPACE1);
561 }
562 else if (ch < SPACE)
563 {
564 if (ch==LINE_FEED)
565 throw new BadMessageException("No URI");
566 else
567 throw new IllegalCharacterException(_state,ch,buffer);
568 }
569 else
570 _string.append((char)ch);
571 break;
572
573 case RESPONSE_VERSION:
574 if (ch == HttpTokens.SPACE)
575 {
576 _length=_string.length();
577 String version=takeString();
578 _version=HttpVersion.CACHE.get(version);
579 if (_version==null)
580 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
581 setState(State.SPACE1);
582 }
583 else if (ch < HttpTokens.SPACE)
584 throw new IllegalCharacterException(_state,ch,buffer);
585 else
586 _string.append((char)ch);
587 break;
588
589 case SPACE1:
590 if (ch > HttpTokens.SPACE || ch<0)
591 {
592 if (_responseHandler!=null)
593 {
594 setState(State.STATUS);
595 setResponseStatus(ch-'0');
596 }
597 else
598 {
599 _uri.reset();
600 setState(State.URI);
601 // quick scan for space or EoBuffer
602 if (buffer.hasArray())
603 {
604 byte[] array=buffer.array();
605 int p=buffer.arrayOffset()+buffer.position();
606 int l=buffer.arrayOffset()+buffer.limit();
607 int i=p;
608 while (i<l && array[i]>HttpTokens.SPACE)
609 i++;
610
611 int len=i-p;
612 _headerBytes+=len;
613
614 if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
615 {
616 LOG.warn("URI is too large >"+_maxHeaderBytes);
617 throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
618 }
619 _uri.append(array,p-1,len+1);
620 buffer.position(i-buffer.arrayOffset());
621 }
622 else
623 _uri.append(ch);
624 }
625 }
626 else if (ch < HttpTokens.SPACE)
627 {
628 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
629 }
630 break;
631
632 case STATUS:
633 if (ch == HttpTokens.SPACE)
634 {
635 setState(State.SPACE2);
636 }
637 else if (ch>='0' && ch<='9')
638 {
639 _responseStatus=_responseStatus*10+(ch-'0');
640 }
641 else if (ch < HttpTokens.SPACE && ch>=0)
642 {
643 handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
644 setState(State.HEADER);
645 }
646 else
647 {
648 throw new BadMessageException();
649 }
650 break;
651
652 case URI:
653 if (ch == HttpTokens.SPACE)
654 {
655 setState(State.SPACE2);
656 }
657 else if (ch < HttpTokens.SPACE && ch>=0)
658 {
659 // HTTP/0.9
660 throw new BadMessageException("HTTP/0.9 not supported");
661 }
662 else
663 {
664 _uri.append(ch);
665 }
666 break;
667
668 case SPACE2:
669 if (ch > HttpTokens.SPACE)
670 {
671 _string.setLength(0);
672 _string.append((char)ch);
673 if (_responseHandler!=null)
674 {
675 _length=1;
676 setState(State.REASON);
677 }
678 else
679 {
680 setState(State.REQUEST_VERSION);
681
682 // try quick look ahead for HTTP Version
683 HttpVersion version;
684 if (buffer.position()>0 && buffer.hasArray())
685 version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
686 else
687 version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
688
689 if (version!=null)
690 {
691 int pos = buffer.position()+version.asString().length()-1;
692 if (pos<buffer.limit())
693 {
694 byte n=buffer.get(pos);
695 if (n==HttpTokens.CARRIAGE_RETURN)
696 {
697 _cr=true;
698 _version=version;
699 _string.setLength(0);
700 buffer.position(pos+1);
701 }
702 else if (n==HttpTokens.LINE_FEED)
703 {
704 _version=version;
705 _string.setLength(0);
706 buffer.position(pos);
707 }
708 }
709 }
710 }
711 }
712 else if (ch == HttpTokens.LINE_FEED)
713 {
714 if (_responseHandler!=null)
715 {
716 handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
717 setState(State.HEADER);
718 }
719 else
720 {
721 // HTTP/0.9
722 throw new BadMessageException("HTTP/0.9 not supported");
723 }
724 }
725 else if (ch<0)
726 throw new BadMessageException();
727 break;
728
729 case REQUEST_VERSION:
730 if (ch == HttpTokens.LINE_FEED)
731 {
732 if (_version==null)
733 {
734 _length=_string.length();
735 _version=HttpVersion.CACHE.get(takeString());
736 }
737 if (_version==null)
738 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
739
740 // Should we try to cache header fields?
741 if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion() && _handler.getHeaderCacheSize()>0)
742 {
743 int header_cache = _handler.getHeaderCacheSize();
744 _connectionFields=new ArrayTernaryTrie<>(header_cache);
745 }
746
747 setState(State.HEADER);
748
749 handle=_requestHandler.startRequest(_methodString,_uri.toString(), _version)||handle;
750 continue;
751 }
752 else if (ch>=HttpTokens.SPACE)
753 _string.append((char)ch);
754 else
755 throw new BadMessageException();
756
757 break;
758
759 case REASON:
760 if (ch == HttpTokens.LINE_FEED)
761 {
762 String reason=takeString();
763
764 setState(State.HEADER);
765 handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
766 continue;
767 }
768 else if (ch>=HttpTokens.SPACE)
769 {
770 _string.append((char)ch);
771 if (ch!=' '&&ch!='\t')
772 _length=_string.length();
773 }
774 else
775 throw new BadMessageException();
776 break;
777
778 default:
779 throw new IllegalStateException(_state.toString());
780
781 }
782 }
783
784 return handle;
785 }
786
787 private void parsedHeader()
788 {
789 // handler last header if any. Delayed to here just in case there was a continuation line (above)
790 if (_headerString!=null || _valueString!=null)
791 {
792 // Handle known headers
793 if (_header!=null)
794 {
795 boolean add_to_connection_trie=false;
796 switch (_header)
797 {
798 case CONTENT_LENGTH:
799 if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
800 {
801 try
802 {
803 _contentLength=Long.parseLong(_valueString);
804 }
805 catch(NumberFormatException e)
806 {
807 LOG.ignore(e);
808 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
809 }
810 if (_contentLength <= 0)
811 _endOfContent=EndOfContent.NO_CONTENT;
812 else
813 _endOfContent=EndOfContent.CONTENT_LENGTH;
814 }
815 break;
816
817 case TRANSFER_ENCODING:
818 if (_value==HttpHeaderValue.CHUNKED)
819 _endOfContent=EndOfContent.CHUNKED_CONTENT;
820 else
821 {
822 if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
823 _endOfContent=EndOfContent.CHUNKED_CONTENT;
824 else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
825 {
826 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad chunking");
827 }
828 }
829 break;
830
831 case HOST:
832 _host=true;
833 if (!(_field instanceof HostPortHttpField))
834 {
835 _field=new HostPortHttpField(_header,_strict?_headerString:_header.asString(),_valueString);
836 add_to_connection_trie=_connectionFields!=null;
837 }
838 break;
839
840 case CONNECTION:
841 // Don't cache if not persistent
842 if (_valueString!=null && _valueString.contains("close"))
843 _connectionFields=null;
844
845 break;
846
847 case AUTHORIZATION:
848 case ACCEPT:
849 case ACCEPT_CHARSET:
850 case ACCEPT_ENCODING:
851 case ACCEPT_LANGUAGE:
852 case COOKIE:
853 case CACHE_CONTROL:
854 case USER_AGENT:
855 add_to_connection_trie=_connectionFields!=null && _field==null;
856 break;
857
858 default: break;
859 }
860
861 if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
862 {
863 if (_field==null)
864 _field=new HttpField(_header,_strict?_headerString:_header.asString(),_valueString);
865 _connectionFields.put(_field);
866 }
867 }
868 _handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString));
869 }
870
871 _headerString=_valueString=null;
872 _header=null;
873 _value=null;
874 _field=null;
875 }
876
877
878 /* ------------------------------------------------------------------------------- */
879 /*
880 * Parse the message headers and return true if the handler has signaled for a return
881 */
882 protected boolean parseHeaders(ByteBuffer buffer)
883 {
884 boolean handle=false;
885
886 // Process headers
887 while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
888 {
889 // process each character
890 byte ch=next(buffer);
891 if (ch==0)
892 break;
893
894 if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
895 {
896 LOG.warn("Header is too large >"+_maxHeaderBytes);
897 throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
898 }
899
900 switch (_state)
901 {
902 case HEADER:
903 switch(ch)
904 {
905 case HttpTokens.COLON:
906 case HttpTokens.SPACE:
907 case HttpTokens.TAB:
908 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Continuation");
909
910 case HttpTokens.LINE_FEED:
911 {
912 _contentPosition=0;
913
914 // End of headers!
915
916 // Was there a required host header?
917 if (!_host && _version==HttpVersion.HTTP_1_1 && _requestHandler!=null)
918 {
919 throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"No Host");
920 }
921
922 // is it a response that cannot have a body?
923 if (_responseHandler !=null && // response
924 (_responseStatus == 304 || // not-modified response
925 _responseStatus == 204 || // no-content response
926 _responseStatus < 200)) // 1xx response
927 _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
928
929 // else if we don't know framing
930 else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
931 {
932 if (_responseStatus == 0 // request
933 || _responseStatus == 304 // not-modified response
934 || _responseStatus == 204 // no-content response
935 || _responseStatus < 200) // 1xx response
936 _endOfContent=EndOfContent.NO_CONTENT;
937 else
938 _endOfContent=EndOfContent.EOF_CONTENT;
939 }
940
941 // How is the message ended?
942 switch (_endOfContent)
943 {
944 case EOF_CONTENT:
945 setState(State.EOF_CONTENT);
946 handle=_handler.headerComplete()||handle;
947 return handle;
948
949 case CHUNKED_CONTENT:
950 setState(State.CHUNKED_CONTENT);
951 handle=_handler.headerComplete()||handle;
952 return handle;
953
954 case NO_CONTENT:
955 handle=_handler.headerComplete()||handle;
956 setState(State.END);
957 handle=_handler.messageComplete()||handle;
958 return handle;
959
960 default:
961 setState(State.CONTENT);
962 handle=_handler.headerComplete()||handle;
963 return handle;
964 }
965 }
966
967 default:
968 {
969 // now handle the ch
970 if (ch<=HttpTokens.SPACE)
971 throw new BadMessageException();
972
973 if (buffer.hasRemaining())
974 {
975 // Try a look ahead for the known header name and value.
976 HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
977 if (field==null)
978 field=CACHE.getBest(buffer,-1,buffer.remaining());
979
980 if (field!=null)
981 {
982 final String n;
983 final String v;
984
985 if (_strict)
986 {
987 // Have to get the fields exactly from the buffer to match case
988 String fn=field.getName();
989 String fv=field.getValue();
990 n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
991 if (fv==null)
992 v=null;
993 else
994 {
995 v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
996 field=new HttpField(field.getHeader(),n,v);
997 }
998 }
999 else
1000 {
1001 n=field.getName();
1002 v=field.getValue();
1003 }
1004
1005 _header=field.getHeader();
1006 _headerString=n;
1007
1008 if (v==null)
1009 {
1010 // Header only
1011 setState(State.HEADER_VALUE);
1012 _string.setLength(0);
1013 _length=0;
1014 buffer.position(buffer.position()+n.length()+1);
1015 break;
1016 }
1017 else
1018 {
1019 // Header and value
1020 int pos=buffer.position()+n.length()+v.length()+1;
1021 byte b=buffer.get(pos);
1022
1023 if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
1024 {
1025 _field=field;
1026 _valueString=v;
1027 setState(State.HEADER_IN_VALUE);
1028
1029 if (b==HttpTokens.CARRIAGE_RETURN)
1030 {
1031 _cr=true;
1032 buffer.position(pos+1);
1033 }
1034 else
1035 buffer.position(pos);
1036 break;
1037 }
1038 else
1039 {
1040 setState(State.HEADER_IN_VALUE);
1041 setString(v);
1042 buffer.position(pos);
1043 break;
1044 }
1045 }
1046 }
1047 }
1048
1049 // New header
1050 setState(State.HEADER_IN_NAME);
1051 _string.setLength(0);
1052 _string.append((char)ch);
1053 _length=1;
1054
1055 }
1056 }
1057 break;
1058
1059 case HEADER_IN_NAME:
1060 if (ch==HttpTokens.COLON)
1061 {
1062 if (_headerString==null)
1063 {
1064 _headerString=takeString();
1065 _header=HttpHeader.CACHE.get(_headerString);
1066 }
1067 _length=-1;
1068
1069 setState(State.HEADER_VALUE);
1070 break;
1071 }
1072
1073 if (ch>HttpTokens.SPACE)
1074 {
1075 if (_header!=null)
1076 {
1077 setString(_header.asString());
1078 _header=null;
1079 _headerString=null;
1080 }
1081
1082 _string.append((char)ch);
1083 if (ch>HttpTokens.SPACE)
1084 _length=_string.length();
1085 break;
1086 }
1087
1088 throw new IllegalCharacterException(_state,ch,buffer);
1089
1090 case HEADER_VALUE:
1091 if (ch>HttpTokens.SPACE || ch<0)
1092 {
1093 _string.append((char)(0xff&ch));
1094 _length=_string.length();
1095 setState(State.HEADER_IN_VALUE);
1096 break;
1097 }
1098
1099 if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
1100 break;
1101
1102 if (ch==HttpTokens.LINE_FEED)
1103 {
1104 _value=null;
1105 _string.setLength(0);
1106 _valueString=null;
1107 _length=-1;
1108
1109 parsedHeader();
1110 setState(State.HEADER);
1111 break;
1112 }
1113 throw new IllegalCharacterException(_state,ch,buffer);
1114
1115 case HEADER_IN_VALUE:
1116 if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
1117 {
1118 if (_valueString!=null)
1119 {
1120 setString(_valueString);
1121 _valueString=null;
1122 _field=null;
1123 }
1124 _string.append((char)(0xff&ch));
1125 if (ch>HttpTokens.SPACE || ch<0)
1126 _length=_string.length();
1127 break;
1128 }
1129
1130 if (ch==HttpTokens.LINE_FEED)
1131 {
1132 if (_length > 0)
1133 {
1134 _value=null;
1135 _valueString=takeString();
1136 _length=-1;
1137 }
1138 parsedHeader();
1139 setState(State.HEADER);
1140 break;
1141 }
1142
1143 throw new IllegalCharacterException(_state,ch,buffer);
1144
1145 default:
1146 throw new IllegalStateException(_state.toString());
1147
1148 }
1149 }
1150
1151 return handle;
1152 }
1153
1154 /* ------------------------------------------------------------------------------- */
1155 /**
1156 * Parse until next Event.
1157 * @param buffer the buffer to parse
1158 * @return True if an {@link RequestHandler} method was called and it returned true;
1159 */
1160 public boolean parseNext(ByteBuffer buffer)
1161 {
1162 if (DEBUG)
1163 LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
1164 try
1165 {
1166 // Start a request/response
1167 if (_state==State.START)
1168 {
1169 _version=null;
1170 _method=null;
1171 _methodString=null;
1172 _endOfContent=EndOfContent.UNKNOWN_CONTENT;
1173 _header=null;
1174 if (quickStart(buffer))
1175 return true;
1176 }
1177
1178 // Request/response line
1179 if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
1180 {
1181 if (parseLine(buffer))
1182 return true;
1183 }
1184
1185 // parse headers
1186 if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
1187 {
1188 if (parseHeaders(buffer))
1189 return true;
1190 }
1191
1192 // parse content
1193 if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
1194 {
1195 // Handle HEAD response
1196 if (_responseStatus>0 && _headResponse)
1197 {
1198 setState(State.END);
1199 return _handler.messageComplete();
1200 }
1201 else
1202 {
1203 if (parseContent(buffer))
1204 return true;
1205 }
1206 }
1207
1208 // handle end states
1209 if (_state==State.END)
1210 {
1211 // eat white space
1212 while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
1213 buffer.get();
1214 }
1215 else if (_state==State.CLOSE)
1216 {
1217 // Seeking EOF
1218 if (BufferUtil.hasContent(buffer))
1219 {
1220 // Just ignore data when closed
1221 _headerBytes+=buffer.remaining();
1222 BufferUtil.clear(buffer);
1223 if (_maxHeaderBytes>0 && _headerBytes>_maxHeaderBytes)
1224 {
1225 // Don't want to waste time reading data of a closed request
1226 throw new IllegalStateException("too much data seeking EOF");
1227 }
1228 }
1229 }
1230 else if (_state==State.CLOSED)
1231 {
1232 BufferUtil.clear(buffer);
1233 }
1234
1235 // Handle EOF
1236 if (_eof && !buffer.hasRemaining())
1237 {
1238 switch(_state)
1239 {
1240 case CLOSED:
1241 break;
1242
1243 case START:
1244 setState(State.CLOSED);
1245 _handler.earlyEOF();
1246 break;
1247
1248 case END:
1249 case CLOSE:
1250 setState(State.CLOSED);
1251 break;
1252
1253 case EOF_CONTENT:
1254 setState(State.CLOSED);
1255 return _handler.messageComplete();
1256
1257 case CONTENT:
1258 case CHUNKED_CONTENT:
1259 case CHUNK_SIZE:
1260 case CHUNK_PARAMS:
1261 case CHUNK:
1262 setState(State.CLOSED);
1263 _handler.earlyEOF();
1264 break;
1265
1266 default:
1267 if (DEBUG)
1268 LOG.debug("{} EOF in {}",this,_state);
1269 setState(State.CLOSED);
1270 _handler.badMessage(400,null);
1271 break;
1272 }
1273 }
1274 }
1275 catch(BadMessageException e)
1276 {
1277 BufferUtil.clear(buffer);
1278
1279 Throwable cause = e.getCause();
1280 boolean stack = LOG.isDebugEnabled() ||
1281 (!(cause instanceof NumberFormatException ) && (cause instanceof RuntimeException || cause instanceof Error));
1282
1283 if (stack)
1284 LOG.warn("bad HTTP parsed: "+e._code+(e.getReason()!=null?" "+e.getReason():"")+" for "+_handler,e);
1285 else
1286 LOG.warn("bad HTTP parsed: "+e._code+(e.getReason()!=null?" "+e.getReason():"")+" for "+_handler);
1287 setState(State.CLOSE);
1288 _handler.badMessage(e.getCode(), e.getReason());
1289 }
1290 catch(NumberFormatException|IllegalStateException e)
1291 {
1292 BufferUtil.clear(buffer);
1293 LOG.warn("parse exception: {} in {} for {}",e.toString(),_state,_handler);
1294 if (DEBUG)
1295 LOG.debug(e);
1296
1297 switch(_state)
1298 {
1299 case CLOSED:
1300 break;
1301 case CLOSE:
1302 _handler.earlyEOF();
1303 break;
1304 default:
1305 setState(State.CLOSE);
1306 _handler.badMessage(400,null);
1307 }
1308 }
1309 catch(Exception|Error e)
1310 {
1311 BufferUtil.clear(buffer);
1312
1313 LOG.warn("parse exception: "+e.toString()+" for "+_handler,e);
1314
1315 switch(_state)
1316 {
1317 case CLOSED:
1318 break;
1319 case CLOSE:
1320 _handler.earlyEOF();
1321 break;
1322 default:
1323 setState(State.CLOSE);
1324 _handler.badMessage(400,null);
1325 }
1326 }
1327 return false;
1328 }
1329
1330 protected boolean parseContent(ByteBuffer buffer)
1331 {
1332 int remaining=buffer.remaining();
1333 if (remaining==0 && _state==State.CONTENT)
1334 {
1335 long content=_contentLength - _contentPosition;
1336 if (content == 0)
1337 {
1338 setState(State.END);
1339 return _handler.messageComplete();
1340 }
1341 }
1342
1343 // Handle _content
1344 byte ch;
1345 while (_state.ordinal() < State.END.ordinal() && remaining>0)
1346 {
1347 switch (_state)
1348 {
1349 case EOF_CONTENT:
1350 _contentChunk=buffer.asReadOnlyBuffer();
1351 _contentPosition += remaining;
1352 buffer.position(buffer.position()+remaining);
1353 if (_handler.content(_contentChunk))
1354 return true;
1355 break;
1356
1357 case CONTENT:
1358 {
1359 long content=_contentLength - _contentPosition;
1360 if (content == 0)
1361 {
1362 setState(State.END);
1363 return _handler.messageComplete();
1364 }
1365 else
1366 {
1367 _contentChunk=buffer.asReadOnlyBuffer();
1368
1369 // limit content by expected size
1370 if (remaining > content)
1371 {
1372 // We can cast remaining to an int as we know that it is smaller than
1373 // or equal to length which is already an int.
1374 _contentChunk.limit(_contentChunk.position()+(int)content);
1375 }
1376
1377 _contentPosition += _contentChunk.remaining();
1378 buffer.position(buffer.position()+_contentChunk.remaining());
1379
1380 if (_handler.content(_contentChunk))
1381 return true;
1382
1383 if(_contentPosition == _contentLength)
1384 {
1385 setState(State.END);
1386 return _handler.messageComplete();
1387 }
1388 }
1389 break;
1390 }
1391
1392 case CHUNKED_CONTENT:
1393 {
1394 ch=next(buffer);
1395 if (ch>HttpTokens.SPACE)
1396 {
1397 _chunkLength=TypeUtil.convertHexDigit(ch);
1398 _chunkPosition=0;
1399 setState(State.CHUNK_SIZE);
1400 }
1401
1402 break;
1403 }
1404
1405 case CHUNK_SIZE:
1406 {
1407 ch=next(buffer);
1408 if (ch==0)
1409 break;
1410 if (ch == HttpTokens.LINE_FEED)
1411 {
1412 if (_chunkLength == 0)
1413 setState(State.CHUNK_END);
1414 else
1415 setState(State.CHUNK);
1416 }
1417 else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
1418 setState(State.CHUNK_PARAMS);
1419 else
1420 _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
1421 break;
1422 }
1423
1424 case CHUNK_PARAMS:
1425 {
1426 ch=next(buffer);
1427 if (ch == HttpTokens.LINE_FEED)
1428 {
1429 if (_chunkLength == 0)
1430 setState(State.CHUNK_END);
1431 else
1432 setState(State.CHUNK);
1433 }
1434 break;
1435 }
1436
1437 case CHUNK:
1438 {
1439 int chunk=_chunkLength - _chunkPosition;
1440 if (chunk == 0)
1441 {
1442 setState(State.CHUNKED_CONTENT);
1443 }
1444 else
1445 {
1446 _contentChunk=buffer.asReadOnlyBuffer();
1447
1448 if (remaining > chunk)
1449 _contentChunk.limit(_contentChunk.position()+chunk);
1450 chunk=_contentChunk.remaining();
1451
1452 _contentPosition += chunk;
1453 _chunkPosition += chunk;
1454 buffer.position(buffer.position()+chunk);
1455 if (_handler.content(_contentChunk))
1456 return true;
1457 }
1458 break;
1459 }
1460
1461 case CHUNK_END:
1462 {
1463 // TODO handle chunk trailer
1464 ch=next(buffer);
1465 if (ch==0)
1466 break;
1467 if (ch == HttpTokens.LINE_FEED)
1468 {
1469 setState(State.END);
1470 return _handler.messageComplete();
1471 }
1472 throw new IllegalCharacterException(_state,ch,buffer);
1473 }
1474
1475 case CLOSED:
1476 {
1477 BufferUtil.clear(buffer);
1478 return false;
1479 }
1480
1481 default:
1482 break;
1483
1484 }
1485
1486 remaining=buffer.remaining();
1487 }
1488 return false;
1489 }
1490
1491 /* ------------------------------------------------------------------------------- */
1492 public boolean isAtEOF()
1493
1494 {
1495 return _eof;
1496 }
1497
1498 /* ------------------------------------------------------------------------------- */
1499 /** Signal that the associated data source is at EOF
1500 */
1501 public void atEOF()
1502 {
1503 if (DEBUG)
1504 LOG.debug("atEOF {}", this);
1505 _eof=true;
1506 }
1507
1508 /* ------------------------------------------------------------------------------- */
1509 /** Request that the associated data source be closed
1510 */
1511 public void close()
1512 {
1513 if (DEBUG)
1514 LOG.debug("close {}", this);
1515 setState(State.CLOSE);
1516 }
1517
1518 /* ------------------------------------------------------------------------------- */
1519 public void reset()
1520 {
1521 if (DEBUG)
1522 LOG.debug("reset {}", this);
1523
1524 // reset state
1525 if (_state==State.CLOSE || _state==State.CLOSED)
1526 return;
1527
1528 setState(State.START);
1529 _endOfContent=EndOfContent.UNKNOWN_CONTENT;
1530 _contentLength=-1;
1531 _contentPosition=0;
1532 _responseStatus=0;
1533 _contentChunk=null;
1534 _headerBytes=0;
1535 _host=false;
1536 }
1537
1538 /* ------------------------------------------------------------------------------- */
1539 protected void setState(State state)
1540 {
1541 if (DEBUG)
1542 LOG.debug("{} --> {}",_state,state);
1543 _state=state;
1544 }
1545
1546 /* ------------------------------------------------------------------------------- */
1547 public Trie<HttpField> getFieldCache()
1548 {
1549 return _connectionFields;
1550 }
1551
1552 /* ------------------------------------------------------------------------------- */
1553 private String getProxyField(ByteBuffer buffer)
1554 {
1555 _string.setLength(0);
1556 _length=0;
1557
1558 while (buffer.hasRemaining())
1559 {
1560 // process each character
1561 byte ch=next(buffer);
1562 if (ch<=' ')
1563 return _string.toString();
1564 _string.append((char)ch);
1565 }
1566 throw new BadMessageException();
1567 }
1568
1569 /* ------------------------------------------------------------------------------- */
1570 @Override
1571 public String toString()
1572 {
1573 return String.format("%s{s=%s,%d of %d}",
1574 getClass().getSimpleName(),
1575 _state,
1576 _contentPosition,
1577 _contentLength);
1578 }
1579
1580 /* ------------------------------------------------------------ */
1581 /* ------------------------------------------------------------ */
1582 /* ------------------------------------------------------------ */
1583 /* Event Handler interface
1584 * These methods return true if the caller should process the events
1585 * so far received (eg return from parseNext and call HttpChannel.handle).
1586 * If multiple callbacks are called in sequence (eg
1587 * headerComplete then messageComplete) from the same point in the parsing
1588 * then it is sufficient for the caller to process the events only once.
1589 */
1590 public interface HttpHandler
1591 {
1592 public boolean content(ByteBuffer item);
1593
1594 public boolean headerComplete();
1595
1596 public boolean messageComplete();
1597
1598 /**
1599 * This is the method called by parser when a HTTP Header name and value is found
1600 * @param field The field parsed
1601 */
1602 public void parsedHeader(HttpField field);
1603
1604 /* ------------------------------------------------------------ */
1605 /** Called to signal that an EOF was received unexpectedly
1606 * during the parsing of a HTTP message
1607 */
1608 public void earlyEOF();
1609
1610 /* ------------------------------------------------------------ */
1611 /** Called to signal that a bad HTTP message has been received.
1612 * @param status The bad status to send
1613 * @param reason The textual reason for badness
1614 */
1615 public void badMessage(int status, String reason);
1616
1617 /* ------------------------------------------------------------ */
1618 /** @return the size in bytes of the per parser header cache
1619 */
1620 public int getHeaderCacheSize();
1621 }
1622
1623 /* ------------------------------------------------------------------------------- */
1624 /* ------------------------------------------------------------------------------- */
1625 /* ------------------------------------------------------------------------------- */
1626 public interface RequestHandler extends HttpHandler
1627 {
1628 /**
1629 * This is the method called by parser when the HTTP request line is parsed
1630 * @param method The method
1631 * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
1632 * @param version the http version in use
1633 * @return true if handling parsing should return.
1634 */
1635 public boolean startRequest(String method, String uri, HttpVersion version);
1636
1637 }
1638
1639 /* ------------------------------------------------------------------------------- */
1640 /* ------------------------------------------------------------------------------- */
1641 /* ------------------------------------------------------------------------------- */
1642 public interface ResponseHandler extends HttpHandler
1643 {
1644 /**
1645 * This is the method called by parser when the HTTP request line is parsed
1646 * @param version the http version in use
1647 * @param status the response status
1648 * @param reason the response reason phrase
1649 * @return true if handling parsing should return
1650 */
1651 public boolean startResponse(HttpVersion version, int status, String reason);
1652 }
1653
1654 /* ------------------------------------------------------------------------------- */
1655 @SuppressWarnings("serial")
1656 private static class IllegalCharacterException extends BadMessageException
1657 {
1658 private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
1659 {
1660 super(400,String.format("Illegal character 0x%X",ch));
1661 // Bug #460642 - don't reveal buffers to end user
1662 LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
1663 }
1664 }
1665 }