View Javadoc

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