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