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