0001: /*
0002: * Copyright 1999,2004 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package org.apache.coyote.tomcat5;
0018:
0019: import java.io.IOException;
0020: import java.io.OutputStream;
0021: import java.io.PrintWriter;
0022: import java.net.MalformedURLException;
0023: import java.security.AccessController;
0024: import java.security.PrivilegedAction;
0025: import java.security.PrivilegedActionException;
0026: import java.security.PrivilegedExceptionAction;
0027: import java.text.SimpleDateFormat;
0028: import java.util.ArrayList;
0029: import java.util.Enumeration;
0030: import java.util.Locale;
0031: import java.util.TimeZone;
0032: import java.util.Vector;
0033:
0034: import javax.servlet.ServletOutputStream;
0035: import javax.servlet.ServletResponse;
0036: import javax.servlet.http.Cookie;
0037: import javax.servlet.http.HttpServletRequest;
0038: import javax.servlet.http.HttpServletResponse;
0039: import javax.servlet.http.HttpSession;
0040:
0041: import org.apache.catalina.Connector;
0042: import org.apache.catalina.Context;
0043: import org.apache.catalina.HttpResponse;
0044: import org.apache.catalina.Wrapper;
0045: import org.apache.catalina.util.CharsetMapper;
0046: import org.apache.catalina.util.DateTool;
0047: import org.apache.catalina.util.StringManager;
0048: import org.apache.coyote.Response;
0049: import org.apache.tomcat.util.buf.CharChunk;
0050: import org.apache.tomcat.util.buf.UEncoder;
0051: import org.apache.tomcat.util.http.FastHttpDateFormat;
0052: import org.apache.tomcat.util.http.MimeHeaders;
0053: import org.apache.tomcat.util.http.ServerCookie;
0054: import org.apache.tomcat.util.net.URL;
0055: import org.apache.tomcat.util.compat.JdkCompat;
0056:
0057: /**
0058: * Wrapper object for the Coyote response.
0059: *
0060: * @author Remy Maucherat
0061: * @author Craig R. McClanahan
0062: * @version $Revision: 1.18.2.1 $ $Date: 2004/08/23 19:22:20 $
0063: */
0064:
0065: public class CoyoteResponse implements HttpResponse,
0066: HttpServletResponse {
0067:
0068: // ----------------------------------------------------------- Constructors
0069:
0070: public CoyoteResponse() {
0071: urlEncoder.addSafeCharacter('/');
0072: }
0073:
0074: // ----------------------------------------------------- Class Variables
0075:
0076: /**
0077: * JDK compatibility support
0078: */
0079: private static final JdkCompat jdkCompat = JdkCompat.getJdkCompat();
0080:
0081: /**
0082: * Descriptive information about this Response implementation.
0083: */
0084: protected static final String info = "org.apache.coyote.tomcat5.CoyoteResponse/1.0";
0085:
0086: /**
0087: * The string manager for this package.
0088: */
0089: protected static StringManager sm = StringManager
0090: .getManager(Constants.Package);
0091:
0092: // ----------------------------------------------------- Instance Variables
0093:
0094: /**
0095: * The date format we will use for creating date headers.
0096: */
0097: protected SimpleDateFormat format = null;
0098:
0099: // ------------------------------------------------------------- Properties
0100:
0101: /**
0102: * Associated Catalina connector.
0103: */
0104: protected Connector connector;
0105:
0106: /**
0107: * Return the Connector through which this Request was received.
0108: */
0109: public Connector getConnector() {
0110: return (this .connector);
0111: }
0112:
0113: /**
0114: * Set the Connector through which this Request was received.
0115: *
0116: * @param connector The new connector
0117: */
0118: public void setConnector(Connector connector) {
0119: this .connector = connector;
0120: }
0121:
0122: /**
0123: * Coyote response.
0124: */
0125: protected Response coyoteResponse;
0126:
0127: /**
0128: * Set the Coyote response.
0129: *
0130: * @param response The Coyote response
0131: */
0132: public void setCoyoteResponse(Response coyoteResponse) {
0133: this .coyoteResponse = coyoteResponse;
0134: outputBuffer.setResponse(coyoteResponse);
0135: }
0136:
0137: /**
0138: * Get the Coyote response.
0139: */
0140: public Response getCoyoteResponse() {
0141: return (coyoteResponse);
0142: }
0143:
0144: /**
0145: * Return the Context within which this Request is being processed.
0146: */
0147: public Context getContext() {
0148: return (request.getContext());
0149: }
0150:
0151: /**
0152: * Set the Context within which this Request is being processed. This
0153: * must be called as soon as the appropriate Context is identified, because
0154: * it identifies the value to be returned by <code>getContextPath()</code>,
0155: * and thus enables parsing of the request URI.
0156: *
0157: * @param context The newly associated Context
0158: */
0159: public void setContext(Context context) {
0160: request.setContext(context);
0161: }
0162:
0163: /**
0164: * The associated output buffer.
0165: */
0166: protected OutputBuffer outputBuffer = new OutputBuffer();
0167:
0168: /**
0169: * The associated output stream.
0170: */
0171: protected CoyoteOutputStream outputStream = new CoyoteOutputStream(
0172: outputBuffer);
0173:
0174: /**
0175: * The associated writer.
0176: */
0177: protected CoyoteWriter writer = new CoyoteWriter(outputBuffer);
0178:
0179: /**
0180: * The application commit flag.
0181: */
0182: protected boolean appCommitted = false;
0183:
0184: /**
0185: * The included flag.
0186: */
0187: protected boolean included = false;
0188:
0189: /**
0190: * The characterEncoding flag
0191: */
0192: private boolean isCharacterEncodingSet = false;
0193:
0194: /**
0195: * The contextType flag
0196: */
0197: private boolean isContentTypeSet = false;
0198:
0199: /**
0200: * The error flag.
0201: */
0202: protected boolean error = false;
0203:
0204: /**
0205: * The set of Cookies associated with this Response.
0206: */
0207: protected ArrayList cookies = new ArrayList();
0208:
0209: /**
0210: * Using output stream flag.
0211: */
0212: protected boolean usingOutputStream = false;
0213:
0214: /**
0215: * Using writer flag.
0216: */
0217: protected boolean usingWriter = false;
0218:
0219: /**
0220: * URL encoder.
0221: */
0222: protected UEncoder urlEncoder = new UEncoder();
0223:
0224: /**
0225: * Recyclable buffer to hold the redirect URL.
0226: */
0227: protected CharChunk redirectURLCC = new CharChunk();
0228:
0229: // --------------------------------------------------------- Public Methods
0230:
0231: /**
0232: * Release all object references, and initialize instance variables, in
0233: * preparation for reuse of this object.
0234: */
0235: public void recycle() {
0236:
0237: outputBuffer.recycle();
0238: usingOutputStream = false;
0239: usingWriter = false;
0240: appCommitted = false;
0241: included = false;
0242: error = false;
0243: isContentTypeSet = false;
0244: isCharacterEncodingSet = false;
0245:
0246: cookies.clear();
0247:
0248: if (Constants.SECURITY) {
0249: if (facade != null) {
0250: facade.clear();
0251: facade = null;
0252: }
0253: if (outputStream != null) {
0254: outputStream.clear();
0255: outputStream = null;
0256: }
0257: if (writer != null) {
0258: writer.clear();
0259: writer = null;
0260: }
0261: } else {
0262: writer.recycle();
0263: }
0264:
0265: }
0266:
0267: // ------------------------------------------------------- Response Methods
0268:
0269: /**
0270: * Return the number of bytes actually written to the output stream.
0271: */
0272: public int getContentCount() {
0273: return outputBuffer.getContentWritten();
0274: }
0275:
0276: /**
0277: * Set the application commit flag.
0278: *
0279: * @param appCommitted The new application committed flag value
0280: */
0281: public void setAppCommitted(boolean appCommitted) {
0282: this .appCommitted = appCommitted;
0283: }
0284:
0285: /**
0286: * Application commit flag accessor.
0287: */
0288: public boolean isAppCommitted() {
0289: return (this .appCommitted || isCommitted() || isSuspended() || ((getContentLength() != -1) && (getContentCount() >= getContentLength())));
0290: }
0291:
0292: /**
0293: * Return the "processing inside an include" flag.
0294: */
0295: public boolean getIncluded() {
0296: return included;
0297: }
0298:
0299: /**
0300: * Set the "processing inside an include" flag.
0301: *
0302: * @param included <code>true</code> if we are currently inside a
0303: * RequestDispatcher.include(), else <code>false</code>
0304: */
0305: public void setIncluded(boolean included) {
0306: this .included = included;
0307: }
0308:
0309: /**
0310: * Return descriptive information about this Response implementation and
0311: * the corresponding version number, in the format
0312: * <code><description>/<version></code>.
0313: */
0314: public String getInfo() {
0315: return (info);
0316: }
0317:
0318: /**
0319: * The request with which this response is associated.
0320: */
0321: protected CoyoteRequest request = null;
0322:
0323: /**
0324: * Return the Request with which this Response is associated.
0325: */
0326: public org.apache.catalina.Request getRequest() {
0327: return (this .request);
0328: }
0329:
0330: /**
0331: * Set the Request with which this Response is associated.
0332: *
0333: * @param request The new associated request
0334: */
0335: public void setRequest(org.apache.catalina.Request request) {
0336: this .request = (CoyoteRequest) request;
0337: }
0338:
0339: /**
0340: * The facade associated with this response.
0341: */
0342: protected CoyoteResponseFacade facade = null;
0343:
0344: /**
0345: * Return the <code>ServletResponse</code> for which this object
0346: * is the facade.
0347: */
0348: public ServletResponse getResponse() {
0349: if (facade == null) {
0350: facade = new CoyoteResponseFacade(this );
0351: }
0352: return (facade);
0353: }
0354:
0355: /**
0356: * Return the output stream associated with this Response.
0357: */
0358: public OutputStream getStream() {
0359: if (outputStream == null) {
0360: outputStream = new CoyoteOutputStream(outputBuffer);
0361: }
0362: return outputStream;
0363: }
0364:
0365: /**
0366: * Set the output stream associated with this Response.
0367: *
0368: * @param stream The new output stream
0369: */
0370: public void setStream(OutputStream stream) {
0371: // This method is evil
0372: }
0373:
0374: /**
0375: * Set the suspended flag.
0376: *
0377: * @param suspended The new suspended flag value
0378: */
0379: public void setSuspended(boolean suspended) {
0380: outputBuffer.setSuspended(suspended);
0381: }
0382:
0383: /**
0384: * Suspended flag accessor.
0385: */
0386: public boolean isSuspended() {
0387: return outputBuffer.isSuspended();
0388: }
0389:
0390: /**
0391: * Set the error flag.
0392: */
0393: public void setError() {
0394: error = true;
0395: }
0396:
0397: /**
0398: * Error flag accessor.
0399: */
0400: public boolean isError() {
0401: return error;
0402: }
0403:
0404: /**
0405: * Create and return a ServletOutputStream to write the content
0406: * associated with this Response.
0407: *
0408: * @exception IOException if an input/output error occurs
0409: */
0410: public ServletOutputStream createOutputStream() throws IOException {
0411: // Probably useless
0412: if (outputStream == null) {
0413: outputStream = new CoyoteOutputStream(outputBuffer);
0414: }
0415: return outputStream;
0416: }
0417:
0418: /**
0419: * Perform whatever actions are required to flush and close the output
0420: * stream or writer, in a single operation.
0421: *
0422: * @exception IOException if an input/output error occurs
0423: */
0424: public void finishResponse() throws IOException {
0425: // Writing leftover bytes
0426: try {
0427: outputBuffer.close();
0428: } catch (IOException e) {
0429: ;
0430: } catch (Throwable t) {
0431: t.printStackTrace();
0432: }
0433: }
0434:
0435: /**
0436: * Return the content length that was set or calculated for this Response.
0437: */
0438: public int getContentLength() {
0439: return (coyoteResponse.getContentLength());
0440: }
0441:
0442: /**
0443: * Return the content type that was set or calculated for this response,
0444: * or <code>null</code> if no content type was set.
0445: */
0446: public String getContentType() {
0447: return (coyoteResponse.getContentType());
0448: }
0449:
0450: /**
0451: * Return a PrintWriter that can be used to render error messages,
0452: * regardless of whether a stream or writer has already been acquired.
0453: *
0454: * @return Writer which can be used for error reports. If the response is
0455: * not an error report returned using sendError or triggered by an
0456: * unexpected exception thrown during the servlet processing
0457: * (and only in that case), null will be returned if the response stream
0458: * has already been used.
0459: *
0460: * @exception IOException if an input/output error occurs
0461: */
0462: public PrintWriter getReporter() throws IOException {
0463: if (outputBuffer.isNew()) {
0464: outputBuffer.checkConverter();
0465: if (writer == null) {
0466: writer = new CoyoteWriter(outputBuffer);
0467: }
0468: return writer;
0469: } else {
0470: return null;
0471: }
0472: }
0473:
0474: // ------------------------------------------------ ServletResponse Methods
0475:
0476: /**
0477: * Flush the buffer and commit this response.
0478: *
0479: * @exception IOException if an input/output error occurs
0480: */
0481: public void flushBuffer() throws IOException {
0482: outputBuffer.flush();
0483: }
0484:
0485: /**
0486: * Return the actual buffer size used for this Response.
0487: */
0488: public int getBufferSize() {
0489: return outputBuffer.getBufferSize();
0490: }
0491:
0492: /**
0493: * Return the character encoding used for this Response.
0494: */
0495: public String getCharacterEncoding() {
0496: return (coyoteResponse.getCharacterEncoding());
0497: }
0498:
0499: /**
0500: * Return the servlet output stream associated with this Response.
0501: *
0502: * @exception IllegalStateException if <code>getWriter</code> has
0503: * already been called for this response
0504: * @exception IOException if an input/output error occurs
0505: */
0506: public ServletOutputStream getOutputStream() throws IOException {
0507:
0508: if (usingWriter)
0509: throw new IllegalStateException(sm
0510: .getString("coyoteResponse.getOutputStream.ise"));
0511:
0512: usingOutputStream = true;
0513: if (outputStream == null) {
0514: outputStream = new CoyoteOutputStream(outputBuffer);
0515: }
0516: return outputStream;
0517:
0518: }
0519:
0520: /**
0521: * Return the Locale assigned to this response.
0522: */
0523: public Locale getLocale() {
0524: return (coyoteResponse.getLocale());
0525: }
0526:
0527: /**
0528: * Return the writer associated with this Response.
0529: *
0530: * @exception IllegalStateException if <code>getOutputStream</code> has
0531: * already been called for this response
0532: * @exception IOException if an input/output error occurs
0533: */
0534: public PrintWriter getWriter() throws IOException {
0535:
0536: if (usingOutputStream)
0537: throw new IllegalStateException(sm
0538: .getString("coyoteResponse.getWriter.ise"));
0539:
0540: /*
0541: * If the response's character encoding has not been specified as
0542: * described in <code>getCharacterEncoding</code> (i.e., the method
0543: * just returns the default value <code>ISO-8859-1</code>),
0544: * <code>getWriter</code> updates it to <code>ISO-8859-1</code>
0545: * (with the effect that a subsequent call to getContentType() will
0546: * include a charset=ISO-8859-1 component which will also be
0547: * reflected in the Content-Type response header, thereby satisfying
0548: * the Servlet spec requirement that containers must communicate the
0549: * character encoding used for the servlet response's writer to the
0550: * client).
0551: */
0552: setCharacterEncoding(getCharacterEncoding());
0553:
0554: usingWriter = true;
0555: outputBuffer.checkConverter();
0556: if (writer == null) {
0557: writer = new CoyoteWriter(outputBuffer);
0558: }
0559: return writer;
0560:
0561: }
0562:
0563: /**
0564: * Has the output of this response already been committed?
0565: */
0566: public boolean isCommitted() {
0567: return (coyoteResponse.isCommitted());
0568: }
0569:
0570: /**
0571: * Clear any content written to the buffer.
0572: *
0573: * @exception IllegalStateException if this response has already
0574: * been committed
0575: */
0576: public void reset() {
0577:
0578: if (included)
0579: return; // Ignore any call from an included servlet
0580:
0581: coyoteResponse.reset();
0582: outputBuffer.reset();
0583: }
0584:
0585: /**
0586: * Reset the data buffer but not any status or header information.
0587: *
0588: * @exception IllegalStateException if the response has already
0589: * been committed
0590: */
0591: public void resetBuffer() {
0592:
0593: if (isCommitted())
0594: throw new IllegalStateException(sm
0595: .getString("coyoteResponse.resetBuffer.ise"));
0596:
0597: outputBuffer.reset();
0598:
0599: }
0600:
0601: /**
0602: * Set the buffer size to be used for this Response.
0603: *
0604: * @param size The new buffer size
0605: *
0606: * @exception IllegalStateException if this method is called after
0607: * output has been committed for this response
0608: */
0609: public void setBufferSize(int size) {
0610:
0611: if (isCommitted() || !outputBuffer.isNew())
0612: throw new IllegalStateException(sm
0613: .getString("coyoteResponse.setBufferSize.ise"));
0614:
0615: outputBuffer.setBufferSize(size);
0616:
0617: }
0618:
0619: /**
0620: * Set the content length (in bytes) for this Response.
0621: *
0622: * @param length The new content length
0623: */
0624: public void setContentLength(int length) {
0625:
0626: if (isCommitted())
0627: return;
0628:
0629: // Ignore any call from an included servlet
0630: if (included)
0631: return;
0632:
0633: if (usingWriter)
0634: return;
0635:
0636: coyoteResponse.setContentLength(length);
0637:
0638: }
0639:
0640: /**
0641: * Set the content type for this Response.
0642: *
0643: * @param type The new content type
0644: */
0645: public void setContentType(String type) {
0646:
0647: if (isCommitted())
0648: return;
0649:
0650: // Ignore any call from an included servlet
0651: if (included)
0652: return;
0653:
0654: // Ignore charset if getWriter() has already been called
0655: if (usingWriter) {
0656: if (type != null) {
0657: int index = type.indexOf(";");
0658: if (index != -1) {
0659: type = type.substring(0, index);
0660: }
0661: }
0662: }
0663:
0664: coyoteResponse.setContentType(type);
0665:
0666: // Check to see if content type contains charset
0667: if (type != null) {
0668: int index = type.indexOf(";");
0669: if (index != -1) {
0670: int len = type.length();
0671: index++;
0672: while (index < len
0673: && Character.isSpace(type.charAt(index))) {
0674: index++;
0675: }
0676: if (index + 7 < len && type.charAt(index) == 'c'
0677: && type.charAt(index + 1) == 'h'
0678: && type.charAt(index + 2) == 'a'
0679: && type.charAt(index + 3) == 'r'
0680: && type.charAt(index + 4) == 's'
0681: && type.charAt(index + 5) == 'e'
0682: && type.charAt(index + 6) == 't'
0683: && type.charAt(index + 7) == '=') {
0684: isCharacterEncodingSet = true;
0685: }
0686: }
0687: }
0688:
0689: isContentTypeSet = true;
0690: }
0691:
0692: /*
0693: * Overrides the name of the character encoding used in the body
0694: * of the request. This method must be called prior to reading
0695: * request parameters or reading input using getReader().
0696: *
0697: * @param charset String containing the name of the chararacter encoding.
0698: */
0699: public void setCharacterEncoding(String charset) {
0700:
0701: if (isCommitted())
0702: return;
0703:
0704: // Ignore any call from an included servlet
0705: if (included)
0706: return;
0707:
0708: // Ignore any call made after the getWriter has been invoked
0709: // The default should be used
0710: if (usingWriter)
0711: return;
0712:
0713: coyoteResponse.setCharacterEncoding(charset);
0714: isCharacterEncodingSet = true;
0715: }
0716:
0717: /**
0718: * Set the Locale that is appropriate for this response, including
0719: * setting the appropriate character encoding.
0720: *
0721: * @param locale The new locale
0722: */
0723: public void setLocale(Locale locale) {
0724:
0725: if (isCommitted())
0726: return;
0727:
0728: // Ignore any call from an included servlet
0729: if (included)
0730: return;
0731:
0732: coyoteResponse.setLocale(locale);
0733:
0734: // Ignore any call made after the getWriter has been invoked.
0735: // The default should be used
0736: if (usingWriter)
0737: return;
0738:
0739: if (isCharacterEncodingSet) {
0740: return;
0741: }
0742:
0743: CharsetMapper cm = getContext().getCharsetMapper();
0744: String charset = cm.getCharset(locale);
0745: if (charset != null) {
0746: coyoteResponse.setCharacterEncoding(charset);
0747: }
0748:
0749: }
0750:
0751: // --------------------------------------------------- HttpResponse Methods
0752:
0753: /**
0754: * Return an array of all cookies set for this response, or
0755: * a zero-length array if no cookies have been set.
0756: */
0757: public Cookie[] getCookies() {
0758: return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
0759: }
0760:
0761: /**
0762: * Return the value for the specified header, or <code>null</code> if this
0763: * header has not been set. If more than one value was added for this
0764: * name, only the first is returned; use getHeaderValues() to retrieve all
0765: * of them.
0766: *
0767: * @param name Header name to look up
0768: */
0769: public String getHeader(String name) {
0770: return coyoteResponse.getMimeHeaders().getHeader(name);
0771: }
0772:
0773: /**
0774: * Return an array of all the header names set for this response, or
0775: * a zero-length array if no headers have been set.
0776: */
0777: public String[] getHeaderNames() {
0778:
0779: MimeHeaders headers = coyoteResponse.getMimeHeaders();
0780: int n = headers.size();
0781: String[] result = new String[n];
0782: for (int i = 0; i < n; i++) {
0783: result[i] = headers.getName(i).toString();
0784: }
0785: return result;
0786:
0787: }
0788:
0789: /**
0790: * Return an array of all the header values associated with the
0791: * specified header name, or an zero-length array if there are no such
0792: * header values.
0793: *
0794: * @param name Header name to look up
0795: */
0796: public String[] getHeaderValues(String name) {
0797:
0798: Enumeration enum = coyoteResponse.getMimeHeaders().values(name);
0799: Vector result = new Vector();
0800: while (enum.hasMoreElements()) {
0801: result.addElement(enum.nextElement());
0802: }
0803: String[] resultArray = new String[result.size()];
0804: result.copyInto(resultArray);
0805: return resultArray;
0806:
0807: }
0808:
0809: /**
0810: * Return the error message that was set with <code>sendError()</code>
0811: * for this Response.
0812: */
0813: public String getMessage() {
0814: return coyoteResponse.getMessage();
0815: }
0816:
0817: /**
0818: * Return the HTTP status code associated with this Response.
0819: */
0820: public int getStatus() {
0821: return coyoteResponse.getStatus();
0822: }
0823:
0824: /**
0825: * Reset this response, and specify the values for the HTTP status code
0826: * and corresponding message.
0827: *
0828: * @exception IllegalStateException if this response has already been
0829: * committed
0830: */
0831: public void reset(int status, String message) {
0832: reset();
0833: setStatus(status, message);
0834: }
0835:
0836: // -------------------------------------------- HttpServletResponse Methods
0837:
0838: /**
0839: * Add the specified Cookie to those that will be included with
0840: * this Response.
0841: *
0842: * @param cookie Cookie to be added
0843: */
0844: public void addCookie(final Cookie cookie) {
0845:
0846: if (isCommitted())
0847: return;
0848:
0849: // Ignore any call from an included servlet
0850: if (included)
0851: return;
0852:
0853: cookies.add(cookie);
0854:
0855: final StringBuffer sb = new StringBuffer();
0856: if (System.getSecurityManager() != null) {
0857: AccessController.doPrivileged(new PrivilegedAction() {
0858: public Object run() {
0859: ServerCookie.appendCookieValue(sb, cookie
0860: .getVersion(), cookie.getName(), cookie
0861: .getValue(), cookie.getPath(), cookie
0862: .getDomain(), cookie.getComment(), cookie
0863: .getMaxAge(), cookie.getSecure());
0864: return null;
0865: }
0866: });
0867: } else {
0868: ServerCookie.appendCookieValue(sb, cookie.getVersion(),
0869: cookie.getName(), cookie.getValue(), cookie
0870: .getPath(), cookie.getDomain(), cookie
0871: .getComment(), cookie.getMaxAge(), cookie
0872: .getSecure());
0873: }
0874:
0875: // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
0876: // RFC2965 is not supported by browsers and the Servlet spec
0877: // asks for 2109.
0878: addHeader("Set-Cookie", sb.toString());
0879:
0880: }
0881:
0882: /**
0883: * Add the specified date header to the specified value.
0884: *
0885: * @param name Name of the header to set
0886: * @param value Date value to be set
0887: */
0888: public void addDateHeader(String name, long value) {
0889:
0890: if (isCommitted())
0891: return;
0892:
0893: // Ignore any call from an included servlet
0894: if (included) {
0895: return;
0896: }
0897:
0898: if (format == null) {
0899: format = new SimpleDateFormat(
0900: DateTool.HTTP_RESPONSE_DATE_HEADER, Locale.US);
0901: format.setTimeZone(TimeZone.getTimeZone("GMT"));
0902: }
0903:
0904: addHeader(name, FastHttpDateFormat.formatDate(value, format));
0905:
0906: }
0907:
0908: /**
0909: * Add the specified header to the specified value.
0910: *
0911: * @param name Name of the header to set
0912: * @param value Value to be set
0913: */
0914: public void addHeader(String name, String value) {
0915:
0916: if (isCommitted())
0917: return;
0918:
0919: // Ignore any call from an included servlet
0920: if (included)
0921: return;
0922:
0923: coyoteResponse.addHeader(name, value);
0924:
0925: }
0926:
0927: /**
0928: * Add the specified integer header to the specified value.
0929: *
0930: * @param name Name of the header to set
0931: * @param value Integer value to be set
0932: */
0933: public void addIntHeader(String name, int value) {
0934:
0935: if (isCommitted())
0936: return;
0937:
0938: // Ignore any call from an included servlet
0939: if (included)
0940: return;
0941:
0942: addHeader(name, "" + value);
0943:
0944: }
0945:
0946: /**
0947: * Has the specified header been set already in this response?
0948: *
0949: * @param name Name of the header to check
0950: */
0951: public boolean containsHeader(String name) {
0952: return coyoteResponse.containsHeader(name);
0953: }
0954:
0955: /**
0956: * Encode the session identifier associated with this response
0957: * into the specified redirect URL, if necessary.
0958: *
0959: * @param url URL to be encoded
0960: */
0961: public String encodeRedirectURL(String url) {
0962:
0963: if (isEncodeable(toAbsolute(url))) {
0964: HttpServletRequest hreq = (HttpServletRequest) request
0965: .getRequest();
0966: return (toEncoded(url, hreq.getSession().getId()));
0967: } else {
0968: return (url);
0969: }
0970:
0971: }
0972:
0973: /**
0974: * Encode the session identifier associated with this response
0975: * into the specified redirect URL, if necessary.
0976: *
0977: * @param url URL to be encoded
0978: *
0979: * @deprecated As of Version 2.1 of the Java Servlet API, use
0980: * <code>encodeRedirectURL()</code> instead.
0981: */
0982: public String encodeRedirectUrl(String url) {
0983: return (encodeRedirectURL(url));
0984: }
0985:
0986: /**
0987: * Encode the session identifier associated with this response
0988: * into the specified URL, if necessary.
0989: *
0990: * @param url URL to be encoded
0991: */
0992: public String encodeURL(String url) {
0993:
0994: String absolute = toAbsolute(url);
0995: if (isEncodeable(absolute)) {
0996: HttpServletRequest hreq = (HttpServletRequest) request
0997: .getRequest();
0998:
0999: // W3c spec clearly said
1000: if (url.equalsIgnoreCase("")) {
1001: url = absolute;
1002: }
1003: return (toEncoded(url, hreq.getSession().getId()));
1004: } else {
1005: return (url);
1006: }
1007:
1008: }
1009:
1010: /**
1011: * Encode the session identifier associated with this response
1012: * into the specified URL, if necessary.
1013: *
1014: * @param url URL to be encoded
1015: *
1016: * @deprecated As of Version 2.1 of the Java Servlet API, use
1017: * <code>encodeURL()</code> instead.
1018: */
1019: public String encodeUrl(String url) {
1020: return (encodeURL(url));
1021: }
1022:
1023: /**
1024: * Send an acknowledgment of a request.
1025: *
1026: * @exception IOException if an input/output error occurs
1027: */
1028: public void sendAcknowledgement() throws IOException {
1029:
1030: if (isCommitted())
1031: return;
1032:
1033: // Ignore any call from an included servlet
1034: if (included)
1035: return;
1036:
1037: coyoteResponse.acknowledge();
1038:
1039: }
1040:
1041: /**
1042: * Send an error response with the specified status and a
1043: * default message.
1044: *
1045: * @param status HTTP status code to send
1046: *
1047: * @exception IllegalStateException if this response has
1048: * already been committed
1049: * @exception IOException if an input/output error occurs
1050: */
1051: public void sendError(int status) throws IOException {
1052: sendError(status, null);
1053: }
1054:
1055: /**
1056: * Send an error response with the specified status and message.
1057: *
1058: * @param status HTTP status code to send
1059: * @param message Corresponding message to send
1060: *
1061: * @exception IllegalStateException if this response has
1062: * already been committed
1063: * @exception IOException if an input/output error occurs
1064: */
1065: public void sendError(int status, String message)
1066: throws IOException {
1067:
1068: if (isCommitted())
1069: throw new IllegalStateException(sm
1070: .getString("coyoteResponse.sendError.ise"));
1071:
1072: // Ignore any call from an included servlet
1073: if (included)
1074: return;
1075:
1076: Wrapper wrapper = getRequest().getWrapper();
1077: if (wrapper != null) {
1078: wrapper.incrementErrorCount();
1079: }
1080:
1081: setError();
1082:
1083: coyoteResponse.setStatus(status);
1084: coyoteResponse.setMessage(message);
1085:
1086: // Clear any data content that has been buffered
1087: resetBuffer();
1088:
1089: // Cause the response to be finished (from the application perspective)
1090: setSuspended(true);
1091:
1092: }
1093:
1094: /**
1095: * Send a temporary redirect to the specified redirect location URL.
1096: *
1097: * @param location Location URL to redirect to
1098: *
1099: * @exception IllegalStateException if this response has
1100: * already been committed
1101: * @exception IOException if an input/output error occurs
1102: */
1103: public void sendRedirect(String location) throws IOException {
1104:
1105: if (isCommitted())
1106: throw new IllegalStateException(sm
1107: .getString("coyoteResponse.sendRedirect.ise"));
1108:
1109: // Ignore any call from an included servlet
1110: if (included)
1111: return;
1112:
1113: // Clear any data content that has been buffered
1114: resetBuffer();
1115:
1116: // Generate a temporary redirect to the specified location
1117: try {
1118: String absolute = toAbsolute(location);
1119: setStatus(SC_FOUND);
1120: setHeader("Location", absolute);
1121: } catch (IllegalArgumentException e) {
1122: setStatus(SC_NOT_FOUND);
1123: }
1124:
1125: // Cause the response to be finished (from the application perspective)
1126: setSuspended(true);
1127:
1128: }
1129:
1130: /**
1131: * Set the specified date header to the specified value.
1132: *
1133: * @param name Name of the header to set
1134: * @param value Date value to be set
1135: */
1136: public void setDateHeader(String name, long value) {
1137:
1138: if (isCommitted())
1139: return;
1140:
1141: // Ignore any call from an included servlet
1142: if (included) {
1143: return;
1144: }
1145:
1146: if (format == null) {
1147: format = new SimpleDateFormat(
1148: DateTool.HTTP_RESPONSE_DATE_HEADER, Locale.US);
1149: format.setTimeZone(TimeZone.getTimeZone("GMT"));
1150: }
1151:
1152: setHeader(name, FastHttpDateFormat.formatDate(value, format));
1153:
1154: }
1155:
1156: /**
1157: * Set the specified header to the specified value.
1158: *
1159: * @param name Name of the header to set
1160: * @param value Value to be set
1161: */
1162: public void setHeader(String name, String value) {
1163:
1164: if (isCommitted())
1165: return;
1166:
1167: // Ignore any call from an included servlet
1168: if (included)
1169: return;
1170:
1171: coyoteResponse.setHeader(name, value);
1172:
1173: }
1174:
1175: /**
1176: * Set the specified integer header to the specified value.
1177: *
1178: * @param name Name of the header to set
1179: * @param value Integer value to be set
1180: */
1181: public void setIntHeader(String name, int value) {
1182:
1183: if (isCommitted())
1184: return;
1185:
1186: // Ignore any call from an included servlet
1187: if (included)
1188: return;
1189:
1190: setHeader(name, "" + value);
1191:
1192: }
1193:
1194: /**
1195: * Set the HTTP status to be returned with this response.
1196: *
1197: * @param status The new HTTP status
1198: */
1199: public void setStatus(int status) {
1200: setStatus(status, null);
1201: }
1202:
1203: /**
1204: * Set the HTTP status and message to be returned with this response.
1205: *
1206: * @param status The new HTTP status
1207: * @param message The associated text message
1208: *
1209: * @deprecated As of Version 2.1 of the Java Servlet API, this method
1210: * has been deprecated due to the ambiguous meaning of the message
1211: * parameter.
1212: */
1213: public void setStatus(int status, String message) {
1214:
1215: if (isCommitted())
1216: return;
1217:
1218: // Ignore any call from an included servlet
1219: if (included)
1220: return;
1221:
1222: coyoteResponse.setStatus(status);
1223: coyoteResponse.setMessage(message);
1224:
1225: }
1226:
1227: // ------------------------------------------------------ Protected Methods
1228:
1229: /**
1230: * Return <code>true</code> if the specified URL should be encoded with
1231: * a session identifier. This will be true if all of the following
1232: * conditions are met:
1233: * <ul>
1234: * <li>The request we are responding to asked for a valid session
1235: * <li>The requested session ID was not received via a cookie
1236: * <li>The specified URL points back to somewhere within the web
1237: * application that is responding to this request
1238: * </ul>
1239: *
1240: * @param location Absolute URL to be validated
1241: */
1242: protected boolean isEncodeable(final String location) {
1243:
1244: if (location == null)
1245: return (false);
1246:
1247: // Is this an intra-document reference?
1248: if (location.startsWith("#"))
1249: return (false);
1250:
1251: // Are we in a valid session that is not using cookies?
1252: final HttpServletRequest hreq = (HttpServletRequest) request
1253: .getRequest();
1254: final HttpSession session = hreq.getSession(false);
1255: if (session == null)
1256: return (false);
1257: if (hreq.isRequestedSessionIdFromCookie())
1258: return (false);
1259:
1260: if (System.getSecurityManager() != null) {
1261: return ((Boolean) AccessController
1262: .doPrivileged(new PrivilegedAction() {
1263:
1264: public Object run() {
1265: return new Boolean(doIsEncodeable(hreq,
1266: session, location));
1267: }
1268: })).booleanValue();
1269: } else {
1270: return doIsEncodeable(hreq, session, location);
1271: }
1272: }
1273:
1274: private boolean doIsEncodeable(HttpServletRequest hreq,
1275: HttpSession session, String location) {
1276: // Is this a valid absolute URL?
1277: URL url = null;
1278: try {
1279: url = new URL(location);
1280: } catch (MalformedURLException e) {
1281: return (false);
1282: }
1283:
1284: // Does this URL match down to (and including) the context path?
1285: if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol()))
1286: return (false);
1287: if (!hreq.getServerName().equalsIgnoreCase(url.getHost()))
1288: return (false);
1289: int serverPort = hreq.getServerPort();
1290: if (serverPort == -1) {
1291: if ("https".equals(hreq.getScheme()))
1292: serverPort = 443;
1293: else
1294: serverPort = 80;
1295: }
1296: int urlPort = url.getPort();
1297: if (urlPort == -1) {
1298: if ("https".equals(url.getProtocol()))
1299: urlPort = 443;
1300: else
1301: urlPort = 80;
1302: }
1303: if (serverPort != urlPort)
1304: return (false);
1305:
1306: String contextPath = getContext().getPath();
1307: if (contextPath != null) {
1308: String file = url.getFile();
1309: if ((file == null) || !file.startsWith(contextPath))
1310: return (false);
1311: if (file.indexOf(";jsessionid=" + session.getId()) >= 0)
1312: return (false);
1313: }
1314:
1315: // This URL belongs to our web application, so it is encodeable
1316: return (true);
1317:
1318: }
1319:
1320: /**
1321: * Convert (if necessary) and return the absolute URL that represents the
1322: * resource referenced by this possibly relative URL. If this URL is
1323: * already absolute, return it unchanged.
1324: *
1325: * @param location URL to be (possibly) converted and then returned
1326: *
1327: * @exception IllegalArgumentException if a MalformedURLException is
1328: * thrown when converting the relative URL to an absolute one
1329: */
1330: private String toAbsolute(String location) {
1331:
1332: if (location == null)
1333: return (location);
1334:
1335: boolean leadingSlash = location.startsWith("/");
1336:
1337: if (leadingSlash
1338: || (!leadingSlash && (location.indexOf("://") == -1))) {
1339:
1340: redirectURLCC.recycle();
1341:
1342: String scheme = request.getScheme();
1343: String name = request.getServerName();
1344: int port = request.getServerPort();
1345:
1346: try {
1347: redirectURLCC.append(scheme, 0, scheme.length());
1348: redirectURLCC.append("://", 0, 3);
1349: redirectURLCC.append(name, 0, name.length());
1350: if ((scheme.equals("http") && port != 80)
1351: || (scheme.equals("https") && port != 443)) {
1352: redirectURLCC.append(':');
1353: String portS = port + "";
1354: redirectURLCC.append(portS, 0, portS.length());
1355: }
1356: if (!leadingSlash) {
1357: String relativePath = request
1358: .getDecodedRequestURI();
1359: int pos = relativePath.lastIndexOf('/');
1360: relativePath = relativePath.substring(0, pos);
1361:
1362: String encodedURI = null;
1363: final String frelativePath = relativePath;
1364: if (System.getSecurityManager() != null) {
1365: try {
1366: encodedURI = (String) AccessController
1367: .doPrivileged(new PrivilegedExceptionAction() {
1368: public Object run()
1369: throws IOException {
1370: return urlEncoder
1371: .encodeURL(frelativePath);
1372: }
1373: });
1374: } catch (PrivilegedActionException pae) {
1375: IllegalArgumentException iae = new IllegalArgumentException(
1376: location);
1377: jdkCompat.chainException(iae, pae
1378: .getException());
1379: throw iae;
1380: }
1381: } else {
1382: encodedURI = urlEncoder.encodeURL(relativePath);
1383: }
1384: redirectURLCC.append(encodedURI, 0, encodedURI
1385: .length());
1386: redirectURLCC.append('/');
1387: }
1388: redirectURLCC.append(location, 0, location.length());
1389: } catch (IOException e) {
1390: IllegalArgumentException iae = new IllegalArgumentException(
1391: location);
1392: jdkCompat.chainException(iae, e);
1393: throw iae;
1394: }
1395:
1396: return redirectURLCC.toString();
1397:
1398: } else {
1399:
1400: return (location);
1401:
1402: }
1403:
1404: }
1405:
1406: /**
1407: * Return the specified URL with the specified session identifier
1408: * suitably encoded.
1409: *
1410: * @param url URL to be encoded with the session id
1411: * @param sessionId Session id to be included in the encoded URL
1412: */
1413: private String toEncoded(String url, String sessionId) {
1414:
1415: if ((url == null) || (sessionId == null))
1416: return (url);
1417:
1418: String path = url;
1419: String query = "";
1420: String anchor = "";
1421: int question = url.indexOf('?');
1422: if (question >= 0) {
1423: path = url.substring(0, question);
1424: query = url.substring(question);
1425: }
1426: int pound = path.indexOf('#');
1427: if (pound >= 0) {
1428: anchor = path.substring(pound);
1429: path = path.substring(0, pound);
1430: }
1431: StringBuffer sb = new StringBuffer(path);
1432: if (sb.length() > 0) { // jsessionid can't be first.
1433: sb.append(";jsessionid=");
1434: sb.append(sessionId);
1435: }
1436: sb.append(anchor);
1437: sb.append(query);
1438: return (sb.toString());
1439:
1440: }
1441:
1442: }
|