View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.http;
20  
21  import java.io.IOException;
22  import java.nio.BufferOverflowException;
23  import java.nio.ByteBuffer;
24  import java.util.Arrays;
25  
26  import org.eclipse.jetty.http.HttpTokens.EndOfContent;
27  import org.eclipse.jetty.util.BufferUtil;
28  import org.eclipse.jetty.util.StringUtil;
29  import org.eclipse.jetty.util.log.Log;
30  import org.eclipse.jetty.util.log.Logger;
31  
32  /**
33   * HttpGenerator. Builds HTTP Messages.
34   * <p>
35   * If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
36   * then the generator will strictly pass on the exact strings received from methods and header
37   * fields.  Otherwise a fast case insensitive string lookup is used that may alter the
38   * case and white space of some methods/headers
39   */
40  public class HttpGenerator
41  {
42      private final static Logger LOG = Log.getLogger(HttpGenerator.class);
43  
44      public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
45  
46      private final static byte[] __colon_space = new byte[] {':',' '};
47      private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
48      public static final MetaData.Response CONTINUE_100_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,100,null,null,-1);
49      public static final MetaData.Response PROGRESS_102_INFO = new MetaData.Response(HttpVersion.HTTP_1_1,102,null,null,-1);
50      public final static MetaData.Response RESPONSE_500_INFO =
51          new MetaData.Response(HttpVersion.HTTP_1_1,HttpStatus.INTERNAL_SERVER_ERROR_500,null,new HttpFields(){{put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);}},0);
52  
53      // states
54      public enum State { START, COMMITTED, COMPLETING, COMPLETING_1XX, END }
55      public enum Result { NEED_CHUNK,NEED_INFO,NEED_HEADER,FLUSH,CONTINUE,SHUTDOWN_OUT,DONE}
56  
57      // other statics
58      public static final int CHUNK_SIZE = 12;
59  
60      private State _state = State.START;
61      private EndOfContent _endOfContent = EndOfContent.UNKNOWN_CONTENT;
62  
63      private long _contentPrepared = 0;
64      private boolean _noContent = false;
65      private Boolean _persistent = null;
66  
67      private final int _send;
68      private final static int SEND_SERVER = 0x01;
69      private final static int SEND_XPOWEREDBY = 0x02;
70  
71  
72      /* ------------------------------------------------------------------------------- */
73      public static void setJettyVersion(String serverVersion)
74      {
75          SEND[SEND_SERVER] = StringUtil.getBytes("Server: " + serverVersion + "\015\012");
76          SEND[SEND_XPOWEREDBY] = StringUtil.getBytes("X-Powered-By: " + serverVersion + "\015\012");
77          SEND[SEND_SERVER | SEND_XPOWEREDBY] = StringUtil.getBytes("Server: " + serverVersion + "\015\012X-Powered-By: " +
78                  serverVersion + "\015\012");
79      }
80  
81      /* ------------------------------------------------------------------------------- */
82      // data
83      private boolean _needCRLF = false;
84  
85      /* ------------------------------------------------------------------------------- */
86      public HttpGenerator()
87      {
88          this(false,false);
89      }
90  
91      /* ------------------------------------------------------------------------------- */
92      public HttpGenerator(boolean sendServerVersion,boolean sendXPoweredBy)
93      {
94          _send=(sendServerVersion?SEND_SERVER:0) | (sendXPoweredBy?SEND_XPOWEREDBY:0);
95      }
96  
97      /* ------------------------------------------------------------------------------- */
98      public void reset()
99      {
100         _state = State.START;
101         _endOfContent = EndOfContent.UNKNOWN_CONTENT;
102         _noContent=false;
103         _persistent = null;
104         _contentPrepared = 0;
105         _needCRLF = false;
106     }
107 
108     /* ------------------------------------------------------------ */
109     @Deprecated
110     public boolean getSendServerVersion ()
111     {
112         return (_send&SEND_SERVER)!=0;
113     }
114 
115     /* ------------------------------------------------------------ */
116     @Deprecated
117     public void setSendServerVersion (boolean sendServerVersion)
118     {
119         throw new UnsupportedOperationException();
120     }
121 
122     /* ------------------------------------------------------------ */
123     public State getState()
124     {
125         return _state;
126     }
127 
128     /* ------------------------------------------------------------ */
129     public boolean isState(State state)
130     {
131         return _state == state;
132     }
133 
134     /* ------------------------------------------------------------ */
135     public boolean isIdle()
136     {
137         return _state == State.START;
138     }
139 
140     /* ------------------------------------------------------------ */
141     public boolean isEnd()
142     {
143         return _state == State.END;
144     }
145 
146     /* ------------------------------------------------------------ */
147     public boolean isCommitted()
148     {
149         return _state.ordinal() >= State.COMMITTED.ordinal();
150     }
151 
152     /* ------------------------------------------------------------ */
153     public boolean isChunking()
154     {
155         return _endOfContent==EndOfContent.CHUNKED_CONTENT;
156     }
157 
158     /* ------------------------------------------------------------ */
159     public boolean isNoContent()
160     {
161         return _noContent;
162     }
163 
164     /* ------------------------------------------------------------ */
165     public void setPersistent(boolean persistent)
166     {
167         _persistent=persistent;
168     }
169 
170     /* ------------------------------------------------------------ */
171     /**
172      * @return true if known to be persistent
173      */
174     public boolean isPersistent()
175     {
176         return Boolean.TRUE.equals(_persistent);
177     }
178 
179     /* ------------------------------------------------------------ */
180     public boolean isWritten()
181     {
182         return _contentPrepared>0;
183     }
184 
185     /* ------------------------------------------------------------ */
186     public long getContentPrepared()
187     {
188         return _contentPrepared;
189     }
190 
191     /* ------------------------------------------------------------ */
192     public void abort()
193     {
194         _persistent=false;
195         _state=State.END;
196         _endOfContent=null;
197     }
198 
199     /* ------------------------------------------------------------ */
200     public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
201     {
202         switch(_state)
203         {
204             case START:
205             {
206                 if (info==null)
207                     return Result.NEED_INFO;
208 
209                 // Do we need a request header
210                 if (header==null)
211                     return Result.NEED_HEADER;
212 
213                 // If we have not been told our persistence, set the default
214                 if (_persistent==null)
215                 {
216                     _persistent=info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal();
217                     if (!_persistent && HttpMethod.CONNECT.is(info.getMethod()))
218                         _persistent=true;
219                 }
220 
221                 // prepare the header
222                 int pos=BufferUtil.flipToFill(header);
223                 try
224                 {
225                     // generate ResponseLine
226                     generateRequestLine(info,header);
227 
228                     if (info.getVersion()==HttpVersion.HTTP_0_9)
229                         _noContent=true;
230                     else
231                         generateHeaders(info,header,content,last);
232 
233                     boolean expect100 = info.getFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
234 
235                     if (expect100)
236                     {
237                         _state = State.COMMITTED;
238                     }
239                     else
240                     {
241                         // handle the content.
242                         int len = BufferUtil.length(content);
243                         if (len>0)
244                         {
245                             _contentPrepared+=len;
246                             if (isChunking())
247                                 prepareChunk(header,len);
248                         }
249                         _state = last?State.COMPLETING:State.COMMITTED;
250                     }
251 
252                     return Result.FLUSH;
253                 }
254                 catch(Exception e)
255                 {
256                     String message= (e instanceof BufferOverflowException)?"Request header too large":e.getMessage();
257                     throw new IOException(message,e);
258                 }
259                 finally
260                 {
261                     BufferUtil.flipToFlush(header,pos);
262                 }
263             }
264 
265             case COMMITTED:
266             {
267                 int len = BufferUtil.length(content);
268 
269                 if (len>0)
270                 {
271                     // Do we need a chunk buffer?
272                     if (isChunking())
273                     {
274                         // Do we need a chunk buffer?
275                         if (chunk==null)
276                             return Result.NEED_CHUNK;
277                         BufferUtil.clearToFill(chunk);
278                         prepareChunk(chunk,len);
279                         BufferUtil.flipToFlush(chunk,0);
280                     }
281                     _contentPrepared+=len;
282                 }
283 
284                 if (last)
285                     _state=State.COMPLETING;
286 
287                 return len>0?Result.FLUSH:Result.CONTINUE;
288             }
289 
290             case COMPLETING:
291             {
292                 if (BufferUtil.hasContent(content))
293                 {
294                     if (LOG.isDebugEnabled())
295                         LOG.debug("discarding content in COMPLETING");
296                     BufferUtil.clear(content);
297                 }
298 
299                 if (isChunking())
300                 {
301                     // Do we need a chunk buffer?
302                     if (chunk==null)
303                         return Result.NEED_CHUNK;
304                     BufferUtil.clearToFill(chunk);
305                     prepareChunk(chunk,0);
306                     BufferUtil.flipToFlush(chunk,0);
307                     _endOfContent=EndOfContent.UNKNOWN_CONTENT;
308                     return Result.FLUSH;
309                 }
310 
311                 _state=State.END;
312                return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
313             }
314 
315             case END:
316                 if (BufferUtil.hasContent(content))
317                 {
318                     if (LOG.isDebugEnabled())
319                         LOG.debug("discarding content in COMPLETING");
320                     BufferUtil.clear(content);
321                 }
322                 return Result.DONE;
323 
324             default:
325                 throw new IllegalStateException();
326         }
327     }
328 
329     /* ------------------------------------------------------------ */
330     public Result generateResponse(MetaData.Response info, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
331     {
332         return generateResponse(info,false,header,chunk,content,last);
333     }
334 
335     /* ------------------------------------------------------------ */
336     public Result generateResponse(MetaData.Response info, boolean head, ByteBuffer header, ByteBuffer chunk, ByteBuffer content, boolean last) throws IOException
337     {
338         switch(_state)
339         {
340             case START:
341             {
342                 if (info==null)
343                     return Result.NEED_INFO;
344 
345                 // Handle 0.9
346                 if (info.getVersion() == HttpVersion.HTTP_0_9)
347                 {
348                     _persistent = false;
349                     _endOfContent=EndOfContent.EOF_CONTENT;
350                     if (BufferUtil.hasContent(content))
351                         _contentPrepared+=content.remaining();
352                     _state = last?State.COMPLETING:State.COMMITTED;
353                     return Result.FLUSH;
354                 }
355 
356                 // Do we need a response header
357                 if (header==null)
358                     return Result.NEED_HEADER;
359 
360                 // If we have not been told our persistence, set the default
361                 if (_persistent==null)
362                     _persistent=(info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal());
363 
364                 // prepare the header
365                 int pos=BufferUtil.flipToFill(header);
366                 try
367                 {
368                     // generate ResponseLine
369                     generateResponseLine(info,header);
370 
371                     // Handle 1xx and no content responses
372                     int status=info.getStatus();
373                     if (status>=100 && status<200 )
374                     {
375                         _noContent=true;
376 
377                         if (status!=HttpStatus.SWITCHING_PROTOCOLS_101 )
378                         {
379                             header.put(HttpTokens.CRLF);
380                             _state=State.COMPLETING_1XX;
381                             return Result.FLUSH;
382                         }
383                     }
384                     else if (status==HttpStatus.NO_CONTENT_204 || status==HttpStatus.NOT_MODIFIED_304)
385                     {
386                         _noContent=true;
387                     }
388 
389                     generateHeaders(info,header,content,last);
390 
391                     // handle the content.
392                     int len = BufferUtil.length(content);
393                     if (len>0)
394                     {
395                         _contentPrepared+=len;
396                         if (isChunking() && !head)
397                             prepareChunk(header,len);
398                     }
399                     _state = last?State.COMPLETING:State.COMMITTED;
400                 }
401                 catch(Exception e)
402                 {
403                     String message= (e instanceof BufferOverflowException)?"Response header too large":e.getMessage();
404                     throw new IOException(message,e);
405                 }
406                 finally
407                 {
408                     BufferUtil.flipToFlush(header,pos);
409                 }
410 
411                 return Result.FLUSH;
412             }
413 
414             case COMMITTED:
415             {
416                 int len = BufferUtil.length(content);
417 
418                 // handle the content.
419                 if (len>0)
420                 {
421                     if (isChunking())
422                     {
423                         if (chunk==null)
424                             return Result.NEED_CHUNK;
425                         BufferUtil.clearToFill(chunk);
426                         prepareChunk(chunk,len);
427                         BufferUtil.flipToFlush(chunk,0);
428                     }
429                     _contentPrepared+=len;
430                 }
431 
432                 if (last)
433                 {
434                     _state=State.COMPLETING;
435                     return len>0?Result.FLUSH:Result.CONTINUE;
436                 }
437                 return len>0?Result.FLUSH:Result.DONE;
438 
439             }
440 
441             case COMPLETING_1XX:
442             {
443                 reset();
444                 return Result.DONE;
445             }
446 
447             case COMPLETING:
448             {
449                 if (BufferUtil.hasContent(content))
450                 {
451                     if (LOG.isDebugEnabled())
452                         LOG.debug("discarding content in COMPLETING");
453                     BufferUtil.clear(content);
454                 }
455 
456                 if (isChunking())
457                 {
458                     // Do we need a chunk buffer?
459                     if (chunk==null)
460                         return Result.NEED_CHUNK;
461 
462                     // Write the last chunk
463                     BufferUtil.clearToFill(chunk);
464                     prepareChunk(chunk,0);
465                     BufferUtil.flipToFlush(chunk,0);
466                     _endOfContent=EndOfContent.UNKNOWN_CONTENT;
467                     return Result.FLUSH;
468                 }
469 
470                 _state=State.END;
471 
472                return Boolean.TRUE.equals(_persistent)?Result.DONE:Result.SHUTDOWN_OUT;
473             }
474 
475             case END:
476                 if (BufferUtil.hasContent(content))
477                 {
478                     if (LOG.isDebugEnabled())
479                         LOG.debug("discarding content in COMPLETING");
480                     BufferUtil.clear(content);
481                 }
482                 return Result.DONE;
483 
484             default:
485                 throw new IllegalStateException();
486         }
487     }
488 
489     /* ------------------------------------------------------------ */
490     private void prepareChunk(ByteBuffer chunk, int remaining)
491     {
492         // if we need CRLF add this to header
493         if (_needCRLF)
494             BufferUtil.putCRLF(chunk);
495 
496         // Add the chunk size to the header
497         if (remaining>0)
498         {
499             BufferUtil.putHexInt(chunk, remaining);
500             BufferUtil.putCRLF(chunk);
501             _needCRLF=true;
502         }
503         else
504         {
505             chunk.put(LAST_CHUNK);
506             _needCRLF=false;
507         }
508     }
509 
510     /* ------------------------------------------------------------ */
511     private void generateRequestLine(MetaData.Request request,ByteBuffer header)
512     {
513         header.put(StringUtil.getBytes(request.getMethod()));
514         header.put((byte)' ');
515         header.put(StringUtil.getBytes(request.getURIString()));
516         switch(request.getVersion())
517         {
518             case HTTP_1_0:
519             case HTTP_1_1:
520                 header.put((byte)' ');
521                 header.put(request.getVersion().toBytes());
522                 break;
523             default:
524                 throw new IllegalStateException();
525         }
526         header.put(HttpTokens.CRLF);
527     }
528 
529     /* ------------------------------------------------------------ */
530     private void generateResponseLine(MetaData.Response response, ByteBuffer header)
531     {
532         // Look for prepared response line
533         int status=response.getStatus();
534         PreparedResponse preprepared = status<__preprepared.length?__preprepared[status]:null;
535         String reason=response.getReason();
536         if (preprepared!=null)
537         {
538             if (reason==null)
539                 header.put(preprepared._responseLine);
540             else
541             {
542                 header.put(preprepared._schemeCode);
543                 header.put(getReasonBytes(reason));
544                 header.put(HttpTokens.CRLF);
545             }
546         }
547         else // generate response line
548         {
549             header.put(HTTP_1_1_SPACE);
550             header.put((byte) ('0' + status / 100));
551             header.put((byte) ('0' + (status % 100) / 10));
552             header.put((byte) ('0' + (status % 10)));
553             header.put((byte) ' ');
554             if (reason==null)
555             {
556                 header.put((byte) ('0' + status / 100));
557                 header.put((byte) ('0' + (status % 100) / 10));
558                 header.put((byte) ('0' + (status % 10)));
559             }
560             else
561                 header.put(getReasonBytes(reason));
562             header.put(HttpTokens.CRLF);
563         }
564     }
565 
566     /* ------------------------------------------------------------ */
567     private byte[] getReasonBytes(String reason)
568     {
569         if (reason.length()>1024)
570             reason=reason.substring(0,1024);
571         byte[] _bytes = StringUtil.getBytes(reason);
572 
573         for (int i=_bytes.length;i-->0;)
574             if (_bytes[i]=='\r' || _bytes[i]=='\n')
575                 _bytes[i]='?';
576         return _bytes;
577     }
578 
579     /* ------------------------------------------------------------ */
580     private void generateHeaders(MetaData _info,ByteBuffer header,ByteBuffer content,boolean last)
581     {
582         final MetaData.Request request=(_info instanceof MetaData.Request)?(MetaData.Request)_info:null;
583         final MetaData.Response response=(_info instanceof MetaData.Response)?(MetaData.Response)_info:null;
584 
585         // default field values
586         int send=_send;
587         HttpField transfer_encoding=null;
588         boolean keep_alive=false;
589         boolean close=false;
590         boolean content_type=false;
591         StringBuilder connection = null;
592 
593         // Generate fields
594         if (_info.getFields() != null)
595         {
596             for (HttpField field : _info.getFields())
597             {
598                 String v = field.getValue();
599                 if (v==null || v.length()==0)
600                     continue; // rfc7230 does not allow no value
601 
602                 HttpHeader h = field.getHeader();
603 
604                 switch (h==null?HttpHeader.UNKNOWN:h)
605                 {
606                     case CONTENT_LENGTH:
607                         // handle specially below
608                         if (_info.getContentLength()>=0)
609                             _endOfContent=EndOfContent.CONTENT_LENGTH;
610                         break;
611 
612                     case CONTENT_TYPE:
613                     {
614                         if (field.getValue().startsWith(MimeTypes.Type.MULTIPART_BYTERANGES.toString()))
615                             _endOfContent=EndOfContent.SELF_DEFINING_CONTENT;
616 
617                         // write the field to the header
618                         content_type=true;
619                         putTo(field,header);
620                         break;
621                     }
622 
623                     case TRANSFER_ENCODING:
624                     {
625                         if (_info.getVersion() == HttpVersion.HTTP_1_1)
626                             transfer_encoding = field;
627                         // Do NOT add yet!
628                         break;
629                     }
630 
631                     case CONNECTION:
632                     {
633                         if (request!=null)
634                             putTo(field,header);
635 
636                         // Lookup and/or split connection value field
637                         HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
638                         String[] split = null;
639 
640                         if (values[0]==null)
641                         {
642                             split = StringUtil.csvSplit(field.getValue());
643                             if (split.length>0)
644                             {
645                                 values=new HttpHeaderValue[split.length];
646                                 for (int i=0;i<split.length;i++)
647                                     values[i]=HttpHeaderValue.CACHE.get(split[i]);
648                             }
649                         }
650 
651                         // Handle connection values
652                         for (int i=0;i<values.length;i++)
653                         {
654                             HttpHeaderValue value=values[i];
655                             switch (value==null?HttpHeaderValue.UNKNOWN:value)
656                             {
657                                 case UPGRADE:
658                                 {
659                                     // special case for websocket connection ordering
660                                     header.put(HttpHeader.CONNECTION.getBytesColonSpace()).put(HttpHeader.UPGRADE.getBytes());
661                                     header.put(CRLF);
662                                     break;
663                                 }
664 
665                                 case CLOSE:
666                                 {
667                                     close=true;
668                                     _persistent=false;
669                                     if (response!=null)
670                                     {
671                                         if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
672                                             _endOfContent=EndOfContent.EOF_CONTENT;
673                                     }
674                                     break;
675                                 }
676 
677                                 case KEEP_ALIVE:
678                                 {
679                                     if (_info.getVersion() == HttpVersion.HTTP_1_0)
680                                     {
681                                         keep_alive = true;
682                                         if (response!=null)
683                                             _persistent=true;
684                                     }
685                                     break;
686                                 }
687 
688                                 default:
689                                 {
690                                     if (connection==null)
691                                         connection=new StringBuilder();
692                                     else
693                                         connection.append(',');
694                                     connection.append(split==null?field.getValue():split[i]);
695                                 }
696                             }
697                         }
698 
699                         // Do NOT add yet!
700                         break;
701                     }
702 
703                     case SERVER:
704                     {
705                         send=send&~SEND_SERVER;
706                         putTo(field,header);
707                         break;
708                     }
709 
710                     default:
711                         putTo(field,header);
712                 }
713             }
714         }
715 
716 
717         // Calculate how to end _content and connection, _content length and transfer encoding
718         // settings.
719         // From RFC 2616 4.4:
720         // 1. No body for 1xx, 204, 304 & HEAD response
721         // 2. Force _content-length?
722         // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
723         // 4. Content-Length
724         // 5. multipart/byteranges
725         // 6. close
726         int status=response!=null?response.getStatus():-1;
727         switch (_endOfContent)
728         {
729             case UNKNOWN_CONTENT:
730                 // It may be that we have no _content, or perhaps _content just has not been
731                 // written yet?
732 
733                 // Response known not to have a body
734                 if (_contentPrepared == 0 && response!=null && (status < 200 || status == 204 || status == 304))
735                     _endOfContent=EndOfContent.NO_CONTENT;
736                 else if (_info.getContentLength()>0)
737                 {
738                     // we have been given a content length
739                     _endOfContent=EndOfContent.CONTENT_LENGTH;
740                     long content_length = _info.getContentLength();
741                     if ((response!=null || content_length>0 || content_type ) && !_noContent)
742                     {
743                         // known length but not actually set.
744                         header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
745                         BufferUtil.putDecLong(header, content_length);
746                         header.put(HttpTokens.CRLF);
747                     }
748                 }
749                 else if (last)
750                 {
751                     // we have seen all the _content there is, so we can be content-length limited.
752                     _endOfContent=EndOfContent.CONTENT_LENGTH;
753                     long content_length = _contentPrepared+BufferUtil.length(content);
754 
755                     // Do we need to tell the headers about it
756                     if ((response!=null || content_length>0 || content_type ) && !_noContent)
757                     {
758                         header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
759                         BufferUtil.putDecLong(header, content_length);
760                         header.put(HttpTokens.CRLF);
761                     }
762                 }
763                 else
764                 {
765                     // No idea, so we must assume that a body is coming.
766                     _endOfContent = EndOfContent.CHUNKED_CONTENT;
767                     // HTTP 1.0 does not understand chunked content, so we must use EOF content.
768                     // For a request with HTTP 1.0 & Connection: keep-alive
769                     // we *must* close the connection, otherwise the client
770                     // has no way to detect the end of the content.
771                     if (!isPersistent() || _info.getVersion().ordinal() < HttpVersion.HTTP_1_1.ordinal())
772                         _endOfContent = EndOfContent.EOF_CONTENT;
773                 }
774                 break;
775 
776             case CONTENT_LENGTH:
777             {
778                 long content_length = _info.getContentLength();
779                 if ((response!=null || content_length>0 || content_type ) && !_noContent)
780                 {
781                     header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
782                     BufferUtil.putDecLong(header, content_length);
783                     header.put(HttpTokens.CRLF);
784                 }
785                 break;
786             }
787 
788             case SELF_DEFINING_CONTENT:
789             {
790                 // TODO - Should we do this? Why was it not required before?
791                 long content_length = _info.getContentLength();
792                 if (content_length>0)
793                 {
794                     header.put(HttpHeader.CONTENT_LENGTH.getBytesColonSpace());
795                     BufferUtil.putDecLong(header, content_length);
796                     header.put(HttpTokens.CRLF);
797                 }
798                 break;
799             }
800             case NO_CONTENT:
801                 if (response!=null && status >= 200 && status != 204 && status != 304)
802                     header.put(CONTENT_LENGTH_0);
803                 break;
804 
805             case EOF_CONTENT:
806                 _persistent = request!=null;
807                 break;
808 
809             case CHUNKED_CONTENT:
810                 break;
811 
812             default:
813                 break;
814         }
815 
816         // Add transfer_encoding if needed
817         if (isChunking())
818         {
819             // try to use user supplied encoding as it may have other values.
820             if (transfer_encoding != null && !HttpHeaderValue.CHUNKED.toString().equalsIgnoreCase(transfer_encoding.getValue()))
821             {
822                 String c = transfer_encoding.getValue();
823                 if (c.endsWith(HttpHeaderValue.CHUNKED.toString()))
824                     putTo(transfer_encoding,header);
825                 else
826                     throw new IllegalArgumentException("BAD TE");
827             }
828             else
829                 header.put(TRANSFER_ENCODING_CHUNKED);
830         }
831 
832         // Handle connection if need be
833         if (_endOfContent==EndOfContent.EOF_CONTENT)
834         {
835             keep_alive=false;
836             _persistent=false;
837         }
838 
839         // If this is a response, work out persistence
840         if (response!=null)
841         {
842             if (!isPersistent() && (close || _info.getVersion().ordinal() > HttpVersion.HTTP_1_0.ordinal()))
843             {
844                 if (connection==null)
845                     header.put(CONNECTION_CLOSE);
846                 else
847                 {
848                     header.put(CONNECTION_CLOSE,0,CONNECTION_CLOSE.length-2);
849                     header.put((byte)',');
850                     header.put(StringUtil.getBytes(connection.toString()));
851                     header.put(CRLF);
852                 }
853             }
854             else if (keep_alive)
855             {
856                 if (connection==null)
857                     header.put(CONNECTION_KEEP_ALIVE);
858                 else
859                 {
860                     header.put(CONNECTION_KEEP_ALIVE,0,CONNECTION_KEEP_ALIVE.length-2);
861                     header.put((byte)',');
862                     header.put(StringUtil.getBytes(connection.toString()));
863                     header.put(CRLF);
864                 }
865             }
866             else if (connection!=null)
867             {
868                 header.put(HttpHeader.CONNECTION.getBytesColonSpace());
869                 header.put(StringUtil.getBytes(connection.toString()));
870                 header.put(CRLF);
871             }
872         }
873 
874         if (status>199)
875             header.put(SEND[send]);
876 
877         // end the header.
878         header.put(HttpTokens.CRLF);
879     }
880 
881     /* ------------------------------------------------------------------------------- */
882     public static byte[] getReasonBuffer(int code)
883     {
884         PreparedResponse status = code<__preprepared.length?__preprepared[code]:null;
885         if (status!=null)
886             return status._reason;
887         return null;
888     }
889 
890     /* ------------------------------------------------------------------------------- */
891     @Override
892     public String toString()
893     {
894         return String.format("%s@%x{s=%s}",
895                 getClass().getSimpleName(),
896                 hashCode(),
897                 _state);
898     }
899 
900     /* ------------------------------------------------------------------------------- */
901     /* ------------------------------------------------------------------------------- */
902     /* ------------------------------------------------------------------------------- */
903     // common _content
904     private static final byte[] LAST_CHUNK =    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
905     private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
906     private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
907     private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
908     private static final byte[] HTTP_1_1_SPACE = StringUtil.getBytes(HttpVersion.HTTP_1_1+" ");
909     private static final byte[] CRLF = StringUtil.getBytes("\015\012");
910     private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
911     private static final byte[][] SEND = new byte[][]{
912             new byte[0],
913             StringUtil.getBytes("Server: Jetty(9.x.x)\015\012"),
914         StringUtil.getBytes("X-Powered-By: Jetty(9.x.x)\015\012"),
915         StringUtil.getBytes("Server: Jetty(9.x.x)\015\012X-Powered-By: Jetty(9.x.x)\015\012")
916     };
917 
918     /* ------------------------------------------------------------------------------- */
919     /* ------------------------------------------------------------------------------- */
920     /* ------------------------------------------------------------------------------- */
921     // Build cache of response lines for status
922     private static class PreparedResponse
923     {
924         byte[] _reason;
925         byte[] _schemeCode;
926         byte[] _responseLine;
927     }
928     private static final PreparedResponse[] __preprepared = new PreparedResponse[HttpStatus.MAX_CODE+1];
929     static
930     {
931         int versionLength=HttpVersion.HTTP_1_1.toString().length();
932 
933         for (int i=0;i<__preprepared.length;i++)
934         {
935             HttpStatus.Code code = HttpStatus.getCode(i);
936             if (code==null)
937                 continue;
938             String reason=code.getMessage();
939             byte[] line=new byte[versionLength+5+reason.length()+2];
940             HttpVersion.HTTP_1_1.toBuffer().get(line,0,versionLength);
941             line[versionLength+0]=' ';
942             line[versionLength+1]=(byte)('0'+i/100);
943             line[versionLength+2]=(byte)('0'+(i%100)/10);
944             line[versionLength+3]=(byte)('0'+(i%10));
945             line[versionLength+4]=' ';
946             for (int j=0;j<reason.length();j++)
947                 line[versionLength+5+j]=(byte)reason.charAt(j);
948             line[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
949             line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
950 
951             __preprepared[i] = new PreparedResponse();
952             __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5);
953             __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2);
954             __preprepared[i]._responseLine=line;
955         }
956     }
957 
958     private static void putSanitisedName(String s,ByteBuffer buffer)
959     {
960         int l=s.length();
961         for (int i=0;i<l;i++)
962         {
963             char c=s.charAt(i);
964 
965             if (c<0 || c>0xff || c=='\r' || c=='\n'|| c==':')
966                 buffer.put((byte)'?');
967             else
968                 buffer.put((byte)(0xff&c));
969         }
970     }
971 
972     private static void putSanitisedValue(String s,ByteBuffer buffer)
973     {
974         int l=s.length();
975         for (int i=0;i<l;i++)
976         {
977             char c=s.charAt(i);
978 
979             if (c<0 || c>0xff || c=='\r' || c=='\n')
980                 buffer.put((byte)' ');
981             else
982                 buffer.put((byte)(0xff&c));
983         }
984     }
985 
986     public static void putTo(HttpField field, ByteBuffer bufferInFillMode)
987     {
988         if (field instanceof PreEncodedHttpField)
989         {
990             ((PreEncodedHttpField)field).putTo(bufferInFillMode,HttpVersion.HTTP_1_0);
991         }
992         else
993         {
994             HttpHeader header=field.getHeader();
995             if (header!=null)
996             {
997                 bufferInFillMode.put(header.getBytesColonSpace());
998                 putSanitisedValue(field.getValue(),bufferInFillMode);
999             }
1000             else
1001             {
1002                 putSanitisedName(field.getName(),bufferInFillMode);
1003                 bufferInFillMode.put(__colon_space);
1004                 putSanitisedValue(field.getValue(),bufferInFillMode);
1005             }
1006 
1007             BufferUtil.putCRLF(bufferInFillMode);
1008         }
1009     }
1010 
1011     public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode)
1012     {
1013         for (HttpField field : fields)
1014         {
1015             if (field != null)
1016                 putTo(field,bufferInFillMode);
1017         }
1018         BufferUtil.putCRLF(bufferInFillMode);
1019     }
1020 }