View Javadoc

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