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