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