View Javadoc

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