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 volatile 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         _writer=null;
112         _outputState=NONE;
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.replace(HTTP_ONLY_COMMENT,"").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         resetBuffer();
470         setHeader(HttpHeaders.LOCATION,location);
471         setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
472         complete();
473 
474     }
475 
476     /* ------------------------------------------------------------ */
477     /*
478      * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
479      */
480     public void setDateHeader(String name, long date)
481     {
482         if (!_connection.isIncluding())
483             _connection.getResponseFields().putDateField(name, date);
484     }
485 
486     /* ------------------------------------------------------------ */
487     /*
488      * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
489      */
490     public void addDateHeader(String name, long date)
491     {
492         if (!_connection.isIncluding())
493             _connection.getResponseFields().addDateField(name, date);
494     }
495 
496     /* ------------------------------------------------------------ */
497     /*
498      * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
499      */
500     public void setHeader(String name, String value)
501     {
502         if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
503             setContentType(value);
504         else
505         {
506             if (_connection.isIncluding())
507             {
508                 if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
509                     name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
510                 else
511                     return;
512             }
513             _connection.getResponseFields().put(name, value);
514             if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
515             {
516                 if (value==null)
517                     _connection._generator.setContentLength(-1);
518                 else
519                     _connection._generator.setContentLength(Long.parseLong(value));
520             }
521         }
522     }
523 
524     /* ------------------------------------------------------------ */
525     /*
526      */
527     public String getHeader(String name)
528     {
529         return _connection.getResponseFields().getStringField(name);
530     }
531 
532     /* ------------------------------------------------------------ */
533     /*
534      */
535     public Enumeration getHeaders(String name)
536     {
537         Enumeration e = _connection.getResponseFields().getValues(name);
538         if (e==null)
539             return Collections.enumeration(Collections.EMPTY_LIST);
540         return e;
541     }
542 
543     /* ------------------------------------------------------------ */
544     /*
545      * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
546      */
547     public void addHeader(String name, String value)
548     {
549         if (_connection.isIncluding())
550         {
551             if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
552                 name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
553             else
554                 return;
555         }
556 
557         _connection.getResponseFields().add(name, value);
558         if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
559             _connection._generator.setContentLength(Long.parseLong(value));
560     }
561 
562     /* ------------------------------------------------------------ */
563     /*
564      * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
565      */
566     public void setIntHeader(String name, int value)
567     {
568         if (!_connection.isIncluding())
569         {
570             _connection.getResponseFields().putLongField(name, value);
571             if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
572                 _connection._generator.setContentLength(value);
573         }
574     }
575 
576     /* ------------------------------------------------------------ */
577     /*
578      * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
579      */
580     public void addIntHeader(String name, int value)
581     {
582         if (!_connection.isIncluding())
583         {
584             _connection.getResponseFields().addLongField(name, value);
585             if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
586                 _connection._generator.setContentLength(value);
587         }
588     }
589 
590     /* ------------------------------------------------------------ */
591     /*
592      * @see javax.servlet.http.HttpServletResponse#setStatus(int)
593      */
594     public void setStatus(int sc)
595     {
596         setStatus(sc,null);
597     }
598 
599     /* ------------------------------------------------------------ */
600     /*
601      * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String)
602      */
603     public void setStatus(int sc, String sm)
604     {
605         if (sc<=0)
606             throw new IllegalArgumentException();
607         if (!_connection.isIncluding())
608         {
609             _status=sc;
610             _reason=sm;
611         }
612     }
613 
614     /* ------------------------------------------------------------ */
615     /*
616      * @see javax.servlet.ServletResponse#getCharacterEncoding()
617      */
618     public String getCharacterEncoding()
619     {
620         if (_characterEncoding==null)
621             _characterEncoding=StringUtil.__ISO_8859_1;
622         return _characterEncoding;
623     }
624     
625     /* ------------------------------------------------------------ */
626     String getSetCharacterEncoding()
627     {
628         return _characterEncoding;
629     }
630 
631     /* ------------------------------------------------------------ */
632     /*
633      * @see javax.servlet.ServletResponse#getContentType()
634      */
635     public String getContentType()
636     {
637         return _contentType;
638     }
639 
640     /* ------------------------------------------------------------ */
641     /*
642      * @see javax.servlet.ServletResponse#getOutputStream()
643      */
644     public ServletOutputStream getOutputStream() throws IOException
645     {
646         if (_outputState!=NONE && _outputState!=STREAM)
647             throw new IllegalStateException("WRITER");
648 
649         ServletOutputStream out = _connection.getOutputStream();
650         _outputState=STREAM;
651         return out;
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                         _contentType=_cachedMimeType.toString();
721                     else if (_mimeType!=null)
722                         _contentType=_mimeType;
723                     else
724                         _contentType=null;
725 
726                     if (_contentType==null)
727                         _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
728                     else
729                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
730                 }
731             }
732             else
733             {
734                 // No, so just add this one to the mimetype
735                 _characterEncoding=encoding;
736                 if (_contentType!=null)
737                 {
738                     int i0=_contentType.indexOf(';');
739                     if (i0<0)
740                     {
741                         _contentType=null;
742                         if(_cachedMimeType!=null)
743                         {
744                             CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
745                             if (content_type!=null)
746                             {
747                                 _contentType=content_type.toString();
748                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
749                             }
750                         }
751 
752                         if (_contentType==null)
753                         {
754                             _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
755                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
756                         }
757                     }
758                     else
759                     {
760                         int i1=_contentType.indexOf("charset=",i0);
761                         if (i1<0)
762                         {
763                             _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
764                         }
765                         else
766                         {
767                             int i8=i1+8;
768                             int i2=_contentType.indexOf(" ",i8);
769                             if (i2<0)
770                                 _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
771                             else
772                                 _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2);
773                         }
774                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
775                     }
776                 }
777             }
778         }
779     }
780 
781     /* ------------------------------------------------------------ */
782     /*
783      * @see javax.servlet.ServletResponse#setContentLength(int)
784      */
785     public void setContentLength(int len)
786     {
787         // Protect from setting after committed as default handling
788         // of a servlet HEAD request ALWAYS sets _content length, even
789         // if the getHandling committed the response!
790         if (isCommitted() || _connection.isIncluding())
791             return;
792         _connection._generator.setContentLength(len);
793         if (len>0)
794         {
795             _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
796             if (_connection._generator.isAllContentWritten())
797             {
798                 if (_outputState==WRITER)
799                     _writer.close();
800                 else if (_outputState==STREAM)
801                 {
802                     try
803                     {
804                         getOutputStream().close();
805                     }
806                     catch(IOException e)
807                     {
808                         throw new RuntimeException(e);
809                     }
810                 }
811             }
812         }
813     }
814 
815     /* ------------------------------------------------------------ */
816     /*
817      * @see javax.servlet.ServletResponse#setContentLength(int)
818      */
819     public void setLongContentLength(long len)
820     {
821         // Protect from setting after committed as default handling
822         // of a servlet HEAD request ALWAYS sets _content length, even
823         // if the getHandling committed the response!
824         if (isCommitted() || _connection.isIncluding())
825         	return;
826         _connection._generator.setContentLength(len);
827         _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
828     }
829 
830     /* ------------------------------------------------------------ */
831     /*
832      * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
833      */
834     public void setContentType(String contentType)
835     {
836         if (isCommitted() || _connection.isIncluding())
837             return;
838 
839         // Yes this method is horribly complex.... but there are lots of special cases and
840         // as this method is called on every request, it is worth trying to save string creation.
841         //
842 
843         if (contentType==null)
844         {
845             if (_locale==null)
846                 _characterEncoding=null;
847             _mimeType=null;
848             _cachedMimeType=null;
849             _contentType=null;
850             _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
851         }
852         else
853         {
854             // Look for encoding in contentType
855             int i0=contentType.indexOf(';');
856 
857             if (i0>0)
858             {
859                 // we have content type parameters
860 
861                 // Extract params off mimetype
862                 _mimeType=contentType.substring(0,i0).trim();
863                 _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
864 
865                 // Look for charset
866                 int i1=contentType.indexOf("charset=",i0+1);
867                 if (i1>=0)
868                 {
869                     _explicitEncoding=true;
870                     int i8=i1+8;
871                     int i2 = contentType.indexOf(' ',i8);
872 
873                     if (_outputState==WRITER)
874                     {
875                         // strip the charset and ignore;
876                         if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
877                         {
878                             if (_cachedMimeType!=null)
879                             {
880                                 CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
881                                 if (content_type!=null)
882                                 {
883                                     _contentType=content_type.toString();
884                                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
885                                 }
886                                 else
887                                 {
888                                     _contentType=_mimeType+";charset="+_characterEncoding;
889                                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
890                                 }
891                             }
892                             else
893                             {
894                                 _contentType=_mimeType+";charset="+_characterEncoding;
895                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
896                             }
897                         }
898                         else if (i2<0)
899                         {
900                             _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
901                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
902                         }
903                         else
904                         {
905                             _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
906                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
907                         }
908                     }
909                     else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
910                     {
911                         // The params are just the char encoding
912                         _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
913                         _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
914 
915                         if (_cachedMimeType!=null)
916                         {
917                             CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
918                             if (content_type!=null)
919                             {
920                                 _contentType=content_type.toString();
921                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
922                             }
923                             else
924                             {
925                                 _contentType=contentType;
926                                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
927                             }
928                         }
929                         else
930                         {
931                             _contentType=contentType;
932                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
933                         }
934                     }
935                     else if (i2>0)
936                     {
937                         _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2));
938                         _contentType=contentType;
939                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
940                     }
941                     else
942                     {
943                         _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
944                         _contentType=contentType;
945                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
946                     }
947                 }
948                 else // No encoding in the params.
949                 {
950                     _cachedMimeType=null;
951                     _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
952                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
953                 }
954             }
955             else // No params at all
956             {
957                 _mimeType=contentType;
958                 _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
959 
960                 if (_characterEncoding!=null)
961                 {
962                     if (_cachedMimeType!=null)
963                     {
964                         CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
965                         if (content_type!=null)
966                         {
967                             _contentType=content_type.toString();
968                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
969                         }
970                         else
971                         {
972                             _contentType=_mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
973                             _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
974                         }
975                     }
976                     else
977                     {
978                         _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
979                         _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
980                     }
981                 }
982                 else if (_cachedMimeType!=null)
983                 {
984                     _contentType=_cachedMimeType.toString();
985                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
986                 }
987                 else
988                 {
989                     _contentType=contentType;
990                     _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
991                 }
992             }
993         }
994     }
995 
996     /* ------------------------------------------------------------ */
997     /*
998      * @see javax.servlet.ServletResponse#setBufferSize(int)
999      */
1000     public void setBufferSize(int size)
1001     {
1002         if (isCommitted() || getContentCount()>0)
1003             throw new IllegalStateException("Committed or content written");
1004         _connection.getGenerator().increaseContentBufferSize(size);
1005     }
1006 
1007     /* ------------------------------------------------------------ */
1008     /*
1009      * @see javax.servlet.ServletResponse#getBufferSize()
1010      */
1011     public int getBufferSize()
1012     {
1013         return _connection.getGenerator().getContentBufferSize();
1014     }
1015 
1016     /* ------------------------------------------------------------ */
1017     /*
1018      * @see javax.servlet.ServletResponse#flushBuffer()
1019      */
1020     public void flushBuffer() throws IOException
1021     {
1022         _connection.flushResponse();
1023     }
1024 
1025     /* ------------------------------------------------------------ */
1026     /*
1027      * @see javax.servlet.ServletResponse#reset()
1028      */
1029     public void reset()
1030     {
1031         resetBuffer();
1032         fwdReset();
1033         _status=200;
1034         _reason=null;
1035         
1036         HttpFields response_fields=_connection.getResponseFields();
1037         
1038         response_fields.clear();
1039         String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER);
1040         if (connection!=null)
1041         {
1042             String[] values = connection.split(",");
1043             for  (int i=0;values!=null && i<values.length;i++)
1044             {
1045                 CachedBuffer cb = HttpHeaderValues.CACHE.get(values[0].trim());
1046 
1047                 if (cb!=null)
1048                 {
1049                     switch(cb.getOrdinal())
1050                     {
1051                         case HttpHeaderValues.CLOSE_ORDINAL:
1052                             response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
1053                             break;
1054 
1055                         case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
1056                             if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol()))
1057                                 response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE);
1058                             break;
1059                         case HttpHeaderValues.TE_ORDINAL:
1060                             response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.TE);
1061                             break;
1062                     }
1063                 }
1064             }
1065         }
1066     }
1067     
1068     /* ------------------------------------------------------------ */
1069     /*
1070      * @see javax.servlet.ServletResponse#reset()
1071      */
1072     public void fwdReset()
1073     {
1074         resetBuffer();
1075 
1076         _writer=null;
1077         _outputState=NONE;
1078     }
1079 
1080     /* ------------------------------------------------------------ */
1081     /*
1082      * @see javax.servlet.ServletResponse#resetBuffer()
1083      */
1084     public void resetBuffer()
1085     {
1086         if (isCommitted())
1087             throw new IllegalStateException("Committed");
1088         _connection.getGenerator().resetBuffer();
1089     }
1090 
1091     /* ------------------------------------------------------------ */
1092     /*
1093      * @see javax.servlet.ServletResponse#isCommitted()
1094      */
1095     public boolean isCommitted()
1096     {
1097         return _connection.isResponseCommitted();
1098     }
1099 
1100 
1101     /* ------------------------------------------------------------ */
1102     /*
1103      * @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
1104      */
1105     public void setLocale(Locale locale)
1106     {
1107         if (locale == null || isCommitted() ||_connection.isIncluding())
1108             return;
1109 
1110         _locale = locale;
1111         _connection.getResponseFields().put(HttpHeaders.CONTENT_LANGUAGE_BUFFER,locale.toString().replace('_','-'));
1112 
1113         if (_explicitEncoding || _outputState!=0 )
1114             return;
1115 
1116         if (_connection.getRequest().getContext()==null)
1117             return;
1118 
1119         String charset = _connection.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
1120 
1121         if (charset!=null && charset.length()>0)
1122         {
1123             _characterEncoding=charset;
1124 
1125             /* get current MIME type from Content-Type header */
1126             String type=getContentType();
1127             if (type!=null)
1128             {
1129                 _characterEncoding=charset;
1130                 int semi=type.indexOf(';');
1131                 if (semi<0)
1132                 {
1133                     _mimeType=type;
1134                     _contentType= type += ";charset="+charset;
1135                 }
1136                 else
1137                 {
1138                     _mimeType=type.substring(0,semi);
1139                     _contentType= _mimeType += ";charset="+charset;
1140                 }
1141 
1142                 _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
1143                 _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
1144             }
1145         }
1146     }
1147 
1148     /* ------------------------------------------------------------ */
1149     /*
1150      * @see javax.servlet.ServletResponse#getLocale()
1151      */
1152     public Locale getLocale()
1153     {
1154         if (_locale==null)
1155             return Locale.getDefault();
1156         return _locale;
1157     }
1158 
1159     /* ------------------------------------------------------------ */
1160     /**
1161      * @return The HTTP status code that has been set for this request. This will be <code>200<code>
1162      *    ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods.
1163      */
1164     public int getStatus()
1165     {
1166         return _status;
1167     }
1168 
1169     /* ------------------------------------------------------------ */
1170     /**
1171      * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>,
1172      *    unless one of the <code>setStatus</code> methods have been called.
1173      */
1174     public String getReason()
1175     {
1176         return _reason;
1177     }
1178 
1179     /* ------------------------------------------------------------ */
1180     /**
1181      */
1182     public void complete()
1183         throws IOException
1184     {
1185         _connection.completeResponse();
1186     }
1187 
1188     /* ------------------------------------------------------------- */
1189     /**
1190      * @return the number of bytes actually written in response body
1191      */
1192     public long getContentCount()
1193     {
1194         if (_connection==null || _connection.getGenerator()==null)
1195             return -1;
1196         return _connection.getGenerator().getContentWritten();
1197     }
1198 
1199     /* ------------------------------------------------------------ */
1200     public HttpFields getHttpFields()
1201     {
1202         return _connection.getResponseFields();
1203     }
1204 
1205     /* ------------------------------------------------------------ */
1206     @Override
1207     public String toString()
1208     {
1209         return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+
1210         _connection.getResponseFields().toString();
1211     }
1212     
1213     /* ------------------------------------------------------------ */
1214     /* ------------------------------------------------------------ */
1215     /* ------------------------------------------------------------ */
1216     private static class NullOutput extends ServletOutputStream
1217     {
1218         @Override
1219         public void write(int b) throws IOException
1220         {
1221         }
1222 
1223         @Override
1224         public void print(String s) throws IOException
1225         {
1226         }
1227 
1228         @Override
1229         public void println(String s) throws IOException
1230         {
1231         }
1232 
1233         @Override
1234         public void write(byte[] b, int off, int len) throws IOException
1235         {
1236         }
1237 
1238     }
1239 
1240 }