| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package org.apache.catalina.connector; |
| |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.net.MalformedURLException; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| import java.util.Vector; |
| |
| import javax.servlet.ServletOutputStream; |
| import javax.servlet.http.Cookie; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import javax.servlet.http.HttpSession; |
| |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Wrapper; |
| import org.apache.catalina.util.CharsetMapper; |
| import org.apache.catalina.util.DateTool; |
| import org.apache.catalina.util.StringManager; |
| import org.apache.catalina.security.SecurityUtil; |
| import org.apache.tomcat.util.buf.CharChunk; |
| import org.apache.tomcat.util.buf.UEncoder; |
| import org.apache.tomcat.util.http.FastHttpDateFormat; |
| import org.apache.tomcat.util.http.MimeHeaders; |
| import org.apache.tomcat.util.http.ServerCookie; |
| import org.apache.tomcat.util.net.URL; |
| import org.apache.tomcat.util.compat.JdkCompat; |
| |
| /** |
| * Wrapper object for the Coyote response. |
| * |
| * @author Remy Maucherat |
| * @author Craig R. McClanahan |
| * @version $Revision$ $Date$ |
| */ |
| |
| public class Response |
| implements HttpServletResponse { |
| |
| |
| // ----------------------------------------------------------- Constructors |
| |
| |
| public Response() { |
| urlEncoder.addSafeCharacter('/'); |
| } |
| |
| |
| // ----------------------------------------------------- Class Variables |
| |
| |
| /** |
| * JDK compatibility support |
| */ |
| private static final JdkCompat jdkCompat = JdkCompat.getJdkCompat(); |
| |
| |
| /** |
| * Descriptive information about this Response implementation. |
| */ |
| protected static final String info = |
| "org.apache.coyote.tomcat5.CoyoteResponse/1.0"; |
| |
| |
| /** |
| * The string manager for this package. |
| */ |
| protected static StringManager sm = |
| StringManager.getManager(Constants.Package); |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| /** |
| * The date format we will use for creating date headers. |
| */ |
| protected SimpleDateFormat format = null; |
| |
| |
| // ------------------------------------------------------------- Properties |
| |
| |
| /** |
| * Associated Catalina connector. |
| */ |
| protected Connector connector; |
| |
| /** |
| * Return the Connector through which this Request was received. |
| */ |
| public Connector getConnector() { |
| return (this.connector); |
| } |
| |
| /** |
| * Set the Connector through which this Request was received. |
| * |
| * @param connector The new connector |
| */ |
| public void setConnector(Connector connector) { |
| this.connector = connector; |
| } |
| |
| |
| /** |
| * Coyote response. |
| */ |
| protected org.apache.coyote.Response coyoteResponse; |
| |
| /** |
| * Set the Coyote response. |
| * |
| * @param coyoteResponse The Coyote response |
| */ |
| public void setCoyoteResponse(org.apache.coyote.Response coyoteResponse) { |
| this.coyoteResponse = coyoteResponse; |
| outputBuffer.setResponse(coyoteResponse); |
| } |
| |
| /** |
| * Get the Coyote response. |
| */ |
| public org.apache.coyote.Response getCoyoteResponse() { |
| return (coyoteResponse); |
| } |
| |
| |
| /** |
| * Return the Context within which this Request is being processed. |
| */ |
| public Context getContext() { |
| return (request.getContext()); |
| } |
| |
| /** |
| * Set the Context within which this Request is being processed. This |
| * must be called as soon as the appropriate Context is identified, because |
| * it identifies the value to be returned by <code>getContextPath()</code>, |
| * and thus enables parsing of the request URI. |
| * |
| * @param context The newly associated Context |
| */ |
| public void setContext(Context context) { |
| request.setContext(context); |
| } |
| |
| |
| /** |
| * The associated output buffer. |
| */ |
| protected OutputBuffer outputBuffer = new OutputBuffer(); |
| |
| |
| /** |
| * The associated output stream. |
| */ |
| protected CoyoteOutputStream outputStream = |
| new CoyoteOutputStream(outputBuffer); |
| |
| |
| /** |
| * The associated writer. |
| */ |
| protected CoyoteWriter writer = new CoyoteWriter(outputBuffer); |
| |
| |
| /** |
| * The application commit flag. |
| */ |
| protected boolean appCommitted = false; |
| |
| |
| /** |
| * The included flag. |
| */ |
| protected boolean included = false; |
| |
| |
| /** |
| * The characterEncoding flag |
| */ |
| private boolean isCharacterEncodingSet = false; |
| |
| /** |
| * The contextType flag |
| */ |
| private boolean isContentTypeSet = false; |
| |
| |
| /** |
| * The error flag. |
| */ |
| protected boolean error = false; |
| |
| |
| /** |
| * The set of Cookies associated with this Response. |
| */ |
| protected ArrayList cookies = new ArrayList(); |
| |
| |
| /** |
| * Using output stream flag. |
| */ |
| protected boolean usingOutputStream = false; |
| |
| |
| /** |
| * Using writer flag. |
| */ |
| protected boolean usingWriter = false; |
| |
| |
| /** |
| * URL encoder. |
| */ |
| protected UEncoder urlEncoder = new UEncoder(); |
| |
| |
| /** |
| * Recyclable buffer to hold the redirect URL. |
| */ |
| protected CharChunk redirectURLCC = new CharChunk(); |
| |
| |
| // --------------------------------------------------------- Public Methods |
| |
| |
| /** |
| * Release all object references, and initialize instance variables, in |
| * preparation for reuse of this object. |
| */ |
| public void recycle() { |
| |
| outputBuffer.recycle(); |
| usingOutputStream = false; |
| usingWriter = false; |
| appCommitted = false; |
| included = false; |
| error = false; |
| isContentTypeSet = false; |
| isCharacterEncodingSet = false; |
| |
| cookies.clear(); |
| |
| if (Constants.SECURITY) { |
| if (facade != null) { |
| facade.clear(); |
| facade = null; |
| } |
| if (outputStream != null) { |
| outputStream.clear(); |
| outputStream = null; |
| } |
| if (writer != null) { |
| writer.clear(); |
| writer = null; |
| } |
| } else { |
| writer.recycle(); |
| } |
| |
| } |
| |
| |
| // ------------------------------------------------------- Response Methods |
| |
| |
| /** |
| * Return the number of bytes actually written to the output stream. |
| */ |
| public int getContentCount() { |
| return outputBuffer.getContentWritten(); |
| } |
| |
| |
| /** |
| * Set the application commit flag. |
| * |
| * @param appCommitted The new application committed flag value |
| */ |
| public void setAppCommitted(boolean appCommitted) { |
| this.appCommitted = appCommitted; |
| } |
| |
| |
| /** |
| * Application commit flag accessor. |
| */ |
| public boolean isAppCommitted() { |
| return (this.appCommitted || isCommitted() || isSuspended() |
| || ((getContentLength() != -1) |
| && (getContentCount() >= getContentLength()))); |
| } |
| |
| |
| /** |
| * Return the "processing inside an include" flag. |
| */ |
| public boolean getIncluded() { |
| return included; |
| } |
| |
| |
| /** |
| * Set the "processing inside an include" flag. |
| * |
| * @param included <code>true</code> if we are currently inside a |
| * RequestDispatcher.include(), else <code>false</code> |
| */ |
| public void setIncluded(boolean included) { |
| this.included = included; |
| } |
| |
| |
| /** |
| * Return descriptive information about this Response implementation and |
| * the corresponding version number, in the format |
| * <code><description>/<version></code>. |
| */ |
| public String getInfo() { |
| return (info); |
| } |
| |
| |
| /** |
| * The request with which this response is associated. |
| */ |
| protected Request request = null; |
| |
| /** |
| * Return the Request with which this Response is associated. |
| */ |
| public org.apache.catalina.connector.Request getRequest() { |
| return (this.request); |
| } |
| |
| /** |
| * Set the Request with which this Response is associated. |
| * |
| * @param request The new associated request |
| */ |
| public void setRequest(org.apache.catalina.connector.Request request) { |
| this.request = (Request) request; |
| } |
| |
| |
| /** |
| * The facade associated with this response. |
| */ |
| protected ResponseFacade facade = null; |
| |
| /** |
| * Return the <code>ServletResponse</code> for which this object |
| * is the facade. |
| */ |
| public HttpServletResponse getResponse() { |
| if (facade == null) { |
| facade = new ResponseFacade(this); |
| } |
| return (facade); |
| } |
| |
| |
| /** |
| * Return the output stream associated with this Response. |
| */ |
| public OutputStream getStream() { |
| if (outputStream == null) { |
| outputStream = new CoyoteOutputStream(outputBuffer); |
| } |
| return outputStream; |
| } |
| |
| |
| /** |
| * Set the output stream associated with this Response. |
| * |
| * @param stream The new output stream |
| */ |
| public void setStream(OutputStream stream) { |
| // This method is evil |
| } |
| |
| |
| /** |
| * Set the suspended flag. |
| * |
| * @param suspended The new suspended flag value |
| */ |
| public void setSuspended(boolean suspended) { |
| outputBuffer.setSuspended(suspended); |
| } |
| |
| |
| /** |
| * Suspended flag accessor. |
| */ |
| public boolean isSuspended() { |
| return outputBuffer.isSuspended(); |
| } |
| |
| |
| /** |
| * Set the error flag. |
| */ |
| public void setError() { |
| error = true; |
| } |
| |
| |
| /** |
| * Error flag accessor. |
| */ |
| public boolean isError() { |
| return error; |
| } |
| |
| |
| /** |
| * Create and return a ServletOutputStream to write the content |
| * associated with this Response. |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| public ServletOutputStream createOutputStream() |
| throws IOException { |
| // Probably useless |
| if (outputStream == null) { |
| outputStream = new CoyoteOutputStream(outputBuffer); |
| } |
| return outputStream; |
| } |
| |
| |
| /** |
| * Perform whatever actions are required to flush and close the output |
| * stream or writer, in a single operation. |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| public void finishResponse() |
| throws IOException { |
| // Writing leftover bytes |
| try { |
| outputBuffer.close(); |
| } catch(IOException e) { |
| ; |
| } catch(Throwable t) { |
| t.printStackTrace(); |
| } |
| } |
| |
| |
| /** |
| * Return the content length that was set or calculated for this Response. |
| */ |
| public int getContentLength() { |
| return (coyoteResponse.getContentLength()); |
| } |
| |
| |
| /** |
| * Return the content type that was set or calculated for this response, |
| * or <code>null</code> if no content type was set. |
| */ |
| public String getContentType() { |
| return (coyoteResponse.getContentType()); |
| } |
| |
| |
| /** |
| * Return a PrintWriter that can be used to render error messages, |
| * regardless of whether a stream or writer has already been acquired. |
| * |
| * @return Writer which can be used for error reports. If the response is |
| * not an error report returned using sendError or triggered by an |
| * unexpected exception thrown during the servlet processing |
| * (and only in that case), null will be returned if the response stream |
| * has already been used. |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| public PrintWriter getReporter() throws IOException { |
| if (outputBuffer.isNew()) { |
| outputBuffer.checkConverter(); |
| if (writer == null) { |
| writer = new CoyoteWriter(outputBuffer); |
| } |
| return writer; |
| } else { |
| return null; |
| } |
| } |
| |
| |
| // ------------------------------------------------ ServletResponse Methods |
| |
| |
| /** |
| * Flush the buffer and commit this response. |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| public void flushBuffer() |
| throws IOException { |
| outputBuffer.flush(); |
| } |
| |
| |
| /** |
| * Return the actual buffer size used for this Response. |
| */ |
| public int getBufferSize() { |
| return outputBuffer.getBufferSize(); |
| } |
| |
| |
| /** |
| * Return the character encoding used for this Response. |
| */ |
| public String getCharacterEncoding() { |
| return (coyoteResponse.getCharacterEncoding()); |
| } |
| |
| |
| /** |
| * Return the servlet output stream associated with this Response. |
| * |
| * @exception IllegalStateException if <code>getWriter</code> has |
| * already been called for this response |
| * @exception IOException if an input/output error occurs |
| */ |
| public ServletOutputStream getOutputStream() |
| throws IOException { |
| |
| if (usingWriter) |
| throw new IllegalStateException |
| (sm.getString("coyoteResponse.getOutputStream.ise")); |
| |
| usingOutputStream = true; |
| if (outputStream == null) { |
| outputStream = new CoyoteOutputStream(outputBuffer); |
| } |
| return outputStream; |
| |
| } |
| |
| |
| /** |
| * Return the Locale assigned to this response. |
| */ |
| public Locale getLocale() { |
| return (coyoteResponse.getLocale()); |
| } |
| |
| |
| /** |
| * Return the writer associated with this Response. |
| * |
| * @exception IllegalStateException if <code>getOutputStream</code> has |
| * already been called for this response |
| * @exception IOException if an input/output error occurs |
| */ |
| public PrintWriter getWriter() |
| throws IOException { |
| |
| if (usingOutputStream) |
| throw new IllegalStateException |
| (sm.getString("coyoteResponse.getWriter.ise")); |
| |
| /* |
| * If the response's character encoding has not been specified as |
| * described in <code>getCharacterEncoding</code> (i.e., the method |
| * just returns the default value <code>ISO-8859-1</code>), |
| * <code>getWriter</code> updates it to <code>ISO-8859-1</code> |
| * (with the effect that a subsequent call to getContentType() will |
| * include a charset=ISO-8859-1 component which will also be |
| * reflected in the Content-Type response header, thereby satisfying |
| * the Servlet spec requirement that containers must communicate the |
| * character encoding used for the servlet response's writer to the |
| * client). |
| */ |
| setCharacterEncoding(getCharacterEncoding()); |
| |
| usingWriter = true; |
| outputBuffer.checkConverter(); |
| if (writer == null) { |
| writer = new CoyoteWriter(outputBuffer); |
| } |
| return writer; |
| |
| } |
| |
| |
| /** |
| * Has the output of this response already been committed? |
| */ |
| public boolean isCommitted() { |
| return (coyoteResponse.isCommitted()); |
| } |
| |
| |
| /** |
| * Clear any content written to the buffer. |
| * |
| * @exception IllegalStateException if this response has already |
| * been committed |
| */ |
| public void reset() { |
| |
| if (included) |
| return; // Ignore any call from an included servlet |
| |
| coyoteResponse.reset(); |
| outputBuffer.reset(); |
| } |
| |
| |
| /** |
| * Reset the data buffer but not any status or header information. |
| * |
| * @exception IllegalStateException if the response has already |
| * been committed |
| */ |
| public void resetBuffer() { |
| |
| if (isCommitted()) |
| throw new IllegalStateException |
| (sm.getString("coyoteResponse.resetBuffer.ise")); |
| |
| outputBuffer.reset(); |
| |
| } |
| |
| |
| /** |
| * Set the buffer size to be used for this Response. |
| * |
| * @param size The new buffer size |
| * |
| * @exception IllegalStateException if this method is called after |
| * output has been committed for this response |
| */ |
| public void setBufferSize(int size) { |
| |
| if (isCommitted() || !outputBuffer.isNew()) |
| throw new IllegalStateException |
| (sm.getString("coyoteResponse.setBufferSize.ise")); |
| |
| outputBuffer.setBufferSize(size); |
| |
| } |
| |
| |
| /** |
| * Set the content length (in bytes) for this Response. |
| * |
| * @param length The new content length |
| */ |
| public void setContentLength(int length) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| if (usingWriter) |
| return; |
| |
| coyoteResponse.setContentLength(length); |
| |
| } |
| |
| |
| /** |
| * Set the content type for this Response. |
| * |
| * @param type The new content type |
| */ |
| public void setContentType(String type) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| // Ignore charset if getWriter() has already been called |
| if (usingWriter) { |
| if (type != null) { |
| int index = type.indexOf(";"); |
| if (index != -1) { |
| type = type.substring(0, index); |
| } |
| } |
| } |
| |
| coyoteResponse.setContentType(type); |
| |
| // Check to see if content type contains charset |
| if (type != null) { |
| int index = type.indexOf(";"); |
| if (index != -1) { |
| int len = type.length(); |
| index++; |
| while (index < len && Character.isSpace(type.charAt(index))) { |
| index++; |
| } |
| if (index+7 < len |
| && type.charAt(index) == 'c' |
| && type.charAt(index+1) == 'h' |
| && type.charAt(index+2) == 'a' |
| && type.charAt(index+3) == 'r' |
| && type.charAt(index+4) == 's' |
| && type.charAt(index+5) == 'e' |
| && type.charAt(index+6) == 't' |
| && type.charAt(index+7) == '=') { |
| isCharacterEncodingSet = true; |
| } |
| } |
| } |
| |
| isContentTypeSet = true; |
| } |
| |
| |
| /* |
| * Overrides the name of the character encoding used in the body |
| * of the request. This method must be called prior to reading |
| * request parameters or reading input using getReader(). |
| * |
| * @param charset String containing the name of the chararacter encoding. |
| */ |
| public void setCharacterEncoding(String charset) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| // Ignore any call made after the getWriter has been invoked |
| // The default should be used |
| if (usingWriter) |
| return; |
| |
| coyoteResponse.setCharacterEncoding(charset); |
| isCharacterEncodingSet = true; |
| } |
| |
| |
| |
| /** |
| * Set the Locale that is appropriate for this response, including |
| * setting the appropriate character encoding. |
| * |
| * @param locale The new locale |
| */ |
| public void setLocale(Locale locale) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| coyoteResponse.setLocale(locale); |
| |
| // Ignore any call made after the getWriter has been invoked. |
| // The default should be used |
| if (usingWriter) |
| return; |
| |
| if (isCharacterEncodingSet) { |
| return; |
| } |
| |
| CharsetMapper cm = getContext().getCharsetMapper(); |
| String charset = cm.getCharset( locale ); |
| if ( charset != null ){ |
| coyoteResponse.setCharacterEncoding(charset); |
| } |
| |
| } |
| |
| |
| // --------------------------------------------------- HttpResponse Methods |
| |
| |
| /** |
| * Return an array of all cookies set for this response, or |
| * a zero-length array if no cookies have been set. |
| */ |
| public Cookie[] getCookies() { |
| return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()])); |
| } |
| |
| |
| /** |
| * Return the value for the specified header, or <code>null</code> if this |
| * header has not been set. If more than one value was added for this |
| * name, only the first is returned; use getHeaderValues() to retrieve all |
| * of them. |
| * |
| * @param name Header name to look up |
| */ |
| public String getHeader(String name) { |
| return coyoteResponse.getMimeHeaders().getHeader(name); |
| } |
| |
| |
| /** |
| * Return an array of all the header names set for this response, or |
| * a zero-length array if no headers have been set. |
| */ |
| public String[] getHeaderNames() { |
| |
| MimeHeaders headers = coyoteResponse.getMimeHeaders(); |
| int n = headers.size(); |
| String[] result = new String[n]; |
| for (int i = 0; i < n; i++) { |
| result[i] = headers.getName(i).toString(); |
| } |
| return result; |
| |
| } |
| |
| |
| /** |
| * Return an array of all the header values associated with the |
| * specified header name, or an zero-length array if there are no such |
| * header values. |
| * |
| * @param name Header name to look up |
| */ |
| public String[] getHeaderValues(String name) { |
| |
| Enumeration enumeration = coyoteResponse.getMimeHeaders().values(name); |
| Vector result = new Vector(); |
| while (enumeration.hasMoreElements()) { |
| result.addElement(enumeration.nextElement()); |
| } |
| String[] resultArray = new String[result.size()]; |
| result.copyInto(resultArray); |
| return resultArray; |
| |
| } |
| |
| |
| /** |
| * Return the error message that was set with <code>sendError()</code> |
| * for this Response. |
| */ |
| public String getMessage() { |
| return coyoteResponse.getMessage(); |
| } |
| |
| |
| /** |
| * Return the HTTP status code associated with this Response. |
| */ |
| public int getStatus() { |
| return coyoteResponse.getStatus(); |
| } |
| |
| |
| /** |
| * Reset this response, and specify the values for the HTTP status code |
| * and corresponding message. |
| * |
| * @exception IllegalStateException if this response has already been |
| * committed |
| */ |
| public void reset(int status, String message) { |
| reset(); |
| setStatus(status, message); |
| } |
| |
| |
| // -------------------------------------------- HttpServletResponse Methods |
| |
| |
| /** |
| * Add the specified Cookie to those that will be included with |
| * this Response. |
| * |
| * @param cookie Cookie to be added |
| */ |
| public void addCookie(final Cookie cookie) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| cookies.add(cookie); |
| |
| final StringBuffer sb = new StringBuffer(); |
| if (SecurityUtil.isPackageProtectionEnabled()) { |
| AccessController.doPrivileged(new PrivilegedAction() { |
| public Object run(){ |
| ServerCookie.appendCookieValue |
| (sb, cookie.getVersion(), cookie.getName(), |
| cookie.getValue(), cookie.getPath(), |
| cookie.getDomain(), cookie.getComment(), |
| cookie.getMaxAge(), cookie.getSecure()); |
| return null; |
| } |
| }); |
| } else { |
| ServerCookie.appendCookieValue |
| (sb, cookie.getVersion(), cookie.getName(), cookie.getValue(), |
| cookie.getPath(), cookie.getDomain(), cookie.getComment(), |
| cookie.getMaxAge(), cookie.getSecure()); |
| } |
| |
| // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 ) |
| // RFC2965 is not supported by browsers and the Servlet spec |
| // asks for 2109. |
| addHeader("Set-Cookie", sb.toString()); |
| |
| } |
| |
| |
| /** |
| * Add the specified date header to the specified value. |
| * |
| * @param name Name of the header to set |
| * @param value Date value to be set |
| */ |
| public void addDateHeader(String name, long value) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) { |
| return; |
| } |
| |
| if (format == null) { |
| format = new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER, |
| Locale.US); |
| format.setTimeZone(TimeZone.getTimeZone("GMT")); |
| } |
| |
| addHeader(name, FastHttpDateFormat.formatDate(value, format)); |
| |
| } |
| |
| |
| /** |
| * Add the specified header to the specified value. |
| * |
| * @param name Name of the header to set |
| * @param value Value to be set |
| */ |
| public void addHeader(String name, String value) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| coyoteResponse.addHeader(name, value); |
| |
| } |
| |
| |
| /** |
| * Add the specified integer header to the specified value. |
| * |
| * @param name Name of the header to set |
| * @param value Integer value to be set |
| */ |
| public void addIntHeader(String name, int value) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| addHeader(name, "" + value); |
| |
| } |
| |
| |
| /** |
| * Has the specified header been set already in this response? |
| * |
| * @param name Name of the header to check |
| */ |
| public boolean containsHeader(String name) { |
| return coyoteResponse.containsHeader(name); |
| } |
| |
| |
| /** |
| * Encode the session identifier associated with this response |
| * into the specified redirect URL, if necessary. |
| * |
| * @param url URL to be encoded |
| */ |
| public String encodeRedirectURL(String url) { |
| |
| if (isEncodeable(toAbsolute(url))) { |
| return (toEncoded(url, request.getSession().getId())); |
| } else { |
| return (url); |
| } |
| |
| } |
| |
| |
| /** |
| * Encode the session identifier associated with this response |
| * into the specified redirect URL, if necessary. |
| * |
| * @param url URL to be encoded |
| * |
| * @deprecated As of Version 2.1 of the Java Servlet API, use |
| * <code>encodeRedirectURL()</code> instead. |
| */ |
| public String encodeRedirectUrl(String url) { |
| return (encodeRedirectURL(url)); |
| } |
| |
| |
| /** |
| * Encode the session identifier associated with this response |
| * into the specified URL, if necessary. |
| * |
| * @param url URL to be encoded |
| */ |
| public String encodeURL(String url) { |
| |
| String absolute = toAbsolute(url); |
| if (isEncodeable(absolute)) { |
| // W3c spec clearly said |
| if (url.equalsIgnoreCase("")){ |
| url = absolute; |
| } |
| return (toEncoded(url, request.getSession().getId())); |
| } else { |
| return (url); |
| } |
| |
| } |
| |
| |
| /** |
| * Encode the session identifier associated with this response |
| * into the specified URL, if necessary. |
| * |
| * @param url URL to be encoded |
| * |
| * @deprecated As of Version 2.1 of the Java Servlet API, use |
| * <code>encodeURL()</code> instead. |
| */ |
| public String encodeUrl(String url) { |
| return (encodeURL(url)); |
| } |
| |
| |
| /** |
| * Send an acknowledgment of a request. |
| * |
| * @exception IOException if an input/output error occurs |
| */ |
| public void sendAcknowledgement() |
| throws IOException { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| coyoteResponse.acknowledge(); |
| |
| } |
| |
| |
| /** |
| * Send an error response with the specified status and a |
| * default message. |
| * |
| * @param status HTTP status code to send |
| * |
| * @exception IllegalStateException if this response has |
| * already been committed |
| * @exception IOException if an input/output error occurs |
| */ |
| public void sendError(int status) |
| throws IOException { |
| sendError(status, null); |
| } |
| |
| |
| /** |
| * Send an error response with the specified status and message. |
| * |
| * @param status HTTP status code to send |
| * @param message Corresponding message to send |
| * |
| * @exception IllegalStateException if this response has |
| * already been committed |
| * @exception IOException if an input/output error occurs |
| */ |
| public void sendError(int status, String message) |
| throws IOException { |
| |
| if (isCommitted()) |
| throw new IllegalStateException |
| (sm.getString("coyoteResponse.sendError.ise")); |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| Wrapper wrapper = getRequest().getWrapper(); |
| if (wrapper != null) { |
| wrapper.incrementErrorCount(); |
| } |
| |
| setError(); |
| |
| coyoteResponse.setStatus(status); |
| coyoteResponse.setMessage(message); |
| |
| // Clear any data content that has been buffered |
| resetBuffer(); |
| |
| // Cause the response to be finished (from the application perspective) |
| setSuspended(true); |
| |
| } |
| |
| |
| /** |
| * Send a temporary redirect to the specified redirect location URL. |
| * |
| * @param location Location URL to redirect to |
| * |
| * @exception IllegalStateException if this response has |
| * already been committed |
| * @exception IOException if an input/output error occurs |
| */ |
| public void sendRedirect(String location) |
| throws IOException { |
| |
| if (isCommitted()) |
| throw new IllegalStateException |
| (sm.getString("coyoteResponse.sendRedirect.ise")); |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| // Clear any data content that has been buffered |
| resetBuffer(); |
| |
| // Generate a temporary redirect to the specified location |
| try { |
| String absolute = toAbsolute(location); |
| setStatus(SC_FOUND); |
| setHeader("Location", absolute); |
| } catch (IllegalArgumentException e) { |
| setStatus(SC_NOT_FOUND); |
| } |
| |
| // Cause the response to be finished (from the application perspective) |
| setSuspended(true); |
| |
| } |
| |
| |
| /** |
| * Set the specified date header to the specified value. |
| * |
| * @param name Name of the header to set |
| * @param value Date value to be set |
| */ |
| public void setDateHeader(String name, long value) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) { |
| return; |
| } |
| |
| if (format == null) { |
| format = new SimpleDateFormat(DateTool.HTTP_RESPONSE_DATE_HEADER, |
| Locale.US); |
| format.setTimeZone(TimeZone.getTimeZone("GMT")); |
| } |
| |
| setHeader(name, FastHttpDateFormat.formatDate(value, format)); |
| |
| } |
| |
| |
| /** |
| * Set the specified header to the specified value. |
| * |
| * @param name Name of the header to set |
| * @param value Value to be set |
| */ |
| public void setHeader(String name, String value) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| coyoteResponse.setHeader(name, value); |
| |
| } |
| |
| |
| /** |
| * Set the specified integer header to the specified value. |
| * |
| * @param name Name of the header to set |
| * @param value Integer value to be set |
| */ |
| public void setIntHeader(String name, int value) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| setHeader(name, "" + value); |
| |
| } |
| |
| |
| /** |
| * Set the HTTP status to be returned with this response. |
| * |
| * @param status The new HTTP status |
| */ |
| public void setStatus(int status) { |
| setStatus(status, null); |
| } |
| |
| |
| /** |
| * Set the HTTP status and message to be returned with this response. |
| * |
| * @param status The new HTTP status |
| * @param message The associated text message |
| * |
| * @deprecated As of Version 2.1 of the Java Servlet API, this method |
| * has been deprecated due to the ambiguous meaning of the message |
| * parameter. |
| */ |
| public void setStatus(int status, String message) { |
| |
| if (isCommitted()) |
| return; |
| |
| // Ignore any call from an included servlet |
| if (included) |
| return; |
| |
| coyoteResponse.setStatus(status); |
| coyoteResponse.setMessage(message); |
| |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Return <code>true</code> if the specified URL should be encoded with |
| * a session identifier. This will be true if all of the following |
| * conditions are met: |
| * <ul> |
| * <li>The request we are responding to asked for a valid session |
| * <li>The requested session ID was not received via a cookie |
| * <li>The specified URL points back to somewhere within the web |
| * application that is responding to this request |
| * </ul> |
| * |
| * @param location Absolute URL to be validated |
| */ |
| protected boolean isEncodeable(final String location) { |
| |
| if (location == null) |
| return (false); |
| |
| // Is this an intra-document reference? |
| if (location.startsWith("#")) |
| return (false); |
| |
| // Are we in a valid session that is not using cookies? |
| final HttpServletRequest hreq = request; |
| final HttpSession session = hreq.getSession(false); |
| if (session == null) |
| return (false); |
| if (hreq.isRequestedSessionIdFromCookie()) |
| return (false); |
| |
| if (SecurityUtil.isPackageProtectionEnabled()) { |
| return ((Boolean) |
| AccessController.doPrivileged(new PrivilegedAction() { |
| |
| public Object run(){ |
| return new Boolean(doIsEncodeable(hreq, session, location)); |
| } |
| })).booleanValue(); |
| } else { |
| return doIsEncodeable(hreq, session, location); |
| } |
| } |
| |
| private boolean doIsEncodeable(HttpServletRequest hreq, |
| HttpSession session, |
| String location){ |
| // Is this a valid absolute URL? |
| URL url = null; |
| try { |
| url = new URL(location); |
| } catch (MalformedURLException e) { |
| return (false); |
| } |
| |
| // Does this URL match down to (and including) the context path? |
| if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) |
| return (false); |
| if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) |
| return (false); |
| int serverPort = hreq.getServerPort(); |
| if (serverPort == -1) { |
| if ("https".equals(hreq.getScheme())) |
| serverPort = 443; |
| else |
| serverPort = 80; |
| } |
| int urlPort = url.getPort(); |
| if (urlPort == -1) { |
| if ("https".equals(url.getProtocol())) |
| urlPort = 443; |
| else |
| urlPort = 80; |
| } |
| if (serverPort != urlPort) |
| return (false); |
| |
| String contextPath = getContext().getPath(); |
| if (contextPath != null) { |
| String file = url.getFile(); |
| if ((file == null) || !file.startsWith(contextPath)) |
| return (false); |
| if( file.indexOf(";jsessionid=" + session.getId()) >= 0 ) |
| return (false); |
| } |
| |
| // This URL belongs to our web application, so it is encodeable |
| return (true); |
| |
| } |
| |
| |
| /** |
| * Convert (if necessary) and return the absolute URL that represents the |
| * resource referenced by this possibly relative URL. If this URL is |
| * already absolute, return it unchanged. |
| * |
| * @param location URL to be (possibly) converted and then returned |
| * |
| * @exception IllegalArgumentException if a MalformedURLException is |
| * thrown when converting the relative URL to an absolute one |
| */ |
| private String toAbsolute(String location) { |
| |
| if (location == null) |
| return (location); |
| |
| boolean leadingSlash = location.startsWith("/"); |
| |
| if (leadingSlash || !hasScheme(location)) { |
| |
| redirectURLCC.recycle(); |
| |
| String scheme = request.getScheme(); |
| String name = request.getServerName(); |
| int port = request.getServerPort(); |
| |
| try { |
| redirectURLCC.append(scheme, 0, scheme.length()); |
| redirectURLCC.append("://", 0, 3); |
| redirectURLCC.append(name, 0, name.length()); |
| if ((scheme.equals("http") && port != 80) |
| || (scheme.equals("https") && port != 443)) { |
| redirectURLCC.append(':'); |
| String portS = port + ""; |
| redirectURLCC.append(portS, 0, portS.length()); |
| } |
| if (!leadingSlash) { |
| String relativePath = request.getDecodedRequestURI(); |
| int pos = relativePath.lastIndexOf('/'); |
| relativePath = relativePath.substring(0, pos); |
| |
| String encodedURI = null; |
| final String frelativePath = relativePath; |
| if (SecurityUtil.isPackageProtectionEnabled() ){ |
| try{ |
| encodedURI = (String)AccessController.doPrivileged( |
| new PrivilegedExceptionAction(){ |
| public Object run() throws IOException{ |
| return urlEncoder.encodeURL(frelativePath); |
| } |
| }); |
| } catch (PrivilegedActionException pae){ |
| IllegalArgumentException iae = |
| new IllegalArgumentException(location); |
| jdkCompat.chainException(iae, pae.getException()); |
| throw iae; |
| } |
| } else { |
| encodedURI = urlEncoder.encodeURL(relativePath); |
| } |
| redirectURLCC.append(encodedURI, 0, encodedURI.length()); |
| redirectURLCC.append('/'); |
| } |
| redirectURLCC.append(location, 0, location.length()); |
| } catch (IOException e) { |
| IllegalArgumentException iae = |
| new IllegalArgumentException(location); |
| jdkCompat.chainException(iae, e); |
| throw iae; |
| } |
| |
| return redirectURLCC.toString(); |
| |
| } else { |
| |
| return (location); |
| |
| } |
| |
| } |
| |
| |
| /** |
| * Determine if a URI string has a <code>scheme</code> component. |
| */ |
| private boolean hasScheme(String uri) { |
| int len = uri.length(); |
| for(int i=0; i < len ; i++) { |
| char c = uri.charAt(i); |
| if(c == ':') { |
| return i > 0; |
| } else if(!URL.isSchemeChar(c)) { |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return the specified URL with the specified session identifier |
| * suitably encoded. |
| * |
| * @param url URL to be encoded with the session id |
| * @param sessionId Session id to be included in the encoded URL |
| */ |
| protected String toEncoded(String url, String sessionId) { |
| |
| if ((url == null) || (sessionId == null)) |
| return (url); |
| |
| String path = url; |
| String query = ""; |
| String anchor = ""; |
| int question = url.indexOf('?'); |
| if (question >= 0) { |
| path = url.substring(0, question); |
| query = url.substring(question); |
| } |
| int pound = path.indexOf('#'); |
| if (pound >= 0) { |
| anchor = path.substring(pound); |
| path = path.substring(0, pound); |
| } |
| StringBuffer sb = new StringBuffer(path); |
| if( sb.length() > 0 ) { // jsessionid can't be first. |
| sb.append(";jsessionid="); |
| sb.append(sessionId); |
| } |
| sb.append(anchor); |
| sb.append(query); |
| return (sb.toString()); |
| |
| } |
| |
| |
| } |
| |