/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
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.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.SessionTrackingMode;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Session;
import org.apache.catalina.security.SecurityUtil;
import org.apache.catalina.util.SessionConfig;
import org.apache.coyote.ActionCode;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.CharChunk;
import org.apache.tomcat.util.buf.UEncoder;
import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.apache.tomcat.util.http.MimeHeaders;
import org.apache.tomcat.util.http.parser.MediaTypeCache;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.security.Escape;

Wrapper object for the Coyote response.
Author:Remy Maucherat, Craig R. McClanahan
/** * Wrapper object for the Coyote response. * * @author Remy Maucherat * @author Craig R. McClanahan */
public class Response implements HttpServletResponse { private static final Log log = LogFactory.getLog(Response.class); protected static final StringManager sm = StringManager.getManager(Response.class); private static final MediaTypeCache MEDIA_TYPE_CACHE = new MediaTypeCache(100);
Compliance with SRV.15.2.22.1. A call to Response.getWriter() if no character encoding has been specified will result in subsequent calls to Response.getCharacterEncoding() returning ISO-8859-1 and the Content-Type response header will include a charset=ISO-8859-1 component.
/** * Compliance with SRV.15.2.22.1. A call to Response.getWriter() if no * character encoding has been specified will result in subsequent calls to * Response.getCharacterEncoding() returning ISO-8859-1 and the Content-Type * response header will include a charset=ISO-8859-1 component. */
private static final boolean ENFORCE_ENCODING_IN_GET_WRITER; static { ENFORCE_ENCODING_IN_GET_WRITER = Boolean.parseBoolean( System.getProperty("org.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER", "true")); } // ----------------------------------------------------- Instance Variables
The date format we will use for creating date headers.
Deprecated:Unused. This will be removed in Tomcat 10
/** * The date format we will use for creating date headers. * * @deprecated Unused. This will be removed in Tomcat 10 */
@Deprecated protected SimpleDateFormat format = null; public Response() { this(OutputBuffer.DEFAULT_BUFFER_SIZE); } public Response(int outputBufferSize) { outputBuffer = new OutputBuffer(outputBufferSize); } // ------------------------------------------------------------- Properties
Coyote response.
/** * Coyote response. */
protected org.apache.coyote.Response coyoteResponse;
Set the Coyote response.
Params:
  • coyoteResponse – The Coyote response
/** * Set the Coyote response. * * @param coyoteResponse The Coyote response */
public void setCoyoteResponse(org.apache.coyote.Response coyoteResponse) { this.coyoteResponse = coyoteResponse; outputBuffer.setResponse(coyoteResponse); }
Returns:the Coyote response.
/** * @return the Coyote response. */
public org.apache.coyote.Response getCoyoteResponse() { return this.coyoteResponse; }
Returns:the Context within which this Request is being processed.
/** * @return the Context within which this Request is being processed. */
public Context getContext() { return request.getContext(); }
The associated output buffer.
/** * The associated output buffer. */
protected final OutputBuffer outputBuffer;
The associated output stream.
/** * The associated output stream. */
protected CoyoteOutputStream outputStream;
The associated writer.
/** * The associated writer. */
protected CoyoteWriter writer;
The application commit flag.
/** * The application commit flag. */
protected boolean appCommitted = false;
The included flag.
/** * The included flag. */
protected boolean included = false;
The characterEncoding flag
/** * The characterEncoding flag */
private boolean isCharacterEncodingSet = false;
Using output stream flag.
/** * Using output stream flag. */
protected boolean usingOutputStream = false;
Using writer flag.
/** * Using writer flag. */
protected boolean usingWriter = false;
URL encoder.
/** * URL encoder. */
protected final UEncoder urlEncoder = new UEncoder(SafeCharsSet.WITH_SLASH);
Recyclable buffer to hold the redirect URL.
/** * Recyclable buffer to hold the redirect URL. */
protected final CharChunk redirectURLCC = new CharChunk(); /* * Not strictly required but it makes generating HTTP/2 push requests a lot * easier if these are retained until the response is recycled. */ private final List<Cookie> cookies = new ArrayList<>(); private HttpServletResponse applicationResponse = null; // --------------------------------------------------------- Public Methods
Release all object references, and initialize instance variables, in preparation for reuse of this object.
/** * Release all object references, and initialize instance variables, in * preparation for reuse of this object. */
public void recycle() { cookies.clear(); outputBuffer.recycle(); usingOutputStream = false; usingWriter = false; appCommitted = false; included = false; isCharacterEncodingSet = false; applicationResponse = null; if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) { if (facade != null) { facade.clear(); facade = null; } if (outputStream != null) { outputStream.clear(); outputStream = null; } if (writer != null) { writer.clear(); writer = null; } } else if (writer != null) { writer.recycle(); } } public List<Cookie> getCookies() { return cookies; } // ------------------------------------------------------- Response Methods
Returns:the number of bytes the application has actually written to the output stream. This excludes chunking, compression, etc. as well as headers.
/** * @return the number of bytes the application has actually written to the * output stream. This excludes chunking, compression, etc. as well as * headers. */
public long getContentWritten() { return outputBuffer.getContentWritten(); }
Params:
  • flush – if true will perform a buffer flush first
Returns:the number of bytes the actually written to the socket. This includes chunking, compression, etc. but excludes headers.
/** * @return the number of bytes the actually written to the socket. This * includes chunking, compression, etc. but excludes headers. * @param flush if <code>true</code> will perform a buffer flush first */
public long getBytesWritten(boolean flush) { if (flush) { try { outputBuffer.flush(); } catch (IOException ioe) { // Ignore - the client has probably closed the connection } } return getCoyoteResponse().getBytesWritten(flush); }
Set the application commit flag.
Params:
  • appCommitted – The new application committed flag value
/** * 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.
Returns:true if the application has committed the response
/** * Application commit flag accessor. * * @return <code>true</code> if the application has committed the response */
public boolean isAppCommitted() { return this.appCommitted || isCommitted() || isSuspended() || ((getContentLength() > 0) && (getContentWritten() >= getContentLength())); }
The request with which this response is associated.
/** * The request with which this response is associated. */
protected Request request = null;
Returns:the Request with which this Response is associated.
/** * @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.
Params:
  • request – The new associated 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; }
The facade associated with this response.
/** * The facade associated with this response. */
protected ResponseFacade facade = null;
Returns:the ServletResponse for which this object is the facade.
/** * @return the <code>ServletResponse</code> for which this object * is the facade. */
public HttpServletResponse getResponse() { if (facade == null) { facade = new ResponseFacade(this); } if (applicationResponse == null) { applicationResponse = facade; } return applicationResponse; }
Set a wrapped HttpServletResponse to pass to the application. Components wishing to wrap the response should obtain the response via getResponse(), wrap it and then call this method with the wrapped response.
Params:
  • applicationResponse – The wrapped response to pass to the application
/** * Set a wrapped HttpServletResponse to pass to the application. Components * wishing to wrap the response should obtain the response via * {@link #getResponse()}, wrap it and then call this method with the * wrapped response. * * @param applicationResponse The wrapped response to pass to the * application */
public void setResponse(HttpServletResponse applicationResponse) { // Check the wrapper wraps this request ServletResponse r = applicationResponse; while (r instanceof HttpServletResponseWrapper) { r = ((HttpServletResponseWrapper) r).getResponse(); } if (r != facade) { throw new IllegalArgumentException(sm.getString("response.illegalWrap")); } this.applicationResponse = applicationResponse; }
Set the suspended flag.
Params:
  • suspended – The new suspended flag value
/** * Set the suspended flag. * * @param suspended The new suspended flag value */
public void setSuspended(boolean suspended) { outputBuffer.setSuspended(suspended); }
Suspended flag accessor.
Returns:true if the response is suspended
/** * Suspended flag accessor. * * @return <code>true</code> if the response is suspended */
public boolean isSuspended() { return outputBuffer.isSuspended(); }
Closed flag accessor.
Returns:true if the response has been closed
/** * Closed flag accessor. * * @return <code>true</code> if the response has been closed */
public boolean isClosed() { return outputBuffer.isClosed(); }
Set the error flag.
Returns:false if the error flag was already set
/** * Set the error flag. * * @return <code>false</code> if the error flag was already set */
public boolean setError() { return getCoyoteResponse().setError(); }
Error flag accessor.
Returns:true if the response has encountered an error
/** * Error flag accessor. * * @return <code>true</code> if the response has encountered an error */
public boolean isError() { return getCoyoteResponse().isError(); } public boolean isErrorReportRequired() { return getCoyoteResponse().isErrorReportRequired(); } public boolean setErrorReported() { return getCoyoteResponse().setErrorReported(); }
Perform whatever actions are required to flush and close the output stream or writer, in a single operation.
Throws:
  • IOException – if an input/output error occurs
/** * 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 outputBuffer.close(); }
Returns:the content length that was set or calculated for this Response.
/** * @return the content length that was set or calculated for this Response. */
public int getContentLength() { return getCoyoteResponse().getContentLength(); }
Returns:the content type that was set or calculated for this response, or null if no content type was set.
/** * @return the content type that was set or calculated for this response, * or <code>null</code> if no content type was set. */
@Override public String getContentType() { return getCoyoteResponse().getContentType(); }
Return a PrintWriter that can be used to render error messages, regardless of whether a stream or writer has already been acquired.
Throws:
Returns: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.
/** * 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.
Throws:
  • IOException – if an input/output error occurs
/** * Flush the buffer and commit this response. * * @exception IOException if an input/output error occurs */
@Override public void flushBuffer() throws IOException { outputBuffer.flush(); }
Returns:the actual buffer size used for this Response.
/** * @return the actual buffer size used for this Response. */
@Override public int getBufferSize() { return outputBuffer.getBufferSize(); }
Returns:the character encoding used for this Response.
/** * @return the character encoding used for this Response. */
@Override public String getCharacterEncoding() { String charset = getCoyoteResponse().getCharacterEncoding(); if (charset != null) { return charset; } Context context = getContext(); String result = null; if (context != null) { result = context.getResponseCharacterEncoding(); } if (result == null) { result = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET.name(); } return result; }
Throws:
Returns:the servlet output stream associated with this Response.
/** * @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 */
@Override 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; }
Returns:the Locale assigned to this response.
/** * @return the Locale assigned to this response. */
@Override public Locale getLocale() { return getCoyoteResponse().getLocale(); }
Throws:
Returns:the writer associated with this Response.
/** * @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 */
@Override public PrintWriter getWriter() throws IOException { if (usingOutputStream) { throw new IllegalStateException (sm.getString("coyoteResponse.getWriter.ise")); } if (ENFORCE_ENCODING_IN_GET_WRITER) { /* * 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?
Returns:true if the response has been committed
/** * Has the output of this response already been committed? * * @return <code>true</code> if the response has been committed */
@Override public boolean isCommitted() { return getCoyoteResponse().isCommitted(); }
Clear any content written to the buffer.
Throws:
  • IllegalStateException – if this response has already been committed
/** * Clear any content written to the buffer. * * @exception IllegalStateException if this response has already * been committed */
@Override public void reset() { // Ignore any call from an included servlet if (included) { return; } getCoyoteResponse().reset(); outputBuffer.reset(); usingOutputStream = false; usingWriter = false; isCharacterEncodingSet = false; }
Reset the data buffer but not any status or header information.
Throws:
  • IllegalStateException – if the response has already been committed
/** * Reset the data buffer but not any status or header information. * * @exception IllegalStateException if the response has already * been committed */
@Override public void resetBuffer() { resetBuffer(false); }
Reset the data buffer and the using Writer/Stream flags but not any status or header information.
Params:
  • resetWriterStreamFlags – true if the internal usingWriter, usingOutputStream, isCharacterEncodingSet flags should also be reset
Throws:
/** * Reset the data buffer and the using Writer/Stream flags but not any * status or header information. * * @param resetWriterStreamFlags <code>true</code> if the internal * <code>usingWriter</code>, <code>usingOutputStream</code>, * <code>isCharacterEncodingSet</code> flags should also be reset * * @exception IllegalStateException if the response has already * been committed */
public void resetBuffer(boolean resetWriterStreamFlags) { if (isCommitted()) { throw new IllegalStateException (sm.getString("coyoteResponse.resetBuffer.ise")); } outputBuffer.reset(resetWriterStreamFlags); if(resetWriterStreamFlags) { usingOutputStream = false; usingWriter = false; isCharacterEncodingSet = false; } }
Set the buffer size to be used for this Response.
Params:
  • size – The new buffer size
Throws:
/** * 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 */
@Override 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.
Params:
  • length – The new content length
/** * Set the content length (in bytes) for this Response. * * @param length The new content length */
@Override public void setContentLength(int length) { setContentLengthLong(length); } @Override public void setContentLengthLong(long length) { if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } getCoyoteResponse().setContentLength(length); }
Set the content type for this Response.
Params:
  • type – The new content type
/** * Set the content type for this Response. * * @param type The new content type */
@Override public void setContentType(String type) { if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } if (type == null) { getCoyoteResponse().setContentType(null); return; } String[] m = MEDIA_TYPE_CACHE.parse(type); if (m == null) { // Invalid - Assume no charset and just pass through whatever // the user provided. getCoyoteResponse().setContentTypeNoCharset(type); return; } getCoyoteResponse().setContentTypeNoCharset(m[0]); if (m[1] != null) { // Ignore charset if getWriter() has already been called if (!usingWriter) { try { getCoyoteResponse().setCharacterEncoding(m[1]); } catch (UnsupportedEncodingException e) { log.warn(sm.getString("coyoteResponse.encoding.invalid", m[1]), e); } isCharacterEncodingSet = 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().
Params:
  • charset – String containing the name of the character encoding.
/** * 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 character encoding. */
@Override 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; } try { getCoyoteResponse().setCharacterEncoding(charset); } catch (UnsupportedEncodingException e) { log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), e); return; } isCharacterEncodingSet = true; }
Set the Locale that is appropriate for this response, including setting the appropriate character encoding.
Params:
  • locale – The new locale
/** * Set the Locale that is appropriate for this response, including * setting the appropriate character encoding. * * @param locale The new locale */
@Override public void setLocale(Locale locale) { if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } getCoyoteResponse().setLocale(locale); // Ignore any call made after the getWriter has been invoked. // The default should be used if (usingWriter) { return; } if (isCharacterEncodingSet) { return; } // In some error handling scenarios, the context is unknown // (e.g. a 404 when a ROOT context is not present) Context context = getContext(); if (context != null) { String charset = context.getCharset(locale); if (charset != null) { try { getCoyoteResponse().setCharacterEncoding(charset); } catch (UnsupportedEncodingException e) { log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), e); } } } } // --------------------------------------------------- HttpResponse Methods @Override public String getHeader(String name) { return getCoyoteResponse().getMimeHeaders().getHeader(name); } @Override public Collection<String> getHeaderNames() { MimeHeaders headers = getCoyoteResponse().getMimeHeaders(); int n = headers.size(); List<String> result = new ArrayList<>(n); for (int i = 0; i < n; i++) { result.add(headers.getName(i).toString()); } return result; } @Override public Collection<String> getHeaders(String name) { Enumeration<String> enumeration = getCoyoteResponse().getMimeHeaders().values(name); List<String> result = new ArrayList<>(); while (enumeration.hasMoreElements()) { result.add(enumeration.nextElement()); } return result; }
Returns:the error message that was set with sendError() for this Response.
/** * @return the error message that was set with <code>sendError()</code> * for this Response. */
public String getMessage() { return getCoyoteResponse().getMessage(); } @Override public int getStatus() { return getCoyoteResponse().getStatus(); } // -------------------------------------------- HttpServletResponse Methods
Add the specified Cookie to those that will be included with this Response.
Params:
  • cookie – Cookie to be added
/** * Add the specified Cookie to those that will be included with * this Response. * * @param cookie Cookie to be added */
@Override public void addCookie(final Cookie cookie) { // Ignore any call from an included servlet if (included || isCommitted()) { return; } cookies.add(cookie); String header = generateCookieString(cookie); //if we reached here, no exception, cookie is valid // 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", header, getContext().getCookieProcessor().getCharset()); }
Special method for adding a session cookie as we should be overriding any previous.
Params:
  • cookie – The new session cookie to add the response
/** * Special method for adding a session cookie as we should be overriding * any previous. * * @param cookie The new session cookie to add the response */
public void addSessionCookieInternal(final Cookie cookie) { if (isCommitted()) { return; } String name = cookie.getName(); final String headername = "Set-Cookie"; final String startsWith = name + "="; String header = generateCookieString(cookie); boolean set = false; MimeHeaders headers = getCoyoteResponse().getMimeHeaders(); int n = headers.size(); for (int i = 0; i < n; i++) { if (headers.getName(i).toString().equals(headername)) { if (headers.getValue(i).toString().startsWith(startsWith)) { headers.getValue(i).setString(header); set = true; } } } if (!set) { addHeader(headername, header); } } public String generateCookieString(final Cookie cookie) { // Web application code can receive a IllegalArgumentException // from the generateHeader() invocation if (SecurityUtil.isPackageProtectionEnabled()) { return AccessController.doPrivileged( new PrivilegedGenerateCookieString(getContext(), cookie)); } else { return getContext().getCookieProcessor().generateHeader(cookie); } }
Add the specified date header to the specified value.
Params:
  • name – Name of the header to set
  • value – Date value to be set
/** * Add the specified date header to the specified value. * * @param name Name of the header to set * @param value Date value to be set */
@Override public void addDateHeader(String name, long value) { if (name == null || name.length() == 0) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } addHeader(name, FastHttpDateFormat.formatDate(value)); }
Add the specified header to the specified value.
Params:
  • name – Name of the header to set
  • value – Value to be set
/** * Add the specified header to the specified value. * * @param name Name of the header to set * @param value Value to be set */
@Override public void addHeader(String name, String value) { addHeader(name, value, null); } private void addHeader(String name, String value, Charset charset) { if (name == null || name.length() == 0 || value == null) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } char cc=name.charAt(0); if (cc=='C' || cc=='c') { if (checkSpecialHeader(name, value)) return; } getCoyoteResponse().addHeader(name, value, charset); }
An extended version of this exists in Response. This check is required here to ensure that the usingWriter check in setContentType(String) is applied since usingWriter is not visible to Response Called from set/addHeader.
Returns:true if the header is special, no need to set the header.
/** * An extended version of this exists in {@link org.apache.coyote.Response}. * This check is required here to ensure that the usingWriter check in * {@link #setContentType(String)} is applied since usingWriter is not * visible to {@link org.apache.coyote.Response} * * Called from set/addHeader. * @return <code>true</code> if the header is special, no need to set the header. */
private boolean checkSpecialHeader(String name, String value) { if (name.equalsIgnoreCase("Content-Type")) { setContentType(value); return true; } return false; }
Add the specified integer header to the specified value.
Params:
  • name – Name of the header to set
  • value – Integer value to be set
/** * Add the specified integer header to the specified value. * * @param name Name of the header to set * @param value Integer value to be set */
@Override public void addIntHeader(String name, int value) { if (name == null || name.length() == 0) { return; } 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?
Params:
  • name – Name of the header to check
Returns:true if the header has been set
/** * Has the specified header been set already in this response? * * @param name Name of the header to check * @return <code>true</code> if the header has been set */
@Override public boolean containsHeader(String name) { // Need special handling for Content-Type and Content-Length due to // special handling of these in coyoteResponse char cc=name.charAt(0); if(cc=='C' || cc=='c') { if(name.equalsIgnoreCase("Content-Type")) { // Will return null if this has not been set return (getCoyoteResponse().getContentType() != null); } if(name.equalsIgnoreCase("Content-Length")) { // -1 means not known and is not sent to client return (getCoyoteResponse().getContentLengthLong() != -1); } } return getCoyoteResponse().containsHeader(name); } @Override public void setTrailerFields(Supplier<Map<String, String>> supplier) { getCoyoteResponse().setTrailerFields(supplier); } @Override public Supplier<Map<String, String>> getTrailerFields() { return getCoyoteResponse().getTrailerFields(); }
Encode the session identifier associated with this response into the specified redirect URL, if necessary.
Params:
  • url – URL to be encoded
Returns:true if the URL was encoded
/** * Encode the session identifier associated with this response * into the specified redirect URL, if necessary. * * @param url URL to be encoded * @return <code>true</code> if the URL was encoded */
@Override public String encodeRedirectURL(String url) { if (isEncodeable(toAbsolute(url))) { return toEncoded(url, request.getSessionInternal().getIdInternal()); } else { return url; } }
Encode the session identifier associated with this response into the specified redirect URL, if necessary.
Params:
  • url – URL to be encoded
Returns:true if the URL was encoded
Deprecated:As of Version 2.1 of the Java Servlet API, use encodeRedirectURL() instead.
/** * Encode the session identifier associated with this response * into the specified redirect URL, if necessary. * * @param url URL to be encoded * @return <code>true</code> if the URL was encoded * * @deprecated As of Version 2.1 of the Java Servlet API, use * <code>encodeRedirectURL()</code> instead. */
@Override @Deprecated public String encodeRedirectUrl(String url) { return encodeRedirectURL(url); }
Encode the session identifier associated with this response into the specified URL, if necessary.
Params:
  • url – URL to be encoded
Returns:true if the URL was encoded
/** * Encode the session identifier associated with this response * into the specified URL, if necessary. * * @param url URL to be encoded * @return <code>true</code> if the URL was encoded */
@Override public String encodeURL(String url) { String absolute; try { absolute = toAbsolute(url); } catch (IllegalArgumentException iae) { // Relative URL return url; } if (isEncodeable(absolute)) { // W3c spec clearly said if (url.equalsIgnoreCase("")) { url = absolute; } else if (url.equals(absolute) && !hasPath(url)) { url += '/'; } return toEncoded(url, request.getSessionInternal().getIdInternal()); } else { return url; } }
Encode the session identifier associated with this response into the specified URL, if necessary.
Params:
  • url – URL to be encoded
Returns:true if the URL was encoded
Deprecated:As of Version 2.1 of the Java Servlet API, use encodeURL() instead.
/** * Encode the session identifier associated with this response * into the specified URL, if necessary. * * @param url URL to be encoded * @return <code>true</code> if the URL was encoded * * @deprecated As of Version 2.1 of the Java Servlet API, use * <code>encodeURL()</code> instead. */
@Override @Deprecated public String encodeUrl(String url) { return encodeURL(url); }
Send an acknowledgement of a request.
Throws:
  • IOException – if an input/output error occurs
/** * Send an acknowledgement 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; } getCoyoteResponse().action(ActionCode.ACK, null); }
Send an error response with the specified status and a default message.
Params:
  • status – HTTP status code to send
Throws:
/** * 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 */
@Override public void sendError(int status) throws IOException { sendError(status, null); }
Send an error response with the specified status and message.
Params:
  • status – HTTP status code to send
  • message – Corresponding message to send
Throws:
/** * 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 */
@Override 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; } setError(); getCoyoteResponse().setStatus(status); getCoyoteResponse().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.
Params:
  • location – Location URL to redirect to
Throws:
/** * 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 */
@Override public void sendRedirect(String location) throws IOException { sendRedirect(location, SC_FOUND); }
Internal method that allows a redirect to be sent with a status other than HttpServletResponse.SC_FOUND (302). No attempt is made to validate the status code.
Params:
  • location – Location URL to redirect to
  • status – HTTP status code that will be sent
Throws:
/** * Internal method that allows a redirect to be sent with a status other * than {@link HttpServletResponse#SC_FOUND} (302). No attempt is made to * validate the status code. * * @param location Location URL to redirect to * @param status HTTP status code that will be sent * @throws IOException an IO exception occurred */
public void sendRedirect(String location, int status) 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(true); // Generate a temporary redirect to the specified location try { String locationUri; // Relative redirects require HTTP/1.1 if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() && getContext().getUseRelativeRedirects()) { locationUri = location; } else { locationUri = toAbsolute(location); } setStatus(status); setHeader("Location", locationUri); if (getContext().getSendRedirectBody()) { PrintWriter writer = getWriter(); writer.print(sm.getString("coyoteResponse.sendRedirect.note", Escape.htmlElementContent(locationUri))); flushBuffer(); } } catch (IllegalArgumentException e) { log.warn(sm.getString("response.sendRedirectFail", location), 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.
Params:
  • name – Name of the header to set
  • value – Date value to be set
/** * Set the specified date header to the specified value. * * @param name Name of the header to set * @param value Date value to be set */
@Override public void setDateHeader(String name, long value) { if (name == null || name.length() == 0) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } setHeader(name, FastHttpDateFormat.formatDate(value)); }
Set the specified header to the specified value.
Params:
  • name – Name of the header to set
  • value – Value to be set
/** * Set the specified header to the specified value. * * @param name Name of the header to set * @param value Value to be set */
@Override public void setHeader(String name, String value) { if (name == null || name.length() == 0 || value == null) { return; } if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } char cc=name.charAt(0); if (cc=='C' || cc=='c') { if (checkSpecialHeader(name, value)) { return; } } getCoyoteResponse().setHeader(name, value); }
Set the specified integer header to the specified value.
Params:
  • name – Name of the header to set
  • value – Integer value to be set
/** * Set the specified integer header to the specified value. * * @param name Name of the header to set * @param value Integer value to be set */
@Override public void setIntHeader(String name, int value) { if (name == null || name.length() == 0) { return; } 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.
Params:
  • status – The new HTTP status
/** * Set the HTTP status to be returned with this response. * * @param status The new HTTP status */
@Override public void setStatus(int status) { setStatus(status, null); }
Set the HTTP status and message to be returned with this response.
Params:
  • status – The new HTTP status
  • 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.
/** * 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. */
@Override @Deprecated public void setStatus(int status, String message) { if (isCommitted()) { return; } // Ignore any call from an included servlet if (included) { return; } getCoyoteResponse().setStatus(status); getCoyoteResponse().setMessage(message); } // ------------------------------------------------------ Protected Methods
Return true if the specified URL should be encoded with a session identifier. This will be true if all of the following conditions are met:
  • The request we are responding to asked for a valid session
  • The requested session ID was not received via a cookie
  • The specified URL points back to somewhere within the web application that is responding to this request
Params:
  • location – Absolute URL to be validated
Returns:true if the URL should be encoded
/** * 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 * @return <code>true</code> if the URL should be encoded */
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 Request hreq = request; final Session session = hreq.getSessionInternal(false); if (session == null) { return false; } if (hreq.isRequestedSessionIdFromCookie()) { return false; } // Is URL encoding permitted if (!hreq.getServletContext().getEffectiveSessionTrackingModes(). contains(SessionTrackingMode.URL)) { return false; } if (SecurityUtil.isPackageProtectionEnabled()) { Boolean result = AccessController.doPrivileged( new PrivilegedDoIsEncodable(getContext(), hreq, session, location)); return result.booleanValue(); } else { return doIsEncodeable(getContext(), hreq, session, location); } } private static boolean doIsEncodeable(Context context, Request hreq, Session 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 = context.getPath(); if (contextPath != null) { String file = url.getFile(); if (!file.startsWith(contextPath)) { return false; } String tok = ";" + SessionConfig.getSessionUriParamName(context) + "=" + session.getIdInternal(); if( file.indexOf(tok, contextPath.length()) >= 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.
Params:
  • location – URL to be (possibly) converted and then returned
Throws:
Returns:the encoded URL
/** * 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 * @return the encoded URL * * @exception IllegalArgumentException if a MalformedURLException is * thrown when converting the relative URL to an absolute one */
protected String toAbsolute(String location) { if (location == null) { return location; } boolean leadingSlash = location.startsWith("/"); if (location.startsWith("//")) { // Scheme relative redirectURLCC.recycle(); // Add the scheme String scheme = request.getScheme(); try { redirectURLCC.append(scheme, 0, scheme.length()); redirectURLCC.append(':'); redirectURLCC.append(location, 0, location.length()); return redirectURLCC.toString(); } catch (IOException e) { IllegalArgumentException iae = new IllegalArgumentException(location); iae.initCause(e); throw iae; } } else if (leadingSlash || !UriUtil.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('/'); CharChunk encodedURI = null; if (SecurityUtil.isPackageProtectionEnabled() ){ try{ encodedURI = AccessController.doPrivileged( new PrivilgedEncodeUrl(urlEncoder, relativePath, pos)); } catch (PrivilegedActionException pae){ IllegalArgumentException iae = new IllegalArgumentException(location); iae.initCause(pae.getException()); throw iae; } } else { encodedURI = urlEncoder.encodeURL(relativePath, 0, pos); } redirectURLCC.append(encodedURI); encodedURI.recycle(); redirectURLCC.append('/'); } redirectURLCC.append(location, 0, location.length()); normalize(redirectURLCC); } catch (IOException e) { IllegalArgumentException iae = new IllegalArgumentException(location); iae.initCause(e); throw iae; } return redirectURLCC.toString(); } else { return location; } }
Removes /./ and /../ sequences from absolute URLs. Code borrowed heavily from CoyoteAdapter.normalize()
Params:
  • cc – the char chunk containing the chars to normalize
/** * Removes /./ and /../ sequences from absolute URLs. * Code borrowed heavily from CoyoteAdapter.normalize() * * @param cc the char chunk containing the chars to normalize */
private void normalize(CharChunk cc) { // Strip query string and/or fragment first as doing it this way makes // the normalization logic a lot simpler int truncate = cc.indexOf('?'); if (truncate == -1) { truncate = cc.indexOf('#'); } char[] truncateCC = null; if (truncate > -1) { truncateCC = Arrays.copyOfRange(cc.getBuffer(), cc.getStart() + truncate, cc.getEnd()); cc.setEnd(cc.getStart() + truncate); } if (cc.endsWith("/.") || cc.endsWith("/..")) { try { cc.append('/'); } catch (IOException e) { throw new IllegalArgumentException(cc.toString(), e); } } char[] c = cc.getChars(); int start = cc.getStart(); int end = cc.getEnd(); int index = 0; int startIndex = 0; // Advance past the first three / characters (should place index just // scheme://host[:port] for (int i = 0; i < 3; i++) { startIndex = cc.indexOf('/', startIndex + 1); } // Remove /./ index = startIndex; while (true) { index = cc.indexOf("/./", 0, 3, index); if (index < 0) { break; } copyChars(c, start + index, start + index + 2, end - start - index - 2); end = end - 2; cc.setEnd(end); } // Remove /../ index = startIndex; int pos; while (true) { index = cc.indexOf("/../", 0, 4, index); if (index < 0) { break; } // Can't go above the server root if (index == startIndex) { throw new IllegalArgumentException(); } int index2 = -1; for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) { if (c[pos] == (byte) '/') { index2 = pos; } } copyChars(c, start + index2, start + index + 3, end - start - index - 3); end = end + index2 - index - 3; cc.setEnd(end); index = index2; } // Add the query string and/or fragment (if present) back in if (truncateCC != null) { try { cc.append(truncateCC, 0, truncateCC.length); } catch (IOException ioe) { throw new IllegalArgumentException(ioe); } } } private void copyChars(char[] c, int dest, int src, int len) { System.arraycopy(c, src, c, dest, len); }
Determine if an absolute URL has a path component.
Params:
  • uri – the URL that will be checked
Returns:true if the URL has a path
/** * Determine if an absolute URL has a path component. * * @param uri the URL that will be checked * @return <code>true</code> if the URL has a path */
private boolean hasPath(String uri) { int pos = uri.indexOf("://"); if (pos < 0) { return false; } pos = uri.indexOf('/', pos + 3); if (pos < 0) { return false; } return true; }
Return the specified URL with the specified session identifier suitably encoded.
Params:
  • url – URL to be encoded with the session id
  • sessionId – Session id to be included in the encoded URL
Returns:the encoded URL
/** * 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 * @return 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); } StringBuilder sb = new StringBuilder(path); if( sb.length() > 0 ) { // jsessionid can't be first. sb.append(";"); sb.append(SessionConfig.getSessionUriParamName( request.getContext())); sb.append("="); sb.append(sessionId); } sb.append(anchor); sb.append(query); return sb.toString(); } private static class PrivilegedGenerateCookieString implements PrivilegedAction<String> { private final Context context; private final Cookie cookie; public PrivilegedGenerateCookieString(Context context, Cookie cookie) { this.context = context; this.cookie = cookie; } @Override public String run(){ return context.getCookieProcessor().generateHeader(cookie); } } private static class PrivilegedDoIsEncodable implements PrivilegedAction<Boolean> { private final Context context; private final Request hreq; private final Session session; private final String location; public PrivilegedDoIsEncodable(Context context, Request hreq, Session session, String location) { this.context = context; this.hreq = hreq; this.session = session; this.location = location; } @Override public Boolean run(){ return Boolean.valueOf(doIsEncodeable(context, hreq, session, location)); } } private static class PrivilgedEncodeUrl implements PrivilegedExceptionAction<CharChunk> { private final UEncoder urlEncoder; private final String relativePath; private final int end; public PrivilgedEncodeUrl(UEncoder urlEncoder, String relativePath, int end) { this.urlEncoder = urlEncoder; this.relativePath = relativePath; this.end = end; } @Override public CharChunk run() throws IOException{ return urlEncoder.encodeURL(relativePath, 0, end); } } }