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