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