1   //========================================================================
2   //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $
3   //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty;
17  
18  import java.io.IOException;
19  import java.util.Iterator;
20  
21  import org.mortbay.io.Buffer;
22  import org.mortbay.io.BufferUtil;
23  import org.mortbay.io.Buffers;
24  import org.mortbay.io.EndPoint;
25  import org.mortbay.io.Portable;
26  import org.mortbay.io.BufferCache.CachedBuffer;
27  import org.mortbay.log.Log;
28  
29  /* ------------------------------------------------------------ */
30  /**
31   * HttpGenerator. Builds HTTP Messages.
32   * 
33   * @author gregw
34   * 
35   */
36  public class HttpGenerator extends AbstractGenerator
37  {
38      // common _content
39      private static byte[] LAST_CHUNK =
40      { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
41      private static byte[] CONTENT_LENGTH_0 = Portable.getBytes("Content-Length: 0\015\012");
42      private static byte[] CONNECTION_KEEP_ALIVE = Portable.getBytes("Connection: keep-alive\015\012");
43      private static byte[] CONNECTION_CLOSE = Portable.getBytes("Connection: close\015\012");
44      private static byte[] CONNECTION_ = Portable.getBytes("Connection: ");
45      private static byte[] CRLF = Portable.getBytes("\015\012");
46      private static byte[] TRANSFER_ENCODING_CHUNKED = Portable.getBytes("Transfer-Encoding: chunked\015\012");
47      private static byte[] SERVER = Portable.getBytes("Server: Jetty(7.0.x)\015\012");
48  
49      // other statics
50      private static int CHUNK_SPACE = 12;
51      
52      public static void setServerVersion(String version)
53      {
54          SERVER=Portable.getBytes("Server: Jetty("+version+")\015\012");
55      }
56  
57      // data
58      private boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
59      private boolean _needCRLF = false;
60      private boolean _needEOC = false;
61      private boolean _bufferChunked = false;
62  
63      
64      /* ------------------------------------------------------------------------------- */
65      /**
66       * Constructor.
67       * 
68       * @param buffers buffer pool
69       * @param headerBufferSize Size of the buffer to allocate for HTTP header
70       * @param contentBufferSize Size of the buffer to allocate for HTTP content
71       */
72      public HttpGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
73      {
74          super(buffers,io,headerBufferSize,contentBufferSize);
75      }
76  
77      /* ------------------------------------------------------------------------------- */
78      public void reset(boolean returnBuffers)
79      {
80          super.reset(returnBuffers);
81          _bypass = false;
82          _needCRLF = false;
83          _needEOC = false;
84          _bufferChunked=false;
85          _method=null;
86          _uri=null;
87          _noContent=false;
88      }
89  
90  
91  
92      /* ------------------------------------------------------------ */
93      /**
94       * Add content.
95       * 
96       * @param content
97       * @param last
98       * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
99       * @throws IllegalStateException If the request is not expecting any more content,
100      *   or if the buffers are full and cannot be flushed.
101      * @throws IOException if there is a problem flushing the buffers.
102      */
103     public void addContent(Buffer content, boolean last) throws IOException
104     {
105         if (_noContent)
106         {
107             content.clear();
108             return;
109         }
110 
111         if (_last || _state==STATE_END) 
112         {
113             Log.debug("Ignoring extra content {}",content);
114             content.clear();
115             return;
116         }
117         _last = last;
118 
119         // Handle any unfinished business?
120         if (_content!=null && _content.length()>0 || _bufferChunked)
121         {
122             if (!_endp.isOpen())
123                 throw new EofException();
124             flush();
125             if (_content != null && _content.length()>0 || _bufferChunked) 
126                 throw new IllegalStateException("FULL");
127         }
128 
129         _content = content;
130         _contentWritten += content.length();
131 
132         // Handle the _content
133         if (_head)
134         {
135             content.clear();
136             _content=null;
137         }
138         else if (_endp != null && _buffer == null && content.length() > 0 && _last)
139         {
140             // TODO - use bypass in more cases.
141             // Make _content a direct buffer
142             _bypass = true;
143         }
144         else
145         {
146             // Yes - so we better check we have a buffer
147             if (_buffer == null) 
148                 _buffer = _buffers.getBuffer(_contentBufferSize);
149 
150             // Copy _content to buffer;
151             int len=_buffer.put(_content);
152             _content.skip(len);
153             if (_content.length() == 0) 
154                 _content = null;
155         }
156     }
157     
158     /* ------------------------------------------------------------ */
159     /**
160      * Add content.
161      * 
162      * @param b byte
163      * @return true if the buffers are full
164      * @throws IOException
165      */
166     public boolean addContent(byte b) throws IOException
167     {
168         if (_noContent)
169             return false;
170         
171         if (_last || _state==STATE_END) 
172         {
173             Log.debug("Ignoring extra content {}",new Byte(b));
174             return false;
175         }
176 
177         // Handle any unfinished business?
178         if (_content != null && _content.length()>0 || _bufferChunked)
179         {
180             flush();
181             if (_content != null && _content.length()>0 || _bufferChunked) 
182                 throw new IllegalStateException("FULL");
183         }
184 
185         _contentWritten++;
186         
187         // Handle the _content
188         if (_head)
189             return false;
190         
191         // we better check we have a buffer
192         if (_buffer == null) 
193             _buffer = _buffers.getBuffer(_contentBufferSize);
194         
195         // Copy _content to buffer;
196         _buffer.put(b);
197         
198         return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
199     }
200 
201     /* ------------------------------------------------------------ */
202     /** Prepare buffer for unchecked writes.
203      * Prepare the generator buffer to receive unchecked writes
204      * @return the available space in the buffer.
205      * @throws IOException
206      */
207     protected int prepareUncheckedAddContent() throws IOException
208     {
209         if (_noContent)
210             return -1;
211         
212         if (_last || _state==STATE_END) 
213             return -1;
214 
215         // Handle any unfinished business?
216         Buffer content = _content;
217         if (content != null && content.length()>0 || _bufferChunked)
218         {
219             flush();
220             if (content != null && content.length()>0 || _bufferChunked) 
221                 throw new IllegalStateException("FULL");
222         }
223 
224         // we better check we have a buffer
225         if (_buffer == null) 
226             _buffer = _buffers.getBuffer(_contentBufferSize);
227 
228         _contentWritten-=_buffer.length();
229         
230         // Handle the _content
231         if (_head)
232             return Integer.MAX_VALUE;
233         
234         return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
235     }
236     
237     /* ------------------------------------------------------------ */
238     public boolean isBufferFull()
239     {
240         // Should we flush the buffers?
241         boolean full = super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
242         return full;
243     }
244     
245     /* ------------------------------------------------------------ */
246     public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
247     {
248         if (_state != STATE_HEADER) 
249             return;
250         
251         // handle a reset 
252         if (_method==null && _status==0)
253             throw new EofException();
254 
255         if (_last && !allContentAdded) 
256             throw new IllegalStateException("last?");
257         _last = _last | allContentAdded;
258 
259         // get a header buffer
260         if (_header == null) 
261             _header = _buffers.getBuffer(_headerBufferSize);
262         
263         boolean has_server = false;
264         
265         if (_method!=null)
266         {
267             _close = false;
268             // Request
269             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
270             {
271                 _contentLength = HttpTokens.NO_CONTENT;
272                 _header.put(_method);
273                 _header.put((byte)' ');
274                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
275                 _header.put(HttpTokens.CRLF);
276                 _state = STATE_FLUSHING;
277                 _noContent=true;
278                 return;
279             }
280             else
281             {
282                 _header.put(_method);
283                 _header.put((byte)' ');
284                 _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
285                 _header.put((byte)' ');
286                 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
287                 _header.put(HttpTokens.CRLF);
288             }
289         }
290         else
291         {
292             // Response
293             if (_version == HttpVersions.HTTP_0_9_ORDINAL)
294             {
295                 _close = true;
296                 _contentLength = HttpTokens.EOF_CONTENT;
297                 _state = STATE_CONTENT;
298                 return;
299             }
300             else
301             {
302                 if (_version == HttpVersions.HTTP_1_0_ORDINAL) 
303                     _close = true;
304 
305                 // add response line
306                 Buffer line = HttpStatus.getResponseLine(_status);
307 
308                 
309                 if (line==null)
310                 {
311                     if (_reason==null)
312                         _reason=getReasonBuffer(_status);
313 
314                     _header.put(HttpVersions.HTTP_1_1_BUFFER);
315                     _header.put((byte) ' ');
316                     _header.put((byte) ('0' + _status / 100));
317                     _header.put((byte) ('0' + (_status % 100) / 10));
318                     _header.put((byte) ('0' + (_status % 10)));
319                     _header.put((byte) ' ');
320                     if (_reason==null)
321                     {
322                         _header.put((byte) ('0' + _status / 100));
323                         _header.put((byte) ('0' + (_status % 100) / 10));
324                         _header.put((byte) ('0' + (_status % 10)));
325                     }
326                     else
327                         _header.put(_reason);
328                     _header.put(HttpTokens.CRLF);
329                 }
330                 else
331                 {
332                     if (_reason==null)
333                         _header.put(line);
334                     else
335                     {
336                         _header.put(line.array(), 0, HttpVersions.HTTP_1_1_BUFFER.length() + 5);
337                         _header.put(_reason);
338                         _header.put(HttpTokens.CRLF);
339                     }
340                 }
341 
342                 if (_status<200 && _status>=100 )
343                 {
344                     _noContent=true;
345                     _content=null;
346                     if (_buffer!=null)
347                         _buffer.clear();
348                     // end the header.
349                     _header.put(HttpTokens.CRLF);
350                     _state = STATE_CONTENT;
351                     return;
352                 }
353 
354                 if (_status==204 || _status==304)
355                 {
356                     _noContent=true;
357                     _content=null;
358                     if (_buffer!=null)
359                         _buffer.clear();
360                 }
361             }
362         }
363         
364         // Add headers
365 
366         // key field values
367         HttpFields.Field content_length = null;
368         HttpFields.Field transfer_encoding = null;
369         boolean keep_alive = false;
370         boolean close=false;
371         StringBuilder connection = null;
372 
373         if (fields != null)
374         {
375             Iterator iter = fields.getFields();
376 
377             while (iter.hasNext())
378             {
379                 HttpFields.Field field = (HttpFields.Field) iter.next();
380 
381                 switch (field.getNameOrdinal())
382                 {
383                     case HttpHeaders.CONTENT_LENGTH_ORDINAL:
384                         content_length = field;
385                         _contentLength = field.getLongValue();
386 
387                         if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
388                             content_length = null;
389 
390                         // write the field to the header buffer
391                         field.put(_header);
392                         break;
393 
394                     case HttpHeaders.CONTENT_TYPE_ORDINAL:
395                         if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
396 
397                         // write the field to the header buffer
398                         field.put(_header);
399                         break;
400 
401                     case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
402                         if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
403                         // Do NOT add yet!
404                         break;
405 
406                     case HttpHeaders.CONNECTION_ORDINAL:
407                         if (_method!=null)
408                             field.put(_header);
409                         
410                         int connection_value = field.getValueOrdinal();
411                         switch (connection_value)
412                         {
413                             case -1:
414                             { 
415                                 String[] values = field.getValue().split(",");
416                                 for  (int i=0;values!=null && i<values.length;i++)
417                                 {
418                                     CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
419 
420                                     if (cb!=null)
421                                     {
422                                         switch(cb.getOrdinal())
423                                         {
424                                             case HttpHeaderValues.CLOSE_ORDINAL:
425                                                 close=true;
426                                                 if (_method==null)
427                                                     _close=true;
428                                                 keep_alive=false;
429                                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
430                                                     _contentLength = HttpTokens.EOF_CONTENT;
431                                                 break;
432 
433                                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
434                                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
435                                                 {
436                                                     keep_alive = true;
437                                                     if (_method==null) 
438                                                         _close = false;
439                                                 }
440                                                 break;
441                                             
442                                             default:
443                                                 if (connection==null)
444                                                     connection=new StringBuilder();
445                                                 else
446                                                     connection.append(',');
447                                                 connection.append(values[i]);
448                                         }
449                                     }
450                                     else
451                                     {
452                                         if (connection==null)
453                                             connection=new StringBuilder();
454                                         else
455                                             connection.append(',');
456                                         connection.append(values[i]);
457                                     }
458                                 }
459                                 
460                                 break;
461                             }
462                             case HttpHeaderValues.CLOSE_ORDINAL:
463                             {
464                                 close=true;
465                                 if (_method==null)
466                                     _close=true;
467                                 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT) 
468                                     _contentLength = HttpTokens.EOF_CONTENT;
469                                 break;
470                             }
471                             case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
472                             {
473                                 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
474                                 {
475                                     keep_alive = true;
476                                     if (_method==null) 
477                                         _close = false;
478                                 }
479                                 break;
480                             }
481                             default:
482                             {
483                                 if (connection==null)
484                                     connection=new StringBuilder();
485                                 else
486                                     connection.append(',');
487                                 connection.append(field.getValue());
488                             }
489                         }
490 
491                         // Do NOT add yet!
492                         break;
493 
494                     case HttpHeaders.SERVER_ORDINAL:
495                         if (getSendServerVersion()) 
496                         {
497                             has_server=true;
498                             field.put(_header);
499                         }
500                         break;
501 
502                     default:
503                         // write the field to the header buffer
504                         field.put(_header);
505                 }
506             }
507         }
508 
509         // Calculate how to end _content and connection, _content length and transfer encoding
510         // settings.
511         // From RFC 2616 4.4:
512         // 1. No body for 1xx, 204, 304 & HEAD response
513         // 2. Force _content-length?
514         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
515         // 4. Content-Length
516         // 5. multipart/byteranges
517         // 6. close
518         switch ((int) _contentLength)
519         {
520             case HttpTokens.UNKNOWN_CONTENT:
521                 // It may be that we have no _content, or perhaps _content just has not been
522                 // written yet?
523 
524                 // Response known not to have a body
525                 if (_contentWritten == 0 && (_status < 200 || _status == 204 || _status == 304))
526                     _contentLength = HttpTokens.NO_CONTENT;
527                 else if (_last)
528                 {
529                     // we have seen all the _content there is
530                     _contentLength = _contentWritten;
531                     if (content_length == null)
532                     {
533                         // known length but not actually set.
534                         _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
535                         _header.put(HttpTokens.COLON);
536                         _header.put((byte) ' ');
537                         BufferUtil.putDecLong(_header, _contentLength);
538                         _header.put(HttpTokens.CRLF);
539                     }
540                 }
541                 else
542                 {
543                     // No idea, so we must assume that a body is coming
544                     _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
545                     if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
546                         throw new IllegalStateException("No Content-Length");
547                 }
548                 break;
549 
550             case HttpTokens.NO_CONTENT:
551                 if (content_length == null && _status >= 200 && _status != 204 && _status != 304) _header.put(CONTENT_LENGTH_0);
552                 break;
553 
554             case HttpTokens.EOF_CONTENT:
555                 _close = _method==null;
556                 break;
557 
558             case HttpTokens.CHUNKED_CONTENT:
559                 break;
560 
561             default:
562                 // TODO - maybe allow forced chunking by setting te ???
563                 break;
564         }
565 
566         // Add transfer_encoding if needed
567         if (_contentLength == HttpTokens.CHUNKED_CONTENT)
568         {
569             // try to use user supplied encoding as it may have other values.
570             if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
571             {
572                 String c = transfer_encoding.getValue();
573                 if (c.endsWith(HttpHeaderValues.CHUNKED))
574                     transfer_encoding.put(_header);
575                 else
576                     throw new IllegalArgumentException("BAD TE");
577             }
578             else
579                 _header.put(TRANSFER_ENCODING_CHUNKED);
580         }
581 
582         // Handle connection if need be
583         if (_contentLength==HttpTokens.EOF_CONTENT)
584         {
585             keep_alive=false;
586             _close=true;
587         }
588                
589         if (_method==null)
590         {
591             if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
592             {
593                 _header.put(CONNECTION_CLOSE);
594                 if (connection!=null)
595                 {
596                     _header.setPutIndex(_header.putIndex()-2);
597                     _header.put((byte)',');
598                     _header.put(connection.toString().getBytes());
599                     _header.put(CRLF);
600                 }
601             }
602             else if (keep_alive)
603             {
604                 _header.put(CONNECTION_KEEP_ALIVE);
605                 if (connection!=null)
606                 {
607                     _header.setPutIndex(_header.putIndex()-2);
608                     _header.put((byte)',');
609                     _header.put(connection.toString().getBytes());
610                     _header.put(CRLF);
611                 }
612             }
613             else if (connection!=null)
614             {
615                 _header.put(CONNECTION_);
616                 _header.put(connection.toString().getBytes());
617                 _header.put(CRLF);
618             }
619         }
620         
621         if (!has_server && _status>100 && getSendServerVersion())
622             _header.put(SERVER);
623 
624         // end the header.
625         _header.put(HttpTokens.CRLF);
626 
627         _state = STATE_CONTENT;
628 
629     }
630 
631     /* ------------------------------------------------------------ */
632     /**
633      * Complete the message.
634      * 
635      * @throws IOException
636      */
637     public void complete() throws IOException
638     {
639         if (_state == STATE_END) 
640             return;
641         
642         super.complete();
643         
644         if (_state < STATE_FLUSHING)
645         {
646             _state = STATE_FLUSHING;
647             if (_contentLength == HttpTokens.CHUNKED_CONTENT) 
648                 _needEOC = true;
649         }
650         
651         flush();
652     }
653 
654     /* ------------------------------------------------------------ */
655     public long flush() throws IOException
656     {
657         try
658         {   
659             if (_state == STATE_HEADER) 
660                 throw new IllegalStateException("State==HEADER");
661             
662             prepareBuffers();
663             
664             if (_endp == null)
665             {
666                 if (_needCRLF && _buffer!=null) 
667                     _buffer.put(HttpTokens.CRLF);
668                 if (_needEOC && _buffer!=null && !_head) 
669                     _buffer.put(LAST_CHUNK);
670                 _needCRLF=false;
671                 _needEOC=false;
672                 return 0;
673             }
674             
675             // Keep flushing while there is something to flush (except break below)
676             int total= 0;
677             long last_len = -1;
678             Flushing: while (true)
679             {
680                 int len = -1;
681                 int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0);
682                 switch (to_flush)
683                 {
684                     case 7:
685                         throw new IllegalStateException(); // should never happen!
686                     case 6:
687                         len = _endp.flush(_header, _buffer, null);
688                         break;
689                     case 5:
690                         len = _endp.flush(_header, _content, null);
691                         break;
692                     case 4:
693                         len = _endp.flush(_header);
694                         break;
695                     case 3:
696                         throw new IllegalStateException(); // should never happen!
697                     case 2:
698                         len = _endp.flush(_buffer);
699                         break;
700                     case 1:
701                         len = _endp.flush(_content);
702                         break;
703                     case 0:
704                     {
705                         // Nothing more we can write now.
706                         if (_header != null) 
707                             _header.clear();
708                         
709                         _bypass = false;
710                         _bufferChunked = false;
711                         
712                         if (_buffer != null)
713                         {
714                             _buffer.clear();
715                             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
716                             {
717                                 // reserve some space for the chunk header
718                                 _buffer.setPutIndex(CHUNK_SPACE);
719                                 _buffer.setGetIndex(CHUNK_SPACE);
720                                 
721                                 // Special case handling for small left over buffer from
722                                 // an addContent that caused a buffer flush.
723                                 if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
724                                 {
725                                     _buffer.put(_content);
726                                     _content.clear();
727                                     _content = null;
728                                     break Flushing;
729                                 }
730                             }
731                         }
732                         
733                         // Are we completely finished for now?
734                         if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
735                         {
736                             if (_state == STATE_FLUSHING)
737                                 _state = STATE_END;
738                             if (_state==STATE_END && _close && _status!=100) 
739                                 _endp.close();
740                             
741                             break Flushing;
742                         }
743                         
744                         // Try to prepare more to write.
745                         prepareBuffers();
746                     }
747                 }
748                 
749                 // If we failed to flush anything twice in a row break
750                 if (len <= 0)
751                 {
752                     if (last_len <= 0) 
753                         break Flushing;
754                     break;
755                 }
756                 last_len = len;
757                 total+=len;
758             }
759             
760             return total;
761         }
762         catch (IOException e)
763         {
764             Log.ignore(e);
765             throw (e instanceof EofException) ? e:new EofException(e);
766         }
767     }
768 
769     /* ------------------------------------------------------------ */
770     private void prepareBuffers()
771     {
772         // if we are not flushing an existing chunk
773         if (!_bufferChunked)
774         {
775             // Refill buffer if possible
776             if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
777             {
778                 int len = _buffer.put(_content);
779                 _content.skip(len);
780                 if (_content.length() == 0) 
781                     _content = null;
782             }
783 
784             // Chunk buffer if need be
785             if (_contentLength == HttpTokens.CHUNKED_CONTENT)
786             {
787                 int size = _buffer == null ? 0 : _buffer.length();
788                 if (size > 0)
789                 {
790                     // Prepare a chunk!
791                     _bufferChunked = true;
792 
793                     // Did we leave space at the start of the buffer.
794                     if (_buffer.getIndex() == CHUNK_SPACE)
795                     {
796                         // Oh yes, goodie! let's use it then!
797                         _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
798                         _buffer.setGetIndex(_buffer.getIndex() - 2);
799                         BufferUtil.prependHexInt(_buffer, size);
800 
801                         if (_needCRLF)
802                         {
803                             _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
804                             _buffer.setGetIndex(_buffer.getIndex() - 2);
805                             _needCRLF = false;
806                         }
807                     }
808                     else
809                     {
810                         // No space so lets use the header buffer.
811                         if (_needCRLF)
812                         {
813                             if (_header.length() > 0) throw new IllegalStateException("EOC");
814                             _header.put(HttpTokens.CRLF);
815                             _needCRLF = false;
816                         }
817                         BufferUtil.putHexInt(_header, size);
818                         _header.put(HttpTokens.CRLF);
819                     }
820 
821                     // Add end chunk trailer.
822                     if (_buffer.space() >= 2)
823                         _buffer.put(HttpTokens.CRLF);
824                     else
825                         _needCRLF = true;
826                 }
827 
828                 // If we need EOC and everything written
829                 if (_needEOC && (_content == null || _content.length() == 0))
830                 {
831                     if (_needCRLF)
832                     {
833                         if (_buffer == null && _header.space() >= 2)
834                         {
835                             _header.put(HttpTokens.CRLF);
836                             _needCRLF = false;
837                         }
838                         else if (_buffer!=null && _buffer.space() >= 2)
839                         {
840                             _buffer.put(HttpTokens.CRLF);
841                             _needCRLF = false;
842                         }
843                     }
844 
845                     if (!_needCRLF && _needEOC)
846                     {
847                         if (_buffer == null && _header.space() >= LAST_CHUNK.length)
848                         {
849                             if (!_head)
850                             {
851                                 _header.put(LAST_CHUNK);
852                                 _bufferChunked=true;
853                             }
854                             _needEOC = false;
855                         }
856                         else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
857                         {
858                             if (!_head)
859                             {
860                                 _buffer.put(LAST_CHUNK);
861                                 _bufferChunked=true;
862                             }
863                             _needEOC = false;
864                         }
865                     }
866                 }
867             }
868         }
869 
870         if (_content != null && _content.length() == 0) 
871             _content = null;
872 
873     }
874 
875 
876 }