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