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