View Javadoc

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