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