View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.http;
15  
16  import java.io.EOFException;
17  import java.io.IOException;
18  
19  import org.eclipse.jetty.io.Buffer;
20  import org.eclipse.jetty.io.BufferCache.CachedBuffer;
21  import org.eclipse.jetty.io.BufferUtil;
22  import org.eclipse.jetty.io.Buffers;
23  import org.eclipse.jetty.io.ByteArrayBuffer;
24  import org.eclipse.jetty.io.EndPoint;
25  import org.eclipse.jetty.io.EofException;
26  import org.eclipse.jetty.io.View;
27  import org.eclipse.jetty.io.bio.StreamEndPoint;
28  import org.eclipse.jetty.util.StringUtil;
29  import org.eclipse.jetty.util.log.Log;
30  import org.eclipse.jetty.util.log.Logger;
31  
32  public class HttpParser implements Parser
33  {
34      private static final Logger LOG = Log.getLogger(HttpParser.class);
35  
36      // States
37      public static final int STATE_START=-14;
38      public static final int STATE_FIELD0=-13;
39      public static final int STATE_SPACE1=-12;
40      public static final int STATE_STATUS=-11;
41      public static final int STATE_URI=-10;
42      public static final int STATE_SPACE2=-9;
43      public static final int STATE_END0=-8;
44      public static final int STATE_END1=-7;
45      public static final int STATE_FIELD2=-6;
46      public static final int STATE_HEADER=-5;
47      public static final int STATE_HEADER_NAME=-4;
48      public static final int STATE_HEADER_IN_NAME=-3;
49      public static final int STATE_HEADER_VALUE=-2;
50      public static final int STATE_HEADER_IN_VALUE=-1;
51      public static final int STATE_END=0;
52      public static final int STATE_EOF_CONTENT=1;
53      public static final int STATE_CONTENT=2;
54      public static final int STATE_CHUNKED_CONTENT=3;
55      public static final int STATE_CHUNK_SIZE=4;
56      public static final int STATE_CHUNK_PARAMS=5;
57      public static final int STATE_CHUNK=6;
58  
59      private final EventHandler _handler;
60      private final Buffers _buffers; // source of buffers
61      private final EndPoint _endp;
62      private Buffer _header; // Buffer for header data (and small _content)
63      private Buffer _body; // Buffer for large content
64      private Buffer _buffer; // The current buffer in use (either _header or _content)
65      private CachedBuffer _cached;
66      private View.CaseInsensitive _tok0; // Saved token: header name, request method or response version
67      private View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code
68      private String _multiLineValue;
69      private int _responseStatus; // If >0 then we are parsing a response
70      private boolean _forceContentBuffer;
71  
72      /* ------------------------------------------------------------------------------- */
73      protected final View  _contentView=new View(); // View of the content in the buffer for {@link Input}
74      protected int _state=STATE_START;
75      protected byte _eol;
76      protected int _length;
77      protected long _contentLength;
78      protected long _contentPosition;
79      protected int _chunkLength;
80      protected int _chunkPosition;
81      private boolean _headResponse;
82  
83      /* ------------------------------------------------------------------------------- */
84      /**
85       * Constructor.
86       */
87      public HttpParser(Buffer buffer, EventHandler handler)
88      {
89          _endp=null;
90          _buffers=null;
91          _header=buffer;
92          _buffer=buffer;
93          _handler=handler;
94  
95          if (buffer != null)
96          {
97              _tok0=new View.CaseInsensitive(buffer);
98              _tok1=new View.CaseInsensitive(buffer);
99              _tok0.setPutIndex(_tok0.getIndex());
100             _tok1.setPutIndex(_tok1.getIndex());
101         }
102     }
103 
104     /* ------------------------------------------------------------------------------- */
105     /**
106      * Constructor.
107      * @param buffers the buffers to use
108      * @param endp the endpoint
109      * @param handler the even handler
110      */
111     public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler)
112     {
113         _buffers=buffers;
114         _endp=endp;
115         _handler=handler;
116     }
117 
118     /* ------------------------------------------------------------------------------- */
119     public long getContentLength()
120     {
121         return _contentLength;
122     }
123 
124     /* ------------------------------------------------------------ */
125     public long getContentRead()
126     {
127         return _contentPosition;
128     }
129 
130     /* ------------------------------------------------------------ */
131     /** Set if a HEAD response is expected
132      * @param head
133      */
134     public void setHeadResponse(boolean head)
135     {
136         _headResponse=head;
137     }
138 
139     /* ------------------------------------------------------------------------------- */
140     public int getState()
141     {
142         return _state;
143     }
144 
145     /* ------------------------------------------------------------------------------- */
146     public boolean inContentState()
147     {
148         return _state > 0;
149     }
150 
151     /* ------------------------------------------------------------------------------- */
152     public boolean inHeaderState()
153     {
154         return _state < 0;
155     }
156 
157     /* ------------------------------------------------------------------------------- */
158     public boolean isChunking()
159     {
160         return _contentLength==HttpTokens.CHUNKED_CONTENT;
161     }
162 
163     /* ------------------------------------------------------------ */
164     public boolean isIdle()
165     {
166         return isState(STATE_START);
167     }
168 
169     /* ------------------------------------------------------------ */
170     public boolean isComplete()
171     {
172         return isState(STATE_END);
173     }
174 
175     /* ------------------------------------------------------------ */
176     public boolean isMoreInBuffer()
177     throws IOException
178     {
179         return ( _header!=null && _header.hasContent() ||
180              _body!=null && _body.hasContent());
181     }
182 
183     /* ------------------------------------------------------------------------------- */
184     public boolean isState(int state)
185     {
186         return _state == state;
187     }
188 
189     /* ------------------------------------------------------------------------------- */
190     /**
191      * Parse until {@link #STATE_END END} state.
192      * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed.
193      * @throws IllegalStateException If the buffers have already been partially parsed.
194      */
195     public void parse() throws IOException
196     {
197         if (_state==STATE_END)
198             reset();
199         if (_state!=STATE_START)
200             throw new IllegalStateException("!START");
201 
202         // continue parsing
203         while (_state != STATE_END)
204             if (parseNext()<0)
205                 return;
206     }
207 
208     /* ------------------------------------------------------------------------------- */
209     /**
210      * Parse until END state.
211      * This method will parse any remaining content in the current buffer. It does not care about the
212      * {@link #getState current state} of the parser.
213      * @see #parse
214      * @see #parseNext
215      */
216     public int parseAvailable() throws IOException
217     {
218         int progress = parseNext();
219         int total=progress>0?1:0;
220 
221         // continue parsing
222         while (!isComplete() && _buffer!=null && _buffer.length()>0)
223         {
224             progress = parseNext();
225             if (progress>0)
226                 total++;
227         }
228         return total;
229     }
230 
231 
232     /* ------------------------------------------------------------------------------- */
233     /**
234      * Parse until next Event.
235      * @return an indication of progress <0 EOF, 0 no progress, >0 progress.
236      */
237     public int parseNext() throws IOException
238     {
239         int progress=0;
240 
241         if (_state == STATE_END)
242             return 0;
243 
244         if (_buffer==null)
245         {
246             if (_header == null)
247             {
248                 _header=_buffers.getHeader();
249             }
250             _buffer=_header;
251             _tok0=new View.CaseInsensitive(_header);
252             _tok1=new View.CaseInsensitive(_header);
253             _tok0.setPutIndex(_tok0.getIndex());
254             _tok1.setPutIndex(_tok1.getIndex());
255         }
256 
257 
258         if (_state == STATE_CONTENT && _contentPosition == _contentLength)
259         {
260             _state=STATE_END;
261             _handler.messageComplete(_contentPosition);
262             return 1;
263         }
264 
265         int length=_buffer.length();
266 
267         // Fill buffer if we can
268         if (length == 0)
269         {
270             long filled=-1;
271             IOException ex=null;
272             try
273             {
274                 filled=fill();
275             }
276             catch(IOException e)
277             {
278                 LOG.debug(this.toString(),e);
279                 ex=e;
280             }
281 
282             if (filled < 0 || _endp.isInputShutdown())
283             {
284                 if (_headResponse && _state>STATE_END)
285                 {
286                     _state=STATE_END;
287                     _handler.messageComplete(_contentPosition);
288                     return 1;
289                 }
290                 if ( _state == STATE_EOF_CONTENT)
291                 {
292                     if (_buffer.length()>0)
293                     {
294                         // TODO should we do this here or fall down to main loop?
295                         Buffer chunk=_buffer.get(_buffer.length());
296                         _contentPosition += chunk.length();
297                         _contentView.update(chunk);
298                         _handler.content(chunk); // May recurse here
299                     }
300                     _state=STATE_END;
301                     _handler.messageComplete(_contentPosition);
302                     return 1;
303                 }
304 
305                 if (ex!=null)
306                     throw ex;
307 
308                 if (!isComplete() && !isIdle())
309                     throw new EofException();
310 
311                 return -1;
312             }
313             length=_buffer.length();
314         }
315 
316 
317         // EventHandler header
318         byte ch;
319         byte[] array=_buffer.array();
320         int last=_state;
321         while (_state<STATE_END && length-->0)
322         {
323             if (last!=_state)
324             {
325                 progress++;
326                 last=_state;
327             }
328 
329             ch=_buffer.get();
330 
331             if (_eol == HttpTokens.CARRIAGE_RETURN && ch == HttpTokens.LINE_FEED)
332             {
333                 _eol=HttpTokens.LINE_FEED;
334                 continue;
335             }
336             _eol=0;
337 
338             switch (_state)
339             {
340                 case STATE_START:
341                     _contentLength=HttpTokens.UNKNOWN_CONTENT;
342                     _cached=null;
343                     if (ch > HttpTokens.SPACE || ch<0)
344                     {
345                         _buffer.mark();
346                         _state=STATE_FIELD0;
347                     }
348                     break;
349 
350                 case STATE_FIELD0:
351                     if (ch == HttpTokens.SPACE)
352                     {
353                         _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1);
354                         _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0;
355                         _state=STATE_SPACE1;
356                         continue;
357                     }
358                     else if (ch < HttpTokens.SPACE && ch>=0)
359                     {
360                         throw new HttpException(HttpStatus.BAD_REQUEST_400);
361                     }
362                     break;
363 
364                 case STATE_SPACE1:
365                     if (ch > HttpTokens.SPACE || ch<0)
366                     {
367                         _buffer.mark();
368                         if (_responseStatus>=0)
369                         {
370                             _state=STATE_STATUS;
371                             _responseStatus=ch-'0';
372                         }
373                         else
374                             _state=STATE_URI;
375                     }
376                     else if (ch < HttpTokens.SPACE)
377                     {
378                         throw new HttpException(HttpStatus.BAD_REQUEST_400);
379                     }
380                     break;
381 
382                 case STATE_STATUS:
383                     if (ch == HttpTokens.SPACE)
384                     {
385                         _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
386                         _state=STATE_SPACE2;
387                         continue;
388                     }
389                     else if (ch>='0' && ch<='9')
390                     {
391                         _responseStatus=_responseStatus*10+(ch-'0');
392                         continue;
393                     }
394                     else if (ch < HttpTokens.SPACE && ch>=0)
395                     {
396                         _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
397                         _eol=ch;
398                         _state=STATE_HEADER;
399                         _tok0.setPutIndex(_tok0.getIndex());
400                         _tok1.setPutIndex(_tok1.getIndex());
401                         _multiLineValue=null;
402                         continue;
403                     }
404                     // not a digit, so must be a URI
405                     _state=STATE_URI;
406                     _responseStatus=-1;
407                     break;
408 
409                 case STATE_URI:
410                     if (ch == HttpTokens.SPACE)
411                     {
412                         _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
413                         _state=STATE_SPACE2;
414                         continue;
415                     }
416                     else if (ch < HttpTokens.SPACE && ch>=0)
417                     {
418                         // HTTP/0.9
419                         _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null);
420                         _state=STATE_END;
421                         _handler.headerComplete();
422                         _handler.messageComplete(_contentPosition);
423                         return 1;
424                     }
425                     break;
426 
427                 case STATE_SPACE2:
428                     if (ch > HttpTokens.SPACE || ch<0)
429                     {
430                         _buffer.mark();
431                         _state=STATE_FIELD2;
432                     }
433                     else if (ch < HttpTokens.SPACE)
434                     {
435                         if (_responseStatus>0)
436                         {
437                             _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
438                             _eol=ch;
439                             _state=STATE_HEADER;
440                             _tok0.setPutIndex(_tok0.getIndex());
441                             _tok1.setPutIndex(_tok1.getIndex());
442                             _multiLineValue=null;
443                         }
444                         else
445                         {
446                             // HTTP/0.9
447                             _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null);
448                             _state=STATE_END;
449                             _handler.headerComplete();
450                             _handler.messageComplete(_contentPosition);
451                             return 1;
452                         }
453                     }
454                     break;
455 
456                 case STATE_FIELD2:
457                     if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
458                     {
459                         if (_responseStatus>0)
460                             _handler.startResponse(HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark());
461                         else
462                             _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, HttpVersions.CACHE.lookup(_buffer.sliceFromMark()));
463                         _eol=ch;
464                         _state=STATE_HEADER;
465                         _tok0.setPutIndex(_tok0.getIndex());
466                         _tok1.setPutIndex(_tok1.getIndex());
467                         _multiLineValue=null;
468                         continue;
469                     }
470                     break;
471 
472                 case STATE_HEADER:
473                     switch(ch)
474                     {
475                         case HttpTokens.COLON:
476                         case HttpTokens.SPACE:
477                         case HttpTokens.TAB:
478                         {
479                             // header value without name - continuation?
480                             _length=-1;
481                             _state=STATE_HEADER_VALUE;
482                             break;
483                         }
484 
485                         default:
486                         {
487                             // handler last header if any
488                             if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null)
489                             {
490 
491                                 Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0);
492                                 _cached=null;
493                                 Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue);
494 
495                                 int ho=HttpHeaders.CACHE.getOrdinal(header);
496                                 if (ho >= 0)
497                                 {
498                                     int vo;
499 
500                                     switch (ho)
501                                     {
502                                         case HttpHeaders.CONTENT_LENGTH_ORDINAL:
503                                             if (_contentLength != HttpTokens.CHUNKED_CONTENT && _responseStatus!=304 && _responseStatus!=204 && (_responseStatus<100 || _responseStatus>=200))
504                                             {
505                                                 try
506                                                 {
507                                                     _contentLength=BufferUtil.toLong(value);
508                                                 }
509                                                 catch(NumberFormatException e)
510                                                 {
511                                                     LOG.ignore(e);
512                                                     throw new HttpException(HttpStatus.BAD_REQUEST_400);
513                                                 }
514                                                 if (_contentLength <= 0)
515                                                     _contentLength=HttpTokens.NO_CONTENT;
516                                             }
517                                             break;
518 
519                                         case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
520                                             value=HttpHeaderValues.CACHE.lookup(value);
521                                             vo=HttpHeaderValues.CACHE.getOrdinal(value);
522                                             if (HttpHeaderValues.CHUNKED_ORDINAL == vo)
523                                                 _contentLength=HttpTokens.CHUNKED_CONTENT;
524                                             else
525                                             {
526                                                 String c=value.toString(StringUtil.__ISO_8859_1);
527                                                 if (c.endsWith(HttpHeaderValues.CHUNKED))
528                                                     _contentLength=HttpTokens.CHUNKED_CONTENT;
529 
530                                                 else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0)
531                                                     throw new HttpException(400,null);
532                                             }
533                                             break;
534                                     }
535                                 }
536 
537                                 _handler.parsedHeader(header, value);
538                                 _tok0.setPutIndex(_tok0.getIndex());
539                                 _tok1.setPutIndex(_tok1.getIndex());
540                                 _multiLineValue=null;
541                             }
542                             _buffer.setMarkIndex(-1);
543 
544 
545                             // now handle ch
546                             if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
547                             {
548                                 // work out the _content demarcation
549                                 if (_contentLength == HttpTokens.UNKNOWN_CONTENT)
550                                 {
551                                     if (_responseStatus == 0  // request
552                                     || _responseStatus == 304 // not-modified response
553                                     || _responseStatus == 204 // no-content response
554                                     || _responseStatus < 200) // 1xx response
555                                         _contentLength=HttpTokens.NO_CONTENT;
556                                     else
557                                         _contentLength=HttpTokens.EOF_CONTENT;
558                                 }
559 
560                                 _contentPosition=0;
561                                 _eol=ch;
562                                 if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
563                                     _eol=_buffer.get();
564 
565                                 // We convert _contentLength to an int for this switch statement because
566                                 // we don't care about the amount of data available just whether there is some.
567                                 switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength)
568                                 {
569                                     case HttpTokens.EOF_CONTENT:
570                                         _state=STATE_EOF_CONTENT;
571                                         _handler.headerComplete(); // May recurse here !
572                                         break;
573 
574                                     case HttpTokens.CHUNKED_CONTENT:
575                                         _state=STATE_CHUNKED_CONTENT;
576                                         _handler.headerComplete(); // May recurse here !
577                                         break;
578 
579                                     case HttpTokens.NO_CONTENT:
580                                         _state=STATE_END;
581                                         returnBuffers();
582                                         _handler.headerComplete();
583                                         _handler.messageComplete(_contentPosition);
584                                         break;
585 
586                                     default:
587                                         _state=STATE_CONTENT;
588                                         _handler.headerComplete(); // May recurse here !
589                                         break;
590                                 }
591                                 return 1;
592                             }
593                             else
594                             {
595                                 // New header
596                                 _length=1;
597                                 _buffer.mark();
598                                 _state=STATE_HEADER_NAME;
599 
600                                 // try cached name!
601                                 if (array!=null)
602                                 {
603                                     _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1);
604 
605                                     if (_cached!=null)
606                                     {
607                                         _length=_cached.length();
608                                         _buffer.setGetIndex(_buffer.markIndex()+_length);
609                                         length=_buffer.length();
610                                     }
611                                 }
612                             }
613                         }
614                     }
615 
616                     break;
617 
618                 case STATE_HEADER_NAME:
619                     switch(ch)
620                     {
621                         case HttpTokens.CARRIAGE_RETURN:
622                         case HttpTokens.LINE_FEED:
623                             if (_length > 0)
624                                 _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
625                             _eol=ch;
626                             _state=STATE_HEADER;
627                             break;
628                         case HttpTokens.COLON:
629                             if (_length > 0 && _cached==null)
630                                 _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
631                             _length=-1;
632                             _state=STATE_HEADER_VALUE;
633                             break;
634                         case HttpTokens.SPACE:
635                         case HttpTokens.TAB:
636                             break;
637                         default:
638                         {
639                             _cached=null;
640                             if (_length == -1)
641                                 _buffer.mark();
642                             _length=_buffer.getIndex() - _buffer.markIndex();
643                             _state=STATE_HEADER_IN_NAME;
644                         }
645                     }
646 
647                     break;
648 
649                 case STATE_HEADER_IN_NAME:
650                     switch(ch)
651                     {
652                         case HttpTokens.CARRIAGE_RETURN:
653                         case HttpTokens.LINE_FEED:
654                             if (_length > 0)
655                                 _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
656                             _eol=ch;
657                             _state=STATE_HEADER;
658                             break;
659                         case HttpTokens.COLON:
660                             if (_length > 0 && _cached==null)
661                                 _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
662                             _length=-1;
663                             _state=STATE_HEADER_VALUE;
664                             break;
665                         case HttpTokens.SPACE:
666                         case HttpTokens.TAB:
667                             _state=STATE_HEADER_NAME;
668                             break;
669                         default:
670                         {
671                             _cached=null;
672                             _length++;
673                         }
674                     }
675                     break;
676 
677                 case STATE_HEADER_VALUE:
678                     switch(ch)
679                     {
680                         case HttpTokens.CARRIAGE_RETURN:
681                         case HttpTokens.LINE_FEED:
682                             if (_length > 0)
683                             {
684                                 if (_tok1.length() == 0)
685                                     _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
686                                 else
687                                 {
688                                     // Continuation line!
689                                     if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1);
690                                     _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
691                                     _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1);
692                                 }
693                             }
694                             _eol=ch;
695                             _state=STATE_HEADER;
696                             break;
697                         case HttpTokens.SPACE:
698                         case HttpTokens.TAB:
699                             break;
700                         default:
701                         {
702                             if (_length == -1)
703                                 _buffer.mark();
704                             _length=_buffer.getIndex() - _buffer.markIndex();
705                             _state=STATE_HEADER_IN_VALUE;
706                         }
707                     }
708                     break;
709 
710                 case STATE_HEADER_IN_VALUE:
711                     switch(ch)
712                     {
713                         case HttpTokens.CARRIAGE_RETURN:
714                         case HttpTokens.LINE_FEED:
715                             if (_length > 0)
716                             {
717                                 if (_tok1.length() == 0)
718                                     _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
719                                 else
720                                 {
721                                     // Continuation line!
722                                     if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1);
723                                     _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
724                                     _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1);
725                                 }
726                             }
727                             _eol=ch;
728                             _state=STATE_HEADER;
729                             break;
730                         case HttpTokens.SPACE:
731                         case HttpTokens.TAB:
732                             _state=STATE_HEADER_VALUE;
733                             break;
734                         default:
735                             _length++;
736                     }
737                     break;
738             }
739         } // end of HEADER states loop
740 
741         // ==========================
742 
743         // Handle HEAD response
744         if (_responseStatus>0 && _headResponse)
745         {
746             _state=STATE_END;
747             _handler.messageComplete(_contentLength);
748         }
749 
750         // ==========================
751 
752         // Handle _content
753         length=_buffer.length();
754         Buffer chunk;
755         last=_state;
756         while (_state > STATE_END && length > 0)
757         {
758             if (last!=_state)
759             {
760                 progress++;
761                 last=_state;
762             }
763 
764             if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED)
765             {
766                 _eol=_buffer.get();
767                 length=_buffer.length();
768                 continue;
769             }
770             _eol=0;
771             switch (_state)
772             {
773                 case STATE_EOF_CONTENT:
774                     chunk=_buffer.get(_buffer.length());
775                     _contentPosition += chunk.length();
776                     _contentView.update(chunk);
777                     _handler.content(chunk); // May recurse here
778                     // TODO adjust the _buffer to keep unconsumed content
779                     return 1;
780 
781                 case STATE_CONTENT:
782                 {
783                     long remaining=_contentLength - _contentPosition;
784                     if (remaining == 0)
785                     {
786                         _state=STATE_END;
787                         _handler.messageComplete(_contentPosition);
788                         return 1;
789                     }
790 
791                     if (length > remaining)
792                     {
793                         // We can cast reamining to an int as we know that it is smaller than
794                         // or equal to length which is already an int.
795                         length=(int)remaining;
796                     }
797 
798                     chunk=_buffer.get(length);
799                     _contentPosition += chunk.length();
800                     _contentView.update(chunk);
801                     _handler.content(chunk); // May recurse here
802 
803                     if(_contentPosition == _contentLength)
804                     {
805                         _state=STATE_END;
806                         _handler.messageComplete(_contentPosition);
807                     }
808                     // TODO adjust the _buffer to keep unconsumed content
809                     return 1;
810                 }
811 
812                 case STATE_CHUNKED_CONTENT:
813                 {
814                     ch=_buffer.peek();
815                     if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
816                         _eol=_buffer.get();
817                     else if (ch <= HttpTokens.SPACE)
818                         _buffer.get();
819                     else
820                     {
821                         _chunkLength=0;
822                         _chunkPosition=0;
823                         _state=STATE_CHUNK_SIZE;
824                     }
825                     break;
826                 }
827 
828                 case STATE_CHUNK_SIZE:
829                 {
830                     ch=_buffer.get();
831                     if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
832                     {
833                         _eol=ch;
834 
835                         if (_chunkLength == 0)
836                         {
837                             if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
838                                 _eol=_buffer.get();
839                             _state=STATE_END;
840                             _handler.messageComplete(_contentPosition);
841                             return 1;
842                         }
843                         else
844                             _state=STATE_CHUNK;
845                     }
846                     else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
847                         _state=STATE_CHUNK_PARAMS;
848                     else if (ch >= '0' && ch <= '9')
849                         _chunkLength=_chunkLength * 16 + (ch - '0');
850                     else if (ch >= 'a' && ch <= 'f')
851                         _chunkLength=_chunkLength * 16 + (10 + ch - 'a');
852                     else if (ch >= 'A' && ch <= 'F')
853                         _chunkLength=_chunkLength * 16 + (10 + ch - 'A');
854                     else
855                         throw new IOException("bad chunk char: " + ch);
856                     break;
857                 }
858 
859                 case STATE_CHUNK_PARAMS:
860                 {
861                     ch=_buffer.get();
862                     if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
863                     {
864                         _eol=ch;
865                         if (_chunkLength == 0)
866                         {
867                             if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
868                                 _eol=_buffer.get();
869                             _state=STATE_END;
870                             _handler.messageComplete(_contentPosition);
871                             return 1;
872                         }
873                         else
874                             _state=STATE_CHUNK;
875                     }
876                     break;
877                 }
878 
879                 case STATE_CHUNK:
880                 {
881                     int remaining=_chunkLength - _chunkPosition;
882                     if (remaining == 0)
883                     {
884                         _state=STATE_CHUNKED_CONTENT;
885                         break;
886                     }
887                     else if (length > remaining)
888                         length=remaining;
889                     chunk=_buffer.get(length);
890                     _contentPosition += chunk.length();
891                     _chunkPosition += chunk.length();
892                     _contentView.update(chunk);
893                     _handler.content(chunk); // May recurse here
894                     // TODO adjust the _buffer to keep unconsumed content
895                     return 1;
896                 }
897             }
898 
899             length=_buffer.length();
900         }
901 
902         return progress;
903     }
904 
905     /* ------------------------------------------------------------------------------- */
906     /** fill the buffers from the endpoint
907      *
908      */
909     public long fill() throws IOException
910     {
911         // Do we have a buffer?
912         if (_buffer==null)
913         {
914             _buffer=_header=getHeaderBuffer();
915             _tok0=new View.CaseInsensitive(_buffer);
916             _tok1=new View.CaseInsensitive(_buffer);
917         }
918 
919         // Is there unconsumed content in body buffer
920         if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent())
921         {
922             _buffer=_body;
923             return _buffer.length();
924         }
925 
926         // Shall we switch to a body buffer?
927         if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null))
928         {
929             if (_body==null)
930                 _body=_buffers.getBuffer();
931             _buffer=_body;
932         }
933 
934         // Do we have somewhere to fill from?
935         if (_endp != null )
936         {
937             // Shall we compact the body?
938             if (_buffer==_body || _state>STATE_END)
939             {
940                 _buffer.compact();
941             }
942 
943             // Are we full?
944             if (_buffer.space() == 0)
945                 throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "FULL "+(_buffer==_body?"body":"head"));
946 
947             try
948             {
949                 return _endp.fill(_buffer);
950             }
951             catch(IOException e)
952             {
953                 LOG.debug(e);
954                 throw (e instanceof EofException) ? e:new EofException(e);
955             }
956         }
957 
958         return -1;
959     }
960 
961     /* ------------------------------------------------------------------------------- */
962     /** Skip any CRLFs in buffers
963      *
964      */
965     public void skipCRLF()
966     {
967 
968         while (_header!=null && _header.length()>0)
969         {
970             byte ch = _header.peek();
971             if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED)
972             {
973                 _eol=ch;
974                 _header.skip(1);
975             }
976             else
977                 break;
978         }
979 
980         while (_body!=null && _body.length()>0)
981         {
982             byte ch = _body.peek();
983             if (ch==HttpTokens.CARRIAGE_RETURN || ch==HttpTokens.LINE_FEED)
984             {
985                 _eol=ch;
986                 _body.skip(1);
987             }
988             else
989                 break;
990         }
991 
992     }
993 
994     /* ------------------------------------------------------------------------------- */
995     public void reset()
996     {
997         // reset state
998         _contentView.setGetIndex(_contentView.putIndex());
999         _state=STATE_START;
1000         _contentLength=HttpTokens.UNKNOWN_CONTENT;
1001         _contentPosition=0;
1002         _length=0;
1003         _responseStatus=0;
1004 
1005         // Consume LF if CRLF
1006         if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer!=null && _buffer.hasContent() && _buffer.peek() == HttpTokens.LINE_FEED)
1007             _eol=_buffer.get();
1008 
1009         if (_body!=null && _body.hasContent())
1010         {
1011             // There is content in the body after the end of the request.
1012             // This is probably a pipelined header of the next request, so we need to
1013             // copy it to the header buffer.
1014             if (_header==null)
1015             {
1016                 _header=_buffers.getHeader();
1017             }
1018             else
1019             {
1020                 _header.setMarkIndex(-1);
1021                 _header.compact();
1022             }
1023             int take=_header.space();
1024             if (take>_body.length())
1025                 take=_body.length();
1026             _body.peek(_body.getIndex(),take);
1027             _body.skip(_header.put(_body.peek(_body.getIndex(),take)));
1028         }
1029 
1030         if (_header!=null)
1031         {
1032             _header.setMarkIndex(-1);
1033             _header.compact();
1034         }
1035         if (_body!=null)
1036             _body.setMarkIndex(-1);
1037 
1038         _buffer=_header;
1039     }
1040 
1041 
1042     /* ------------------------------------------------------------------------------- */
1043     public void returnBuffers()
1044     {
1045         if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null)
1046         {
1047             if (_buffer==_body)
1048                 _buffer=_header;
1049             if (_buffers!=null)
1050                 _buffers.returnBuffer(_body);
1051             _body=null;
1052         }
1053 
1054         if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null)
1055         {
1056             if (_buffer==_header)
1057                 _buffer=null;
1058             _buffers.returnBuffer(_header);
1059             _header=null;
1060         }
1061     }
1062 
1063     /* ------------------------------------------------------------------------------- */
1064     public void setState(int state)
1065     {
1066         this._state=state;
1067         _contentLength=HttpTokens.UNKNOWN_CONTENT;
1068     }
1069 
1070     /* ------------------------------------------------------------------------------- */
1071     public String toString(Buffer buf)
1072     {
1073         return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode();
1074     }
1075 
1076     /* ------------------------------------------------------------------------------- */
1077     @Override
1078     public String toString()
1079     {
1080         return "state=" + _state + " length=" + _length + " len=" + _contentLength;
1081     }
1082 
1083     /* ------------------------------------------------------------ */
1084     public Buffer getHeaderBuffer()
1085     {
1086         if (_header == null)
1087         {
1088             _header=_buffers.getHeader();
1089         }
1090         return _header;
1091     }
1092 
1093     /* ------------------------------------------------------------ */
1094     public Buffer getBodyBuffer()
1095     {
1096         return _body;
1097     }
1098 
1099     /* ------------------------------------------------------------ */
1100     /**
1101      * @param force True if a new buffer will be forced to be used for content and the header buffer will not be used.
1102      */
1103     public void setForceContentBuffer(boolean force)
1104     {
1105         _forceContentBuffer=force;
1106     }
1107 
1108     /* ------------------------------------------------------------ */
1109     public Buffer blockForContent(long maxIdleTime) throws IOException
1110     {
1111         if (_contentView.length()>0)
1112             return _contentView;
1113         if (getState() <= HttpParser.STATE_END)
1114             return null;
1115 
1116         try
1117         {
1118             parseNext();
1119 
1120             // parse until some progress is made (or IOException thrown for timeout)
1121             while(_contentView.length() == 0 && !isState(HttpParser.STATE_END) && _endp!=null && _endp.isOpen())
1122             {
1123                 if (!_endp.isBlocking())
1124                 {
1125                     if (_endp.isBufferingInput() && parseNext()>0)
1126                         continue;
1127 
1128                     if (!_endp.blockReadable(maxIdleTime))
1129                     {
1130                         _endp.close();
1131                         throw new EofException("timeout");
1132                     }
1133                 }
1134 
1135                 parseNext();
1136             }
1137         }
1138         catch(IOException e)
1139         {
1140             _endp.close();
1141             throw e;
1142         }
1143 
1144         return _contentView.length()>0?_contentView:null;
1145     }
1146 
1147     /* ------------------------------------------------------------ */
1148     /* (non-Javadoc)
1149      * @see java.io.InputStream#available()
1150      */
1151     public int available() throws IOException
1152     {
1153         if (_contentView!=null && _contentView.length()>0)
1154             return _contentView.length();
1155 
1156         if (_endp.isBlocking())
1157         {
1158             if (_state>0 && _endp instanceof StreamEndPoint)
1159                 return ((StreamEndPoint)_endp).getInputStream().available()>0?1:0;
1160 
1161             return 0;
1162         }
1163 
1164         parseNext();
1165         return _contentView==null?0:_contentView.length();
1166     }
1167 
1168     /* ------------------------------------------------------------ */
1169     /* ------------------------------------------------------------ */
1170     /* ------------------------------------------------------------ */
1171     public static abstract class EventHandler
1172     {
1173         public abstract void content(Buffer ref) throws IOException;
1174 
1175         public void headerComplete() throws IOException
1176         {
1177         }
1178 
1179         public void messageComplete(long contentLength) throws IOException
1180         {
1181         }
1182 
1183         /**
1184          * This is the method called by parser when a HTTP Header name and value is found
1185          */
1186         public void parsedHeader(Buffer name, Buffer value) throws IOException
1187         {
1188         }
1189 
1190         /**
1191          * This is the method called by parser when the HTTP request line is parsed
1192          */
1193         public abstract void startRequest(Buffer method, Buffer url, Buffer version)
1194                 throws IOException;
1195 
1196         /**
1197          * This is the method called by parser when the HTTP request line is parsed
1198          */
1199         public abstract void startResponse(Buffer version, int status, Buffer reason)
1200                 throws IOException;
1201     }
1202 
1203 
1204 
1205 
1206 }