View Javadoc

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