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