View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.server;
15  
16  import java.io.IOException;
17  import java.io.PrintWriter;
18  import java.util.Collections;
19  import java.util.Enumeration;
20  import java.util.Locale;
21  
22  import javax.servlet.ServletOutputStream;
23  import javax.servlet.http.Cookie;
24  import javax.servlet.http.HttpServletResponse;
25  import javax.servlet.http.HttpSession;
26  
27  import org.eclipse.jetty.http.HttpCookie;
28  import org.eclipse.jetty.http.HttpFields;
29  import org.eclipse.jetty.http.HttpGenerator;
30  import org.eclipse.jetty.http.HttpHeaderValues;
31  import org.eclipse.jetty.http.HttpHeaders;
32  import org.eclipse.jetty.http.HttpSchemes;
33  import org.eclipse.jetty.http.HttpStatus;
34  import org.eclipse.jetty.http.HttpURI;
35  import org.eclipse.jetty.http.HttpVersions;
36  import org.eclipse.jetty.http.MimeTypes;
37  import org.eclipse.jetty.io.BufferCache.CachedBuffer;
38  import org.eclipse.jetty.server.handler.ContextHandler;
39  import org.eclipse.jetty.server.handler.ErrorHandler;
40  import org.eclipse.jetty.util.ByteArrayISO8859Writer;
41  import org.eclipse.jetty.util.QuotedStringTokenizer;
42  import org.eclipse.jetty.util.StringUtil;
43  import org.eclipse.jetty.util.URIUtil;
44  import org.eclipse.jetty.util.log.Log;
45  import org.eclipse.jetty.util.log.Logger;
46  
47  /** Response.
48   * <p>
49   * Implements {@link javax.servlet.http.HttpServletResponse} from the <code>javax.servlet.http</code> package.
50   * </p>
51   */
52  public class Response implements HttpServletResponse
53  {
54      private static final Logger LOG = Log.getLogger(Response.class);
55  
56      
57      public static final int
58          NONE=0,
59          STREAM=1,
60          WRITER=2;
61  
62      /**
63       * If a header name starts with this string,  the header (stripped of the prefix)
64       * can be set during include using only {@link #setHeader(String, String)} or
65       * {@link #addHeader(String, String)}.
66       */
67      public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
68  
69      /**
70       * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie 
71       * will be set as HTTP ONLY.
72       */
73      public final static String HTTP_ONLY_COMMENT="__HTTP_ONLY__";
74      
75      private final AbstractHttpConnection _connection;
76      private int _status=SC_OK;
77      private String _reason;
78      private Locale _locale;
79      private String _mimeType;
80      private CachedBuffer _cachedMimeType;
81      private String _characterEncoding;
82      private boolean _explicitEncoding;
83      private String _contentType;
84      private int _outputState;
85      private PrintWriter _writer;
86  
87      /* ------------------------------------------------------------ */
88      /**
89       *
90       */
91      public Response(AbstractHttpConnection connection)
92      {
93          _connection=connection;
94      }
95  
96  
97      /* ------------------------------------------------------------ */
98      /*
99       * @see javax.servlet.ServletResponse#reset()
100      */
101     protected void recycle()
102     {
103         _status=SC_OK;
104         _reason=null;
105         _locale=null;
106         _mimeType=null;
107         _cachedMimeType=null;
108         _characterEncoding=null;
109         _explicitEncoding=false;
110         _contentType=null;
111         _outputState=NONE;
112         _writer=null;
113     }
114 
115     /* ------------------------------------------------------------ */
116     /*
117      * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
118      */
119     public void addCookie(HttpCookie cookie)
120     {
121         _connection.getResponseFields().addSetCookie(cookie);
122     }
123     
124     /* ------------------------------------------------------------ */
125     /*
126      * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
127      */
128     public void addCookie(Cookie cookie)
129     {
130         String comment=cookie.getComment();
131         boolean http_only=false;
132         
133         if (comment!=null)
134         {
135             int i=comment.indexOf(HTTP_ONLY_COMMENT);
136             if (i>=0)
137             {
138                 http_only=true;
139                 comment=comment.substring(i,i+HTTP_ONLY_COMMENT.length()).trim();
140                 if (comment.length()==0)
141                     comment=null;
142             }
143         }
144         _connection.getResponseFields().addSetCookie(cookie.getName(),
145                 cookie.getValue(),
146                 cookie.getDomain(),
147                 cookie.getPath(),
148                 cookie.getMaxAge(),
149                 comment,
150                 cookie.getSecure(),
151                 http_only,// || cookie.isHttpOnly(),
152                 cookie.getVersion());
153     }
154 
155     /* ------------------------------------------------------------ */
156     /*
157      * @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String)
158      */
159     public boolean containsHeader(String name)
160     {
161         return _connection.getResponseFields().containsKey(name);
162     }
163 
164     /* ------------------------------------------------------------ */
165     /*
166      * @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)
167      */
168     public String encodeURL(String url)
169     {
170         final Request request=_connection.getRequest();
171         SessionManager sessionManager = request.getSessionManager();
172         if (sessionManager==null)
173             return url;
174         
175         HttpURI uri = null;
176         if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
177         {
178             uri = new HttpURI(url);
179             String path = uri.getPath();
180             path = (path == null?"":path);
181             int port=uri.getPort();
182             if (port<0) 
183                 port = HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme())?443:80;
184             if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
185                 request.getServerPort()!=port ||
186                 !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
187                 return url;
188         }
189         
190         String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
191         if (sessionURLPrefix==null)
192             return url;
193 
194         if (url==null)
195             return null;
196         
197         // should not encode if cookies in evidence
198         if (request.isRequestedSessionIdFromCookie())
199         {
200             int prefix=url.indexOf(sessionURLPrefix);
201             if (prefix!=-1)
202             {
203                 int suffix=url.indexOf("?",prefix);
204                 if (suffix<0)
205                     suffix=url.indexOf("#",prefix);
206 
207                 if (suffix<=prefix)
208                     return url.substring(0,prefix);
209                 return url.substring(0,prefix)+url.substring(suffix);
210             }
211             return url;
212         }
213 
214         // get session;
215         HttpSession session=request.getSession(false);
216 
217         // no session
218         if (session == null)
219             return url;
220 
221         // invalid session
222         if (!sessionManager.isValid(session))
223             return url;
224 
225         String id=sessionManager.getNodeId(session);
226 
227         if (uri == null)
228                 uri = new HttpURI(url);
229      
230         
231         // Already encoded
232         int prefix=url.indexOf(sessionURLPrefix);
233         if (prefix!=-1)
234         {
235             int suffix=url.indexOf("?",prefix);
236             if (suffix<0)
237                 suffix=url.indexOf("#",prefix);
238 
239             if (suffix<=prefix)
240                 return url.substring(0,prefix+sessionURLPrefix.length())+id;
241             return url.substring(0,prefix+sessionURLPrefix.length())+id+
242                 url.substring(suffix);
243         }
244 
245         // edit the session
246         int suffix=url.indexOf('?');
247         if (suffix<0)
248             suffix=url.indexOf('#');
249         if (suffix<0) 
250         {          
251             return url+ 
252                    ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"") + //if no path, insert the root path
253                    sessionURLPrefix+id;
254         }
255      
256         
257         return url.substring(0,suffix)+
258             ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"")+ //if no path so insert the root path
259             sessionURLPrefix+id+url.substring(suffix);
260     }
261 
262     /* ------------------------------------------------------------ */
263     /**
264      * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)
265      */
266     public String encodeRedirectURL(String url)
267     {
268         return encodeURL(url);
269     }
270 
271     /* ------------------------------------------------------------ */
272     @Deprecated
273     public String encodeUrl(String url)
274     {
275         return encodeURL(url);
276     }
277 
278     /* ------------------------------------------------------------ */
279     @Deprecated
280     public String encodeRedirectUrl(String url)
281     {
282         return encodeRedirectURL(url);
283     }
284 
285     /* ------------------------------------------------------------ */
286     /*
287      * @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String)
288      */
289     public void sendError(int code, String message) throws IOException
290     {
291     	if (_connection.isIncluding())
292     		return;
293 
294         if (isCommitted())
295             LOG.warn("Committed before "+code+" "+message);
296 
297         resetBuffer();
298         _characterEncoding=null;
299         setHeader(HttpHeaders.EXPIRES,null);
300         setHeader(HttpHeaders.LAST_MODIFIED,null);
301         setHeader(HttpHeaders.CACHE_CONTROL,null);
302         setHeader(HttpHeaders.CONTENT_TYPE,null);
303         setHeader(HttpHeaders.CONTENT_LENGTH,null);
304 
305         _outputState=NONE;
306         setStatus(code,message);
307 
308         if (message==null)
309             message=HttpStatus.getMessage(code);
310 
311         // If we are allowed to have a body
312         if (code!=SC_NO_CONTENT &&
313             code!=SC_NOT_MODIFIED &&
314             code!=SC_PARTIAL_CONTENT &&
315             code>=SC_OK)
316         {
317             Request request = _connection.getRequest();
318 
319             ErrorHandler error_handler = null;
320             ContextHandler.Context context = request.getContext();
321             if (context!=null)
322                 error_handler=context.getContextHandler().getErrorHandler();
323             if (error_handler==null)
324                 error_handler = _connection.getConnector().getServer().getBean(ErrorHandler.class);
325             if (error_handler!=null)
326             {
327                 request.setAttribute(Dispatcher.ERROR_STATUS_CODE,new Integer(code));
328                 request.setAttribute(Dispatcher.ERROR_MESSAGE, message);
329                 request.setAttribute(Dispatcher.ERROR_REQUEST_URI, request.getRequestURI());
330                 request.setAttribute(Dispatcher.ERROR_SERVLET_NAME,request.getServletName());
331                 error_handler.handle(null,_connection.getRequest(),_connection.getRequest(),this );
332             }
333             else
334             {
335                 setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
336                 setContentType(MimeTypes.TEXT_HTML_8859_1);
337                 ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
338                 if (message != null)
339                 {
340                     message= StringUtil.replace(message, "&", "&amp;");
341                     message= StringUtil.replace(message, "<", "&lt;");
342                     message= StringUtil.replace(message, ">", "&gt;");
343                 }
344                 String uri= request.getRequestURI();
345                 if (uri!=null)
346                 {
347                     uri= StringUtil.replace(uri, "&", "&amp;");
348                     uri= StringUtil.replace(uri, "<", "&lt;");
349                     uri= StringUtil.replace(uri, ">", "&gt;");
350                 }
351 
352                 writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
353                 writer.write("<title>Error ");
354                 writer.write(Integer.toString(code));
355                 writer.write(' ');
356                 if (message==null)
357                     message=HttpStatus.getMessage(code);
358                 writer.write(message);
359                 writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
360                 writer.write(Integer.toString(code));
361                 writer.write("</h2>\n<p>Problem accessing ");
362                 writer.write(uri);
363                 writer.write(". Reason:\n<pre>    ");
364                 writer.write(message);
365                 writer.write("</pre>");
366                 writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
367 
368                 for (int i= 0; i < 20; i++)
369                     writer.write("\n                                                ");
370                 writer.write("\n</body>\n</html>\n");
371 
372                 writer.flush();
373                 setContentLength(writer.size());
374                 writer.writeTo(getOutputStream());
375                 writer.destroy();
376             }
377         }
378         else if (code!=SC_PARTIAL_CONTENT)
379         {
380             _connection.getRequestFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
381             _connection.getRequestFields().remove(HttpHeaders.CONTENT_LENGTH_BUFFER);
382             _characterEncoding=null;
383             _mimeType=null;
384             _cachedMimeType=null;
385         }
386 
387         complete();
388     }
389 
390     /* ------------------------------------------------------------ */
391     /*
392      * @see javax.servlet.http.HttpServletResponse#sendError(int)
393      */
394     public void sendError(int sc) throws IOException
395     {
396         if (sc==102)
397             sendProcessing();
398         else
399             sendError(sc,null);
400     }
401 
402     /* ------------------------------------------------------------ */
403     /* Send a 102-Processing response.
404      * If the connection is a HTTP connection, the version is 1.1 and the
405      * request has a Expect header starting with 102, then a 102 response is
406      * sent. This indicates that the request still be processed and real response
407      * can still be sent.   This method is called by sendError if it is passed 102.
408      * @see javax.servlet.http.HttpServletResponse#sendError(int)
409      */
410     public void sendProcessing() throws IOException
411     {
412         if (_connection.isExpecting102Processing() && !isCommitted())
413             ((HttpGenerator)_connection.getGenerator()).send1xx(HttpStatus.PROCESSING_102);
414     }
415 
416     /* ------------------------------------------------------------ */
417     /*
418      * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
419      */
420     public void sendRedirect(String location) throws IOException
421     {
422     	if (_connection.isIncluding())
423     		return;
424 
425         if (location==null)
426             throw new IllegalArgumentException();
427 
428         if (!URIUtil.hasScheme(location))
429         {
430             StringBuilder buf = _connection.getRequest().getRootURL();
431             if (location.startsWith("/"))
432                 buf.append(location);
433             else
434             {
435                 String path=_connection.getRequest().getRequestURI();
436                 String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path);
437                 location=URIUtil.addPaths(parent,location);
438                 if(location==null)
439                     throw new IllegalStateException("path cannot be above root");
440                 if (!location.startsWith("/"))
441                     buf.append('/');
442                 buf.append(location);
443             }
444 
445             location=buf.toString();
446             HttpURI uri = new HttpURI(location);
447             String path=uri.getDecodedPath();
448             String canonical=URIUtil.canonicalPath(path);
449             if (canonical==null)
450                 throw new IllegalArgumentException();
451             if (!canonical.equals(path))
452             {
453                 buf = _connection.getRequest().getRootURL();
454                 buf.append(URIUtil.encodePath(canonical));
455                 if (uri.getQuery()!=null)
456                 {
457                     buf.append('?');
458                     buf.append(uri.getQuery());
459                 }
460                 if (uri.getFragment()!=null)
461                 {
462                     buf.append('#');
463                     buf.append(uri.getFragment());
464                 }
465                 location=buf.toString();
466             }
467         }
468         
469         location=encodeRedirectURL(location);
470         resetBuffer();
471         setHeader(HttpHeaders.LOCATION,location);
472         setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
473         complete();
474 
475     }
476 
477     /* ------------------------------------------------------------ */
478     /*
479      * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
480      */
481     public void setDateHeader(String name, long date)
482     {
483         if (!_connection.isIncluding())
484             _connection.getResponseFields().putDateField(name, date);
485     }
486 
487     /* ------------------------------------------------------------ */
488     /*
489      * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
490      */
491     public void addDateHeader(String name, long date)
492     {
493         if (!_connection.isIncluding())
494             _connection.getResponseFields().addDateField(name, date);
495     }
496 
497     /* ------------------------------------------------------------ */
498     /*
499      * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
500      */
501     public void setHeader(String name, String value)
502     {
503         if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
504             setContentType(value);
505         else
506         {
507             if (_connection.isIncluding())
508             {
509                 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
510                     name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
511                 else
512                     return;
513             }
514             _connection.getResponseFields().put(name, value);
515             if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
516             {
517                 if (value==null)
518                     _connection._generator.setContentLength(-1);
519                 else
520                     _connection._generator.setContentLength(Long.parseLong(value));
521             }
522         }
523     }
524 
525     /* ------------------------------------------------------------ */
526     /*
527      */
528     public String getHeader(String name)
529     {
530         return _connection.getResponseFields().getStringField(name);
531     }
532 
533     /* ------------------------------------------------------------ */
534     /*
535      */
536     public Enumeration getHeaders(String name)
537     {
538         Enumeration e = _connection.getResponseFields().getValues(name);
539         if (e==null)
540             return Collections.enumeration(Collections.EMPTY_LIST);
541         return e;
542     }
543 
544     /* ------------------------------------------------------------ */
545     /*
546      * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
547      */
548     public void addHeader(String name, String value)
549     {
550         if (_connection.isIncluding())
551         {
552             if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
553                 name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
554             else
555                 return;
556         }
557 
558         _connection.getResponseFields().add(name, value);
559         if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
560             _connection._generator.setContentLength(Long.parseLong(value));
561     }
562 
563     /* ------------------------------------------------------------ */
564     /*
565      * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
566      */
567     public void setIntHeader(String name, int value)
568     {
569         if (!_connection.isIncluding())
570         {
571             _connection.getResponseFields().putLongField(name, value);
572             if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
573                 _connection._generator.setContentLength(value);
574         }
575     }
576 
577     /* ------------------------------------------------------------ */
578     /*
579      * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
580      */
581     public void addIntHeader(String name, int value)
582     {
583         if (!_connection.isIncluding())
584         {
585             _connection.getResponseFields().addLongField(name, value);
586             if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
587                 _connection._generator.setContentLength(value);
588         }
589     }
590 
591     /* ------------------------------------------------------------ */
592     /*
593      * @see javax.servlet.http.HttpServletResponse#setStatus(int)
594      */
595     public void setStatus(int sc)
596     {
597         setStatus(sc,null);
598     }
599 
600     /* ------------------------------------------------------------ */
601     /*
602      * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String)
603      */
604     public void setStatus(int sc, String sm)
605     {
606         if (sc<=0)
607             throw new IllegalArgumentException();
608         if (!_connection.isIncluding())
609         {
610             _status=sc;
611             _reason=sm;
612         }
613     }
614 
615     /* ------------------------------------------------------------ */
616     /*
617      * @see javax.servlet.ServletResponse#getCharacterEncoding()
618      */
619     public String getCharacterEncoding()
620     {
621         if (_characterEncoding==null)
622             _characterEncoding=StringUtil.__ISO_8859_1;
623         return _characterEncoding;
624     }
625     
626     /* ------------------------------------------------------------ */
627     String getSetCharacterEncoding()
628     {
629         return _characterEncoding;
630     }
631 
632     /* ------------------------------------------------------------ */
633     /*
634      * @see javax.servlet.ServletResponse#getContentType()
635      */
636     public String getContentType()
637     {
638         return _contentType;
639     }
640 
641     /* ------------------------------------------------------------ */
642     /*
643      * @see javax.servlet.ServletResponse#getOutputStream()
644      */
645     public ServletOutputStream getOutputStream() throws IOException
646     {
647         if (_outputState!=NONE && _outputState!=STREAM)
648             throw new IllegalStateException("WRITER");
649 
650         _outputState=STREAM;
651         return _connection.getOutputStream();
652     }
653 
654     /* ------------------------------------------------------------ */
655     public boolean isWriting()
656     {
657         return _outputState==WRITER;
658     }
659 
660     /* ------------------------------------------------------------ */
661     public boolean isOutputing()
662     {
663         return _outputState!=NONE;
664     }
665 
666     /* ------------------------------------------------------------ */
667     /*
668      * @see javax.servlet.ServletResponse#getWriter()
669      */
670     public PrintWriter getWriter() throws IOException
671     {
672         if (_outputState!=NONE && _outputState!=WRITER)
673             throw new IllegalStateException("STREAM");
674 
675         /* if there is no writer yet */
676         if (_writer==null)
677         {
678             /* get encoding from Content-Type header */
679             String encoding = _characterEncoding;
680 
681             if (encoding==null)
682             {
683                 /* implementation of educated defaults */
684                 if(_cachedMimeType != null)
685                     encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType);
686 
687                 if (encoding==null)
688                     encoding = StringUtil.__ISO_8859_1;
689 
690                 setCharacterEncoding(encoding);
691             }
692 
693             /* construct Writer using correct encoding */
694             _writer = _connection.getPrintWriter(encoding);
695         }
696         _outputState=WRITER;
697         return _writer;
698     }
699 
700     /* ------------------------------------------------------------ */
701     /*
702      * @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String)
703      */
704     public void setCharacterEncoding(String encoding)
705     {
706     	if (_connection.isIncluding())
707     		return;
708 
709         if (this._outputState==0 && !isCommitted())
710         {
711             _explicitEncoding=true;
712 
713             if (encoding==null)
714             {
715                 // Clear any encoding.
716                 if (_characterEncoding!=null)
717                 {
718                     _characterEncoding=null;
719                     if (_cachedMimeType!=null)
720                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
721                     else
722                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_mimeType);
723                 }
724             }
725             else
726             {
727                 // No, so just add this one to the mimetype
728                 _characterEncoding=encoding;
729                 if (_contentType!=null)
730                 {
731                     int i0=_contentType.indexOf(';');
732                     if (i0<0)
733                     {
734                         _contentType=null;
735                         if(_cachedMimeType!=null)
736                         {
737                             CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
738                             if (content_type!=null)
739                             {
740                                 _contentType=content_type.toString();
741                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
742                             }
743                         }
744 
745                         if (_contentType==null)
746                         {
747                             _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
748                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
749                         }
750                     }
751                     else
752                     {
753                         int i1=_contentType.indexOf("charset=",i0);
754                         if (i1<0)
755                         {
756                             _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
757                         }
758                         else
759                         {
760                             int i8=i1+8;
761                             int i2=_contentType.indexOf(" ",i8);
762                             if (i2<0)
763                                 _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
764                             else
765                                 _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2);
766                         }
767                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
768                     }
769                 }
770             }
771         }
772     }
773 
774     /* ------------------------------------------------------------ */
775     /*
776      * @see javax.servlet.ServletResponse#setContentLength(int)
777      */
778     public void setContentLength(int len)
779     {
780         // Protect from setting after committed as default handling
781         // of a servlet HEAD request ALWAYS sets _content length, even
782         // if the getHandling committed the response!
783         if (isCommitted() || _connection.isIncluding())
784             return;
785         _connection._generator.setContentLength(len);
786         if (len>=0)
787         {
788             _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
789             if (_connection._generator.isAllContentWritten())
790             {
791                 if (_outputState==WRITER)
792                     _writer.close();
793                 else if (_outputState==STREAM)
794                 {
795                     try
796                     {
797                         getOutputStream().close();
798                     }
799                     catch(IOException e)
800                     {
801                         throw new RuntimeException(e);
802                     }
803                 }
804             }
805         }
806     }
807 
808     /* ------------------------------------------------------------ */
809     /*
810      * @see javax.servlet.ServletResponse#setContentLength(int)
811      */
812     public void setLongContentLength(long len)
813     {
814         // Protect from setting after committed as default handling
815         // of a servlet HEAD request ALWAYS sets _content length, even
816         // if the getHandling committed the response!
817         if (isCommitted() || _connection.isIncluding())
818         	return;
819         _connection._generator.setContentLength(len);
820         _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
821     }
822 
823     /* ------------------------------------------------------------ */
824     /*
825      * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
826      */
827     public void setContentType(String contentType)
828     {
829         if (isCommitted() || _connection.isIncluding())
830             return;
831 
832         // Yes this method is horribly complex.... but there are lots of special cases and
833         // as this method is called on every request, it is worth trying to save string creation.
834         //
835 
836         if (contentType==null)
837         {
838             if (_locale==null)
839                 _characterEncoding=null;
840             _mimeType=null;
841             _cachedMimeType=null;
842             _contentType=null;
843             _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
844         }
845         else
846         {
847             // Look for encoding in contentType
848             int i0=contentType.indexOf(';');
849 
850             if (i0>0)
851             {
852                 // we have content type parameters
853 
854                 // Extract params off mimetype
855                 _mimeType=contentType.substring(0,i0).trim();
856                 _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
857 
858                 // Look for charset
859                 int i1=contentType.indexOf("charset=",i0+1);
860                 if (i1>=0)
861                 {
862                     _explicitEncoding=true;
863                     int i8=i1+8;
864                     int i2 = contentType.indexOf(' ',i8);
865 
866                     if (_outputState==WRITER)
867                     {
868                         // strip the charset and ignore;
869                         if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
870                         {
871                             if (_cachedMimeType!=null)
872                             {
873                                 CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
874                                 if (content_type!=null)
875                                 {
876                                     _contentType=content_type.toString();
877                                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
878                                 }
879                                 else
880                                 {
881                                     _contentType=_mimeType+";charset="+_characterEncoding;
882                                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
883                                 }
884                             }
885                             else
886                             {
887                                 _contentType=_mimeType+";charset="+_characterEncoding;
888                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
889                             }
890                         }
891                         else if (i2<0)
892                         {
893                             _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
894                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
895                         }
896                         else
897                         {
898                             _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
899                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
900                         }
901                     }
902                     else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
903                     {
904                         // The params are just the char encoding
905                         _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
906                         _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
907 
908                         if (_cachedMimeType!=null)
909                         {
910                             CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
911                             if (content_type!=null)
912                             {
913                                 _contentType=content_type.toString();
914                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
915                             }
916                             else
917                             {
918                                 _contentType=contentType;
919                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
920                             }
921                         }
922                         else
923                         {
924                             _contentType=contentType;
925                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
926                         }
927                     }
928                     else if (i2>0)
929                     {
930                         _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2));
931                         _contentType=contentType;
932                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
933                     }
934                     else
935                     {
936                         _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
937                         _contentType=contentType;
938                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
939                     }
940                 }
941                 else // No encoding in the params.
942                 {
943                     _cachedMimeType=null;
944                     _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
945                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
946                 }
947             }
948             else // No params at all
949             {
950                 _mimeType=contentType;
951                 _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
952 
953                 if (_characterEncoding!=null)
954                 {
955                     if (_cachedMimeType!=null)
956                     {
957                         CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
958                         if (content_type!=null)
959                         {
960                             _contentType=content_type.toString();
961                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
962                         }
963                         else
964                         {
965                             _contentType=_mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
966                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
967                         }
968                     }
969                     else
970                     {
971                         _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
972                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
973                     }
974                 }
975                 else if (_cachedMimeType!=null)
976                 {
977                     _contentType=_cachedMimeType.toString();
978                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
979                 }
980                 else
981                 {
982                     _contentType=contentType;
983                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
984                 }
985             }
986         }
987     }
988 
989     /* ------------------------------------------------------------ */
990     /*
991      * @see javax.servlet.ServletResponse#setBufferSize(int)
992      */
993     public void setBufferSize(int size)
994     {
995         if (isCommitted() || getContentCount()>0)
996             throw new IllegalStateException("Committed or content written");
997         _connection.getGenerator().increaseContentBufferSize(size);
998     }
999 
1000     /* ------------------------------------------------------------ */
1001     /*
1002      * @see javax.servlet.ServletResponse#getBufferSize()
1003      */
1004     public int getBufferSize()
1005     {
1006         return _connection.getGenerator().getContentBufferSize();
1007     }
1008 
1009     /* ------------------------------------------------------------ */
1010     /*
1011      * @see javax.servlet.ServletResponse#flushBuffer()
1012      */
1013     public void flushBuffer() throws IOException
1014     {
1015         _connection.flushResponse();
1016     }
1017 
1018     /* ------------------------------------------------------------ */
1019     /*
1020      * @see javax.servlet.ServletResponse#reset()
1021      */
1022     public void reset()
1023     {
1024         resetBuffer();
1025         fwdReset();
1026         _status=200;
1027         _reason=null;
1028         
1029         HttpFields response_fields=_connection.getResponseFields();
1030         
1031         response_fields.clear();
1032         String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER);
1033         if (connection!=null)
1034         {
1035             String[] values = connection.split(",");
1036             for  (int i=0;values!=null && i<values.length;i++)
1037             {
1038                 CachedBuffer cb = HttpHeaderValues.CACHE.get(values[0].trim());
1039 
1040                 if (cb!=null)
1041                 {
1042                     switch(cb.getOrdinal())
1043                     {
1044                         case HttpHeaderValues.CLOSE_ORDINAL:
1045                             response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
1046                             break;
1047 
1048                         case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
1049                             if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol()))
1050                                 response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE);
1051                             break;
1052                         case HttpHeaderValues.TE_ORDINAL:
1053                             response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.TE);
1054                             break;
1055                     }
1056                 }
1057             }
1058         }
1059     }
1060     
1061     /* ------------------------------------------------------------ */
1062     /*
1063      * @see javax.servlet.ServletResponse#reset()
1064      */
1065     public void fwdReset()
1066     {
1067         resetBuffer();
1068 
1069         _outputState=NONE;
1070         _writer=null;
1071     }
1072 
1073     /* ------------------------------------------------------------ */
1074     /*
1075      * @see javax.servlet.ServletResponse#resetBuffer()
1076      */
1077     public void resetBuffer()
1078     {
1079         if (isCommitted())
1080             throw new IllegalStateException("Committed");
1081         _connection.getGenerator().resetBuffer();
1082     }
1083 
1084     /* ------------------------------------------------------------ */
1085     /*
1086      * @see javax.servlet.ServletResponse#isCommitted()
1087      */
1088     public boolean isCommitted()
1089     {
1090         return _connection.isResponseCommitted();
1091     }
1092 
1093 
1094     /* ------------------------------------------------------------ */
1095     /*
1096      * @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
1097      */
1098     public void setLocale(Locale locale)
1099     {
1100         if (locale == null || isCommitted() ||_connection.isIncluding())
1101             return;
1102 
1103         _locale = locale;
1104         _connection.getResponseFields().put(HttpHeaders.CONTENT_LANGUAGE_BUFFER,locale.toString().replace('_','-'));
1105 
1106         if (_explicitEncoding || _outputState!=0 )
1107             return;
1108 
1109         if (_connection.getRequest().getContext()==null)
1110             return;
1111 
1112         String charset = _connection.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
1113 
1114         if (charset!=null && charset.length()>0)
1115         {
1116             _characterEncoding=charset;
1117 
1118             /* get current MIME type from Content-Type header */
1119             String type=getContentType();
1120             if (type!=null)
1121             {
1122                 _characterEncoding=charset;
1123                 int semi=type.indexOf(';');
1124                 if (semi<0)
1125                 {
1126                     _mimeType=type;
1127                     _contentType= type += ";charset="+charset;
1128                 }
1129                 else
1130                 {
1131                     _mimeType=type.substring(0,semi);
1132                     _contentType= _mimeType += ";charset="+charset;
1133                 }
1134 
1135                 _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
1136                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
1137             }
1138         }
1139     }
1140 
1141     /* ------------------------------------------------------------ */
1142     /*
1143      * @see javax.servlet.ServletResponse#getLocale()
1144      */
1145     public Locale getLocale()
1146     {
1147         if (_locale==null)
1148             return Locale.getDefault();
1149         return _locale;
1150     }
1151 
1152     /* ------------------------------------------------------------ */
1153     /**
1154      * @return The HTTP status code that has been set for this request. This will be <code>200<code>
1155      *    ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods.
1156      */
1157     public int getStatus()
1158     {
1159         return _status;
1160     }
1161 
1162     /* ------------------------------------------------------------ */
1163     /**
1164      * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>,
1165      *    unless one of the <code>setStatus</code> methods have been called.
1166      */
1167     public String getReason()
1168     {
1169         return _reason;
1170     }
1171 
1172     /* ------------------------------------------------------------ */
1173     /**
1174      */
1175     public void complete()
1176         throws IOException
1177     {
1178         _connection.completeResponse();
1179     }
1180 
1181     /* ------------------------------------------------------------- */
1182     /**
1183      * @return the number of bytes actually written in response body
1184      */
1185     public long getContentCount()
1186     {
1187         if (_connection==null || _connection.getGenerator()==null)
1188             return -1;
1189         return _connection.getGenerator().getContentWritten();
1190     }
1191 
1192     /* ------------------------------------------------------------ */
1193     public HttpFields getHttpFields()
1194     {
1195         return _connection.getResponseFields();
1196     }
1197 
1198     /* ------------------------------------------------------------ */
1199     @Override
1200     public String toString()
1201     {
1202         return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+
1203         _connection.getResponseFields().toString();
1204     }
1205     
1206     /* ------------------------------------------------------------ */
1207     /* ------------------------------------------------------------ */
1208     /* ------------------------------------------------------------ */
1209     private static class NullOutput extends ServletOutputStream
1210     {
1211         @Override
1212         public void write(int b) throws IOException
1213         {
1214         }
1215 
1216         @Override
1217         public void print(String s) throws IOException
1218         {
1219         }
1220 
1221         @Override
1222         public void println(String s) throws IOException
1223         {
1224         }
1225 
1226         @Override
1227         public void write(byte[] b, int off, int len) throws IOException
1228         {
1229         }
1230 
1231     }
1232 
1233 }