View Javadoc

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