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