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