1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.connector;
20
21
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.PrintWriter;
25 import java.net.MalformedURLException;
26 import java.security.AccessController;
27 import java.security.PrivilegedAction;
28 import java.security.PrivilegedActionException;
29 import java.security.PrivilegedExceptionAction;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.Locale;
34 import java.util.TimeZone;
35 import java.util.Vector;
36
37 import javax.servlet.ServletOutputStream;
38 import javax.servlet.http.Cookie;
39 import javax.servlet.http.HttpServletResponse;
40
41 import org.apache.catalina.Context;
42 import org.apache.catalina.Globals;
43 import org.apache.catalina.Session;
44 import org.apache.catalina.Wrapper;
45 import org.apache.catalina.security.SecurityUtil;
46 import org.apache.catalina.util.CharsetMapper;
47 import org.apache.catalina.util.DateTool;
48 import org.apache.catalina.util.StringManager;
49 import org.apache.tomcat.util.buf.CharChunk;
50 import org.apache.tomcat.util.buf.UEncoder;
51 import org.apache.tomcat.util.http.FastHttpDateFormat;
52 import org.apache.tomcat.util.http.MimeHeaders;
53 import org.apache.tomcat.util.http.ServerCookie;
54 import org.apache.tomcat.util.net.URL;
55
56 /**
57 * Wrapper object for the Coyote response.
58 *
59 * @author Remy Maucherat
60 * @author Craig R. McClanahan
61 * @version $Revision: 606610 $ $Date: 2007-12-23 21:16:08 +0100 (dim., 23 déc. 2007) $
62 */
63
64 public class Response
65 implements HttpServletResponse {
66
67
68 // ----------------------------------------------------------- Constructors
69
70 static {
71 // Ensure that URL is loaded for SM
72 URL.isSchemeChar('c');
73 }
74
75 public Response() {
76 urlEncoder.addSafeCharacter('/');
77 }
78
79
80 // ----------------------------------------------------- Class Variables
81
82
83 /**
84 * Descriptive information about this Response implementation.
85 */
86 protected static final String info =
87 "org.apache.coyote.tomcat5.CoyoteResponse/1.0";
88
89
90 /**
91 * The string manager for this package.
92 */
93 protected static StringManager sm =
94 StringManager.getManager(Constants.Package);
95
96
97 // ----------------------------------------------------- Instance Variables
98
99 /**
100 * The date format we will use for creating date headers.
101 */
102 protected SimpleDateFormat format = null;
103
104
105 // ------------------------------------------------------------- Properties
106
107
108 /**
109 * Associated Catalina connector.
110 */
111 protected Connector connector;
112
113 /**
114 * Return the Connector through which this Request was received.
115 */
116 public Connector getConnector() {
117 return (this.connector);
118 }
119
120 /**
121 * Set the Connector through which this Request was received.
122 *
123 * @param connector The new connector
124 */
125 public void setConnector(Connector connector) {
126 this.connector = connector;
127 if("AJP/1.3".equals(connector.getProtocol())) {
128 // default size to size of one ajp-packet
129 outputBuffer = new OutputBuffer(8184);
130 } else {
131 outputBuffer = new OutputBuffer();
132 }
133 outputStream = new CoyoteOutputStream(outputBuffer);
134 writer = new CoyoteWriter(outputBuffer);
135 }
136
137
138 /**
139 * Coyote response.
140 */
141 protected org.apache.coyote.Response coyoteResponse;
142
143 /**
144 * Set the Coyote response.
145 *
146 * @param coyoteResponse The Coyote response
147 */
148 public void setCoyoteResponse(org.apache.coyote.Response coyoteResponse) {
149 this.coyoteResponse = coyoteResponse;
150 outputBuffer.setResponse(coyoteResponse);
151 }
152
153 /**
154 * Get the Coyote response.
155 */
156 public org.apache.coyote.Response getCoyoteResponse() {
157 return (coyoteResponse);
158 }
159
160
161 /**
162 * Return the Context within which this Request is being processed.
163 */
164 public Context getContext() {
165 return (request.getContext());
166 }
167
168 /**
169 * Set the Context within which this Request is being processed. This
170 * must be called as soon as the appropriate Context is identified, because
171 * it identifies the value to be returned by <code>getContextPath()</code>,
172 * and thus enables parsing of the request URI.
173 *
174 * @param context The newly associated Context
175 */
176 public void setContext(Context context) {
177 request.setContext(context);
178 }
179
180
181 /**
182 * The associated output buffer.
183 */
184 protected OutputBuffer outputBuffer;
185
186
187 /**
188 * The associated output stream.
189 */
190 protected CoyoteOutputStream outputStream;
191
192
193 /**
194 * The associated writer.
195 */
196 protected CoyoteWriter writer;
197
198
199 /**
200 * The application commit flag.
201 */
202 protected boolean appCommitted = false;
203
204
205 /**
206 * The included flag.
207 */
208 protected boolean included = false;
209
210
211 /**
212 * The characterEncoding flag
213 */
214 private boolean isCharacterEncodingSet = false;
215
216 /**
217 * The error flag.
218 */
219 protected boolean error = false;
220
221
222 /**
223 * The set of Cookies associated with this Response.
224 */
225 protected ArrayList cookies = new ArrayList();
226
227
228 /**
229 * Using output stream flag.
230 */
231 protected boolean usingOutputStream = false;
232
233
234 /**
235 * Using writer flag.
236 */
237 protected boolean usingWriter = false;
238
239
240 /**
241 * URL encoder.
242 */
243 protected UEncoder urlEncoder = new UEncoder();
244
245
246 /**
247 * Recyclable buffer to hold the redirect URL.
248 */
249 protected CharChunk redirectURLCC = new CharChunk();
250
251
252 // --------------------------------------------------------- Public Methods
253
254
255 /**
256 * Release all object references, and initialize instance variables, in
257 * preparation for reuse of this object.
258 */
259 public void recycle() {
260
261 outputBuffer.recycle();
262 usingOutputStream = false;
263 usingWriter = false;
264 appCommitted = false;
265 included = false;
266 error = false;
267 isCharacterEncodingSet = false;
268
269 cookies.clear();
270
271 if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) {
272 if (facade != null) {
273 facade.clear();
274 facade = null;
275 }
276 if (outputStream != null) {
277 outputStream.clear();
278 outputStream = null;
279 }
280 if (writer != null) {
281 writer.clear();
282 writer = null;
283 }
284 } else {
285 writer.recycle();
286 }
287
288 }
289
290
291 /**
292 * Clear cached encoders (to save memory for Comet requests).
293 */
294 public void clearEncoders() {
295 outputBuffer.clearEncoders();
296 }
297
298
299 // ------------------------------------------------------- Response Methods
300
301
302 /**
303 * Return the number of bytes actually written to the output stream.
304 */
305 public int getContentCount() {
306 return outputBuffer.getContentWritten();
307 }
308
309 /**
310 * Return the number of bytes actually written to the output stream.
311 */
312 public long getContentCountLong() {
313 return outputBuffer.getContentWrittenLong();
314 }
315
316 /**
317 * Set the application commit flag.
318 *
319 * @param appCommitted The new application committed flag value
320 */
321 public void setAppCommitted(boolean appCommitted) {
322 this.appCommitted = appCommitted;
323 }
324
325
326 /**
327 * Application commit flag accessor.
328 */
329 public boolean isAppCommitted() {
330 return (this.appCommitted || isCommitted() || isSuspended()
331 || ((getContentLength() > 0)
332 && (getContentCount() >= getContentLength())));
333 }
334
335
336 /**
337 * Return the "processing inside an include" flag.
338 */
339 public boolean getIncluded() {
340 return included;
341 }
342
343
344 /**
345 * Set the "processing inside an include" flag.
346 *
347 * @param included <code>true</code> if we are currently inside a
348 * RequestDispatcher.include(), else <code>false</code>
349 */
350 public void setIncluded(boolean included) {
351 this.included = included;
352 }
353
354
355 /**
356 * Return descriptive information about this Response implementation and
357 * the corresponding version number, in the format
358 * <code><description>/<version></code>.
359 */
360 public String getInfo() {
361 return (info);
362 }
363
364
365 /**
366 * The request with which this response is associated.
367 */
368 protected Request request = null;
369
370 /**
371 * Return the Request with which this Response is associated.
372 */
373 public org.apache.catalina.connector.Request getRequest() {
374 return (this.request);
375 }
376
377 /**
378 * Set the Request with which this Response is associated.
379 *
380 * @param request The new associated request
381 */
382 public void setRequest(org.apache.catalina.connector.Request request) {
383 this.request = (Request) request;
384 }
385
386
387 /**
388 * The facade associated with this response.
389 */
390 protected ResponseFacade facade = null;
391
392 /**
393 * Return the <code>ServletResponse</code> for which this object
394 * is the facade.
395 */
396 public HttpServletResponse getResponse() {
397 if (facade == null) {
398 facade = new ResponseFacade(this);
399 }
400 return (facade);
401 }
402
403
404 /**
405 * Return the output stream associated with this Response.
406 */
407 public OutputStream getStream() {
408 if (outputStream == null) {
409 outputStream = new CoyoteOutputStream(outputBuffer);
410 }
411 return outputStream;
412 }
413
414
415 /**
416 * Set the output stream associated with this Response.
417 *
418 * @param stream The new output stream
419 */
420 public void setStream(OutputStream stream) {
421 // This method is evil
422 }
423
424
425 /**
426 * Set the suspended flag.
427 *
428 * @param suspended The new suspended flag value
429 */
430 public void setSuspended(boolean suspended) {
431 outputBuffer.setSuspended(suspended);
432 }
433
434
435 /**
436 * Suspended flag accessor.
437 */
438 public boolean isSuspended() {
439 return outputBuffer.isSuspended();
440 }
441
442
443 /**
444 * Closed flag accessor.
445 */
446 public boolean isClosed() {
447 return outputBuffer.isClosed();
448 }
449
450
451 /**
452 * Set the error flag.
453 */
454 public void setError() {
455 error = true;
456 }
457
458
459 /**
460 * Error flag accessor.
461 */
462 public boolean isError() {
463 return error;
464 }
465
466
467 /**
468 * Create and return a ServletOutputStream to write the content
469 * associated with this Response.
470 *
471 * @exception IOException if an input/output error occurs
472 */
473 public ServletOutputStream createOutputStream()
474 throws IOException {
475 // Probably useless
476 if (outputStream == null) {
477 outputStream = new CoyoteOutputStream(outputBuffer);
478 }
479 return outputStream;
480 }
481
482
483 /**
484 * Perform whatever actions are required to flush and close the output
485 * stream or writer, in a single operation.
486 *
487 * @exception IOException if an input/output error occurs
488 */
489 public void finishResponse()
490 throws IOException {
491 // Writing leftover bytes
492 outputBuffer.close();
493 }
494
495
496 /**
497 * Return the content length that was set or calculated for this Response.
498 */
499 public int getContentLength() {
500 return (coyoteResponse.getContentLength());
501 }
502
503
504 /**
505 * Return the content type that was set or calculated for this response,
506 * or <code>null</code> if no content type was set.
507 */
508 public String getContentType() {
509 return (coyoteResponse.getContentType());
510 }
511
512
513 /**
514 * Return a PrintWriter that can be used to render error messages,
515 * regardless of whether a stream or writer has already been acquired.
516 *
517 * @return Writer which can be used for error reports. If the response is
518 * not an error report returned using sendError or triggered by an
519 * unexpected exception thrown during the servlet processing
520 * (and only in that case), null will be returned if the response stream
521 * has already been used.
522 *
523 * @exception IOException if an input/output error occurs
524 */
525 public PrintWriter getReporter() throws IOException {
526 if (outputBuffer.isNew()) {
527 outputBuffer.checkConverter();
528 if (writer == null) {
529 writer = new CoyoteWriter(outputBuffer);
530 }
531 return writer;
532 } else {
533 return null;
534 }
535 }
536
537
538 // ------------------------------------------------ ServletResponse Methods
539
540
541 /**
542 * Flush the buffer and commit this response.
543 *
544 * @exception IOException if an input/output error occurs
545 */
546 public void flushBuffer()
547 throws IOException {
548 outputBuffer.flush();
549 }
550
551
552 /**
553 * Return the actual buffer size used for this Response.
554 */
555 public int getBufferSize() {
556 return outputBuffer.getBufferSize();
557 }
558
559
560 /**
561 * Return the character encoding used for this Response.
562 */
563 public String getCharacterEncoding() {
564 return (coyoteResponse.getCharacterEncoding());
565 }
566
567
568 /**
569 * Return the servlet output stream associated with this Response.
570 *
571 * @exception IllegalStateException if <code>getWriter</code> has
572 * already been called for this response
573 * @exception IOException if an input/output error occurs
574 */
575 public ServletOutputStream getOutputStream()
576 throws IOException {
577
578 if (usingWriter)
579 throw new IllegalStateException
580 (sm.getString("coyoteResponse.getOutputStream.ise"));
581
582 usingOutputStream = true;
583 if (outputStream == null) {
584 outputStream = new CoyoteOutputStream(outputBuffer);
585 }
586 return outputStream;
587
588 }
589
590
591 /**
592 * Return the Locale assigned to this response.
593 */
594 public Locale getLocale() {
595 return (coyoteResponse.getLocale());
596 }
597
598
599 /**
600 * Return the writer associated with this Response.
601 *
602 * @exception IllegalStateException if <code>getOutputStream</code> has
603 * already been called for this response
604 * @exception IOException if an input/output error occurs
605 */
606 public PrintWriter getWriter()
607 throws IOException {
608
609 if (usingOutputStream)
610 throw new IllegalStateException
611 (sm.getString("coyoteResponse.getWriter.ise"));
612
613 if (Globals.STRICT_SERVLET_COMPLIANCE) {
614 /*
615 * If the response's character encoding has not been specified as
616 * described in <code>getCharacterEncoding</code> (i.e., the method
617 * just returns the default value <code>ISO-8859-1</code>),
618 * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
619 * (with the effect that a subsequent call to getContentType() will
620 * include a charset=ISO-8859-1 component which will also be
621 * reflected in the Content-Type response header, thereby satisfying
622 * the Servlet spec requirement that containers must communicate the
623 * character encoding used for the servlet response's writer to the
624 * client).
625 */
626 setCharacterEncoding(getCharacterEncoding());
627 }
628
629 usingWriter = true;
630 outputBuffer.checkConverter();
631 if (writer == null) {
632 writer = new CoyoteWriter(outputBuffer);
633 }
634 return writer;
635
636 }
637
638
639 /**
640 * Has the output of this response already been committed?
641 */
642 public boolean isCommitted() {
643 return (coyoteResponse.isCommitted());
644 }
645
646
647 /**
648 * Clear any content written to the buffer.
649 *
650 * @exception IllegalStateException if this response has already
651 * been committed
652 */
653 public void reset() {
654
655 if (included)
656 return; // Ignore any call from an included servlet
657
658 coyoteResponse.reset();
659 outputBuffer.reset();
660 usingOutputStream = false;
661 usingWriter = false;
662 isCharacterEncodingSet = false;
663 }
664
665
666 /**
667 * Reset the data buffer but not any status or header information.
668 *
669 * @exception IllegalStateException if the response has already
670 * been committed
671 */
672 public void resetBuffer() {
673
674 if (isCommitted())
675 throw new IllegalStateException
676 (sm.getString("coyoteResponse.resetBuffer.ise"));
677
678 outputBuffer.reset();
679
680 }
681
682
683 /**
684 * Set the buffer size to be used for this Response.
685 *
686 * @param size The new buffer size
687 *
688 * @exception IllegalStateException if this method is called after
689 * output has been committed for this response
690 */
691 public void setBufferSize(int size) {
692
693 if (isCommitted() || !outputBuffer.isNew())
694 throw new IllegalStateException
695 (sm.getString("coyoteResponse.setBufferSize.ise"));
696
697 outputBuffer.setBufferSize(size);
698
699 }
700
701
702 /**
703 * Set the content length (in bytes) for this Response.
704 *
705 * @param length The new content length
706 */
707 public void setContentLength(int length) {
708
709 if (isCommitted())
710 return;
711
712 // Ignore any call from an included servlet
713 if (included)
714 return;
715
716 if (usingWriter)
717 return;
718
719 coyoteResponse.setContentLength(length);
720
721 }
722
723
724 /**
725 * Set the content type for this Response.
726 *
727 * @param type The new content type
728 */
729 public void setContentType(String type) {
730
731 if (isCommitted())
732 return;
733
734 // Ignore any call from an included servlet
735 if (included)
736 return;
737
738 // Ignore charset if getWriter() has already been called
739 if (usingWriter) {
740 if (type != null) {
741 int index = type.indexOf(";");
742 if (index != -1) {
743 type = type.substring(0, index);
744 }
745 }
746 }
747
748 coyoteResponse.setContentType(type);
749
750 // Check to see if content type contains charset
751 if (type != null) {
752 int index = type.indexOf(";");
753 if (index != -1) {
754 int len = type.length();
755 index++;
756 while (index < len && Character.isSpace(type.charAt(index))) {
757 index++;
758 }
759 if (index+7 < len
760 && type.charAt(index) == 'c'
761 && type.charAt(index+1) == 'h'
762 && type.charAt(index+2) == 'a'
763 && type.charAt(index+3) == 'r'
764 && type.charAt(index+4) == 's'
765 && type.charAt(index+5) == 'e'
766 && type.charAt(index+6) == 't'
767 && type.charAt(index+7) == '=') {
768 isCharacterEncodingSet = true;
769 }
770 }
771 }
772 }
773
774
775 /*
776 * Overrides the name of the character encoding used in the body
777 * of the request. This method must be called prior to reading
778 * request parameters or reading input using getReader().
779 *
780 * @param charset String containing the name of the chararacter encoding.
781 */
782 public void setCharacterEncoding(String charset) {
783
784 if (isCommitted())
785 return;
786
787 // Ignore any call from an included servlet
788 if (included)
789 return;
790
791 // Ignore any call made after the getWriter has been invoked
792 // The default should be used
793 if (usingWriter)
794 return;
795
796 coyoteResponse.setCharacterEncoding(charset);
797 isCharacterEncodingSet = true;
798 }
799
800
801
802 /**
803 * Set the Locale that is appropriate for this response, including
804 * setting the appropriate character encoding.
805 *
806 * @param locale The new locale
807 */
808 public void setLocale(Locale locale) {
809
810 if (isCommitted())
811 return;
812
813 // Ignore any call from an included servlet
814 if (included)
815 return;
816
817 coyoteResponse.setLocale(locale);
818
819 // Ignore any call made after the getWriter has been invoked.
820 // The default should be used
821 if (usingWriter)
822 return;
823
824 if (isCharacterEncodingSet) {
825 return;
826 }
827
828 CharsetMapper cm = getContext().getCharsetMapper();
829 String charset = cm.getCharset( locale );
830 if ( charset != null ){
831 coyoteResponse.setCharacterEncoding(charset);
832 }
833
834 }
835
836
837 // --------------------------------------------------- HttpResponse Methods
838
839
840 /**
841 * Return an array of all cookies set for this response, or
842 * a zero-length array if no cookies have been set.
843 */
844 public Cookie[] getCookies() {
845 return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
846 }
847
848
849 /**
850 * Return the value for the specified header, or <code>null</code> if this
851 * header has not been set. If more than one value was added for this
852 * name, only the first is returned; use getHeaderValues() to retrieve all
853 * of them.
854 *
855 * @param name Header name to look up
856 */
857 public String getHeader(String name) {
858 return coyoteResponse.getMimeHeaders().getHeader(name);
859 }
860
861
862 /**
863 * Return an array of all the header names set for this response, or
864 * a zero-length array if no headers have been set.
865 */
866 public String[] getHeaderNames() {
867
868 MimeHeaders headers = coyoteResponse.getMimeHeaders();
869 int n = headers.size();
870 String[] result = new String[n];
871 for (int i = 0; i < n; i++) {
872 result[i] = headers.getName(i).toString();
873 }
874 return result;
875
876 }
877
878
879 /**
880 * Return an array of all the header values associated with the
881 * specified header name, or an zero-length array if there are no such
882 * header values.
883 *
884 * @param name Header name to look up
885 */
886 public String[] getHeaderValues(String name) {
887
888 Enumeration enumeration = coyoteResponse.getMimeHeaders().values(name);
889 Vector result = new Vector();
890 while (enumeration.hasMoreElements()) {
891 result.addElement(enumeration.nextElement());
892 }
893 String[] resultArray = new String[result.size()];
894 result.copyInto(resultArray);
895 return resultArray;
896
897 }
898
899
900 /**
901 * Return the error message that was set with <code>sendError()</code>
902 * for this Response.
903 */
904 public String getMessage() {
905 return coyoteResponse.getMessage();
906 }
907
908
909 /**
910 * Return the HTTP status code associated with this Response.
911 */
912 public int getStatus() {
913 return coyoteResponse.getStatus();
914 }
915
916
917 /**
918 * Reset this response, and specify the values for the HTTP status code
919 * and corresponding message.
920 *
921 * @exception IllegalStateException if this response has already been
922 * committed
923 */
924 public void reset(int status, String message) {
925 reset();
926 setStatus(status, message);
927 }
928
929
930 // -------------------------------------------- HttpServletResponse Methods
931
932
933 /**
934 * Add the specified Cookie to those that will be included with
935 * this Response.
936 *
937 * @param cookie Cookie to be added
938 */
939 public void addCookie(final Cookie cookie) {
940
941 // Ignore any call from an included servlet
942 if (included)
943 return;
944
945 addCookieInternal(cookie);
946
947 }
948
949
950 /**
951 * Add the specified Cookie to those that will be included with
952 * this Response.
953 *
954 * @param cookie Cookie to be added
955 */
956 public void addCookieInternal(final Cookie cookie) {
957
958 if (isCommitted())
959 return;
960
961 final StringBuffer sb = new StringBuffer();
962 //web application code can receive a IllegalArgumentException
963 //from the appendCookieValue invokation
964 if (SecurityUtil.isPackageProtectionEnabled()) {
965 AccessController.doPrivileged(new PrivilegedAction() {
966 public Object run(){
967 ServerCookie.appendCookieValue
968 (sb, cookie.getVersion(), cookie.getName(),
969 cookie.getValue(), cookie.getPath(),
970 cookie.getDomain(), cookie.getComment(),
971 cookie.getMaxAge(), cookie.getSecure());
972 return null;
973 }
974 });
975 } else {
976 ServerCookie.appendCookieValue
977 (sb, cookie.getVersion(), cookie.getName(), cookie.getValue(),
978 cookie.getPath(), cookie.getDomain(), cookie.getComment(),
979 cookie.getMaxAge(), cookie.getSecure());
980 }
981 //if we reached here, no exception, cookie is valid
982 // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
983 // RFC2965 is not supported by browsers and the Servlet spec
984 // asks for 2109.
985 addHeader("Set-Cookie", sb.toString());
986
987 cookies.add(cookie);
988 }
989
990
991 /**
992 * Add the specified date header to the specified value.
993 *
994 * @param name Name of the header to set
995 * @param value Date value to be set
996 */
997 public void addDateHeader(String name, long value) {
998
999 if (isCommitted())
1000 return;
1001
1002 // Ignore any call from an included servlet
1003 if (included) {
1004 return;
1005 }
1006
1007 if (format == null) {
1008 format = new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
1009 Locale.US);
1010 format.setTimeZone(TimeZone.getTimeZone("GMT"));
1011 }
1012
1013 addHeader(name, FastHttpDateFormat.formatDate(value, format));
1014
1015 }
1016
1017
1018 /**
1019 * Add the specified header to the specified value.
1020 *
1021 * @param name Name of the header to set
1022 * @param value Value to be set
1023 */
1024 public void addHeader(String name, String value) {
1025
1026 if (isCommitted())
1027 return;
1028
1029 // Ignore any call from an included servlet
1030 if (included)
1031 return;
1032
1033 coyoteResponse.addHeader(name, value);
1034
1035 }
1036
1037
1038 /**
1039 * Add the specified integer header to the specified value.
1040 *
1041 * @param name Name of the header to set
1042 * @param value Integer value to be set
1043 */
1044 public void addIntHeader(String name, int value) {
1045
1046 if (isCommitted())
1047 return;
1048
1049 // Ignore any call from an included servlet
1050 if (included)
1051 return;
1052
1053 addHeader(name, "" + value);
1054
1055 }
1056
1057
1058 /**
1059 * Has the specified header been set already in this response?
1060 *
1061 * @param name Name of the header to check
1062 */
1063 public boolean containsHeader(String name) {
1064 // Need special handling for Content-Type and Content-Length due to
1065 // special handling of these in coyoteResponse
1066 char cc=name.charAt(0);
1067 if(cc=='C' || cc=='c') {
1068 if(name.equalsIgnoreCase("Content-Type")) {
1069 // Will return null if this has not been set
1070 return (coyoteResponse.getContentType() != null);
1071 }
1072 if(name.equalsIgnoreCase("Content-Length")) {
1073 // -1 means not known and is not sent to client
1074 return (coyoteResponse.getContentLengthLong() != -1);
1075 }
1076 }
1077
1078 return coyoteResponse.containsHeader(name);
1079 }
1080
1081
1082 /**
1083 * Encode the session identifier associated with this response
1084 * into the specified redirect URL, if necessary.
1085 *
1086 * @param url URL to be encoded
1087 */
1088 public String encodeRedirectURL(String url) {
1089
1090 if (isEncodeable(toAbsolute(url))) {
1091 return (toEncoded(url, request.getSessionInternal().getIdInternal()));
1092 } else {
1093 return (url);
1094 }
1095
1096 }
1097
1098
1099 /**
1100 * Encode the session identifier associated with this response
1101 * into the specified redirect URL, if necessary.
1102 *
1103 * @param url URL to be encoded
1104 *
1105 * @deprecated As of Version 2.1 of the Java Servlet API, use
1106 * <code>encodeRedirectURL()</code> instead.
1107 */
1108 public String encodeRedirectUrl(String url) {
1109 return (encodeRedirectURL(url));
1110 }
1111
1112
1113 /**
1114 * Encode the session identifier associated with this response
1115 * into the specified URL, if necessary.
1116 *
1117 * @param url URL to be encoded
1118 */
1119 public String encodeURL(String url) {
1120
1121 String absolute = toAbsolute(url);
1122 if (isEncodeable(absolute)) {
1123 // W3c spec clearly said
1124 if (url.equalsIgnoreCase("")){
1125 url = absolute;
1126 }
1127 return (toEncoded(url, request.getSessionInternal().getIdInternal()));
1128 } else {
1129 return (url);
1130 }
1131
1132 }
1133
1134
1135 /**
1136 * Encode the session identifier associated with this response
1137 * into the specified URL, if necessary.
1138 *
1139 * @param url URL to be encoded
1140 *
1141 * @deprecated As of Version 2.1 of the Java Servlet API, use
1142 * <code>encodeURL()</code> instead.
1143 */
1144 public String encodeUrl(String url) {
1145 return (encodeURL(url));
1146 }
1147
1148
1149 /**
1150 * Send an acknowledgment of a request.
1151 *
1152 * @exception IOException if an input/output error occurs
1153 */
1154 public void sendAcknowledgement()
1155 throws IOException {
1156
1157 if (isCommitted())
1158 return;
1159
1160 // Ignore any call from an included servlet
1161 if (included)
1162 return;
1163
1164 coyoteResponse.acknowledge();
1165
1166 }
1167
1168
1169 /**
1170 * Send an error response with the specified status and a
1171 * default message.
1172 *
1173 * @param status HTTP status code to send
1174 *
1175 * @exception IllegalStateException if this response has
1176 * already been committed
1177 * @exception IOException if an input/output error occurs
1178 */
1179 public void sendError(int status)
1180 throws IOException {
1181 sendError(status, null);
1182 }
1183
1184
1185 /**
1186 * Send an error response with the specified status and message.
1187 *
1188 * @param status HTTP status code to send
1189 * @param message Corresponding message to send
1190 *
1191 * @exception IllegalStateException if this response has
1192 * already been committed
1193 * @exception IOException if an input/output error occurs
1194 */
1195 public void sendError(int status, String message)
1196 throws IOException {
1197
1198 if (isCommitted())
1199 throw new IllegalStateException
1200 (sm.getString("coyoteResponse.sendError.ise"));
1201
1202 // Ignore any call from an included servlet
1203 if (included)
1204 return;
1205
1206 Wrapper wrapper = getRequest().getWrapper();
1207 if (wrapper != null) {
1208 wrapper.incrementErrorCount();
1209 }
1210
1211 setError();
1212
1213 coyoteResponse.setStatus(status);
1214 coyoteResponse.setMessage(message);
1215
1216 // Clear any data content that has been buffered
1217 resetBuffer();
1218
1219 // Cause the response to be finished (from the application perspective)
1220 setSuspended(true);
1221
1222 }
1223
1224
1225 /**
1226 * Send a temporary redirect to the specified redirect location URL.
1227 *
1228 * @param location Location URL to redirect to
1229 *
1230 * @exception IllegalStateException if this response has
1231 * already been committed
1232 * @exception IOException if an input/output error occurs
1233 */
1234 public void sendRedirect(String location)
1235 throws IOException {
1236
1237 if (isCommitted())
1238 throw new IllegalStateException
1239 (sm.getString("coyoteResponse.sendRedirect.ise"));
1240
1241 // Ignore any call from an included servlet
1242 if (included)
1243 return;
1244
1245 // Clear any data content that has been buffered
1246 resetBuffer();
1247
1248 // Generate a temporary redirect to the specified location
1249 try {
1250 String absolute = toAbsolute(location);
1251 setStatus(SC_FOUND);
1252 setHeader("Location", absolute);
1253 } catch (IllegalArgumentException e) {
1254 setStatus(SC_NOT_FOUND);
1255 }
1256
1257 // Cause the response to be finished (from the application perspective)
1258 setSuspended(true);
1259
1260 }
1261
1262
1263 /**
1264 * Set the specified date header to the specified value.
1265 *
1266 * @param name Name of the header to set
1267 * @param value Date value to be set
1268 */
1269 public void setDateHeader(String name, long value) {
1270
1271 if (isCommitted())
1272 return;
1273
1274 // Ignore any call from an included servlet
1275 if (included) {
1276 return;
1277 }
1278
1279 if (format == null) {
1280 format = new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER,
1281 Locale.US);
1282 format.setTimeZone(TimeZone.getTimeZone("GMT"));
1283 }
1284
1285 setHeader(name, FastHttpDateFormat.formatDate(value, format));
1286
1287 }
1288
1289
1290 /**
1291 * Set the specified header to the specified value.
1292 *
1293 * @param name Name of the header to set
1294 * @param value Value to be set
1295 */
1296 public void setHeader(String name, String value) {
1297
1298 if (isCommitted())
1299 return;
1300
1301 // Ignore any call from an included servlet
1302 if (included)
1303 return;
1304
1305 coyoteResponse.setHeader(name, value);
1306
1307 }
1308
1309
1310 /**
1311 * Set the specified integer header to the specified value.
1312 *
1313 * @param name Name of the header to set
1314 * @param value Integer value to be set
1315 */
1316 public void setIntHeader(String name, int value) {
1317
1318 if (isCommitted())
1319 return;
1320
1321 // Ignore any call from an included servlet
1322 if (included)
1323 return;
1324
1325 setHeader(name, "" + value);
1326
1327 }
1328
1329
1330 /**
1331 * Set the HTTP status to be returned with this response.
1332 *
1333 * @param status The new HTTP status
1334 */
1335 public void setStatus(int status) {
1336 setStatus(status, null);
1337 }
1338
1339
1340 /**
1341 * Set the HTTP status and message to be returned with this response.
1342 *
1343 * @param status The new HTTP status
1344 * @param message The associated text message
1345 *
1346 * @deprecated As of Version 2.1 of the Java Servlet API, this method
1347 * has been deprecated due to the ambiguous meaning of the message
1348 * parameter.
1349 */
1350 public void setStatus(int status, String message) {
1351
1352 if (isCommitted())
1353 return;
1354
1355 // Ignore any call from an included servlet
1356 if (included)
1357 return;
1358
1359 coyoteResponse.setStatus(status);
1360 coyoteResponse.setMessage(message);
1361
1362 }
1363
1364
1365 // ------------------------------------------------------ Protected Methods
1366
1367
1368 /**
1369 * Return <code>true</code> if the specified URL should be encoded with
1370 * a session identifier. This will be true if all of the following
1371 * conditions are met:
1372 * <ul>
1373 * <li>The request we are responding to asked for a valid session
1374 * <li>The requested session ID was not received via a cookie
1375 * <li>The specified URL points back to somewhere within the web
1376 * application that is responding to this request
1377 * </ul>
1378 *
1379 * @param location Absolute URL to be validated
1380 */
1381 protected boolean isEncodeable(final String location) {
1382
1383 if (location == null)
1384 return (false);
1385
1386 // Is this an intra-document reference?
1387 if (location.startsWith("#"))
1388 return (false);
1389
1390 // Are we in a valid session that is not using cookies?
1391 final Request hreq = request;
1392 final Session session = hreq.getSessionInternal(false);
1393 if (session == null)
1394 return (false);
1395 if (hreq.isRequestedSessionIdFromCookie())
1396 return (false);
1397
1398 if (SecurityUtil.isPackageProtectionEnabled()) {
1399 return ((Boolean)
1400 AccessController.doPrivileged(new PrivilegedAction() {
1401
1402 public Object run(){
1403 return new Boolean(doIsEncodeable(hreq, session, location));
1404 }
1405 })).booleanValue();
1406 } else {
1407 return doIsEncodeable(hreq, session, location);
1408 }
1409 }
1410
1411 private boolean doIsEncodeable(Request hreq, Session session,
1412 String location) {
1413 // Is this a valid absolute URL?
1414 URL url = null;
1415 try {
1416 url = new URL(location);
1417 } catch (MalformedURLException e) {
1418 return (false);
1419 }
1420
1421 // Does this URL match down to (and including) the context path?
1422 if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
1423 return (false);
1424 if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
1425 return (false);
1426 int serverPort = hreq.getServerPort();
1427 if (serverPort == -1) {
1428 if ("https".equals(hreq.getScheme()))
1429 serverPort = 443;
1430 else
1431 serverPort = 80;
1432 }
1433 int urlPort = url.getPort();
1434 if (urlPort == -1) {
1435 if ("https".equals(url.getProtocol()))
1436 urlPort = 443;
1437 else
1438 urlPort = 80;
1439 }
1440 if (serverPort != urlPort)
1441 return (false);
1442
1443 String contextPath = getContext().getPath();
1444 if (contextPath != null) {
1445 String file = url.getFile();
1446 if ((file == null) || !file.startsWith(contextPath))
1447 return (false);
1448 String tok = ";" + Globals.SESSION_PARAMETER_NAME + "=" + session.getIdInternal();
1449 if( file.indexOf(tok, contextPath.length()) >= 0 )
1450 return (false);
1451 }
1452
1453 // This URL belongs to our web application, so it is encodeable
1454 return (true);
1455
1456 }
1457
1458
1459 /**
1460 * Convert (if necessary) and return the absolute URL that represents the
1461 * resource referenced by this possibly relative URL. If this URL is
1462 * already absolute, return it unchanged.
1463 *
1464 * @param location URL to be (possibly) converted and then returned
1465 *
1466 * @exception IllegalArgumentException if a MalformedURLException is
1467 * thrown when converting the relative URL to an absolute one
1468 */
1469 private String toAbsolute(String location) {
1470
1471 if (location == null)
1472 return (location);
1473
1474 boolean leadingSlash = location.startsWith("/");
1475
1476 if (leadingSlash || !hasScheme(location)) {
1477
1478 redirectURLCC.recycle();
1479
1480 String scheme = request.getScheme();
1481 String name = request.getServerName();
1482 int port = request.getServerPort();
1483
1484 try {
1485 redirectURLCC.append(scheme, 0, scheme.length());
1486 redirectURLCC.append("://", 0, 3);
1487 redirectURLCC.append(name, 0, name.length());
1488 if ((scheme.equals("http") && port != 80)
1489 || (scheme.equals("https") && port != 443)) {
1490 redirectURLCC.append(':');
1491 String portS = port + "";
1492 redirectURLCC.append(portS, 0, portS.length());
1493 }
1494 if (!leadingSlash) {
1495 String relativePath = request.getDecodedRequestURI();
1496 int pos = relativePath.lastIndexOf('/');
1497 relativePath = relativePath.substring(0, pos);
1498
1499 String encodedURI = null;
1500 final String frelativePath = relativePath;
1501 if (SecurityUtil.isPackageProtectionEnabled() ){
1502 try{
1503 encodedURI = (String)AccessController.doPrivileged(
1504 new PrivilegedExceptionAction(){
1505 public Object run() throws IOException{
1506 return urlEncoder.encodeURL(frelativePath);
1507 }
1508 });
1509 } catch (PrivilegedActionException pae){
1510 IllegalArgumentException iae =
1511 new IllegalArgumentException(location);
1512 iae.initCause(pae.getException());
1513 throw iae;
1514 }
1515 } else {
1516 encodedURI = urlEncoder.encodeURL(relativePath);
1517 }
1518 redirectURLCC.append(encodedURI, 0, encodedURI.length());
1519 redirectURLCC.append('/');
1520 }
1521 redirectURLCC.append(location, 0, location.length());
1522 } catch (IOException e) {
1523 IllegalArgumentException iae =
1524 new IllegalArgumentException(location);
1525 iae.initCause(e);
1526 throw iae;
1527 }
1528
1529 return redirectURLCC.toString();
1530
1531 } else {
1532
1533 return (location);
1534
1535 }
1536
1537 }
1538
1539
1540 /**
1541 * Determine if a URI string has a <code>scheme</code> component.
1542 */
1543 private boolean hasScheme(String uri) {
1544 int len = uri.length();
1545 for(int i=0; i < len ; i++) {
1546 char c = uri.charAt(i);
1547 if(c == ':') {
1548 return i > 0;
1549 } else if(!URL.isSchemeChar(c)) {
1550 return false;
1551 }
1552 }
1553 return false;
1554 }
1555
1556 /**
1557 * Return the specified URL with the specified session identifier
1558 * suitably encoded.
1559 *
1560 * @param url URL to be encoded with the session id
1561 * @param sessionId Session id to be included in the encoded URL
1562 */
1563 protected String toEncoded(String url, String sessionId) {
1564
1565 if ((url == null) || (sessionId == null))
1566 return (url);
1567
1568 String path = url;
1569 String query = "";
1570 String anchor = "";
1571 int question = url.indexOf('?');
1572 if (question >= 0) {
1573 path = url.substring(0, question);
1574 query = url.substring(question);
1575 }
1576 int pound = path.indexOf('#');
1577 if (pound >= 0) {
1578 anchor = path.substring(pound);
1579 path = path.substring(0, pound);
1580 }
1581 StringBuffer sb = new StringBuffer(path);
1582 if( sb.length() > 0 ) { // jsessionid can't be first.
1583 sb.append(";");
1584 sb.append(Globals.SESSION_PARAMETER_NAME);
1585 sb.append("=");
1586 sb.append(sessionId);
1587 }
1588 sb.append(anchor);
1589 sb.append(query);
1590 return (sb.toString());
1591
1592 }
1593
1594
1595 }
1596