View Javadoc

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