View Javadoc

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