All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.connector.Response Maven / Gradle / Ivy

There is a newer version: 11.0.0-M26
Show newest version
/*
 * 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.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.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.Vector;

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

import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.Session;
import org.apache.catalina.Wrapper;
import org.apache.catalina.security.SecurityUtil;
import org.apache.catalina.util.DateTool;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.SessionConfig;
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.http.parser.MediaTypeCache;
import org.apache.tomcat.util.net.URL;
import org.apache.tomcat.util.res.StringManager;

/**
 * Wrapper object for the Coyote response.
 *
 * @author Remy Maucherat
 * @author Craig R. McClanahan
 */
public class Response
    implements HttpServletResponse {


    // ----------------------------------------------------------- Constructors

    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.
     */
    private static final boolean ENFORCE_ENCODING_IN_GET_WRITER;

    static {
        // Ensure that URL is loaded for SM
        URL.isSchemeChar('c');

        ENFORCE_ENCODING_IN_GET_WRITER = Boolean.valueOf(
                System.getProperty("org.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER",
                        "true")).booleanValue();
    }

    public Response() {
        urlEncoder.addSafeCharacter('/');
    }


    // ----------------------------------------------------- Class Variables


    /**
     * Descriptive information about this Response implementation.
     */
    protected static final String info =
        "org.apache.coyote.catalina.CoyoteResponse/1.0";


    /**
     * The string manager for this package.
     */
    protected static final 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.
     * @deprecated  Unused
     */
    @Deprecated
    protected Connector connector;

    /**
     * Return the Connector through which this Request was received.
     */
    @Deprecated
    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;
        if("AJP/1.3".equals(connector.getProtocol())) {
            // default size to size of one ajp-packet
            outputBuffer = new OutputBuffer(8184);
        } else {
            outputBuffer = new OutputBuffer();
        }
        outputStream = new CoyoteOutputStream(outputBuffer);
        writer = new CoyoteWriter(outputBuffer);
    }


    /**
     * 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 getContextPath(),
     * and thus enables parsing of the request URI.
     *
     * @param context The newly associated Context
     */
    @Deprecated
    public void setContext(Context context) {
        request.setContext(context);
    }


    /**
     * The associated output buffer.
     */
    protected OutputBuffer outputBuffer;


    /**
     * The associated output stream.
     */
    protected CoyoteOutputStream outputStream;


    /**
     * The associated writer.
     */
    protected CoyoteWriter writer;


    /**
     * The application commit flag.
     */
    protected boolean appCommitted = false;


    /**
     * The included flag.
     */
    protected boolean included = false;


    /**
     * The characterEncoding flag
     */
    private boolean isCharacterEncodingSet = false;

    /**
     * The error flag.
     */
    protected boolean error = false;


    /**
     * 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;
        isCharacterEncodingSet = false;

        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 {
            writer.recycle();
        }

    }


    /**
     * Clear cached encoders (to save memory for Comet requests).
     */
    public void clearEncoders() {
        outputBuffer.clearEncoders();
    }


    // ------------------------------------------------------- Response Methods


    /**
     * 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();
    }


    /**
     * Return the number of bytes the actually written to the socket. This
     * includes chunking, compression, etc. but excludes headers.
     */
    public long getBytesWritten(boolean flush) {
        if (flush) {
            try {
                outputBuffer.flush();
            } catch (IOException ioe) {
                // Ignore - the client has probably closed the connection
            }
        }
        return coyoteResponse.getBytesWritten(flush);
    }

    /**
     * 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() > 0)
                    && (getContentWritten() >= getContentLength())));
    }


    /**
     * Return the "processing inside an include" flag.
     */
    @Deprecated
    public boolean getIncluded() {
        return included;
    }


    /**
     * Set the "processing inside an include" flag.
     *
     * @param included true if we are currently inside a
     *  RequestDispatcher.include(), else false
     */
    @Deprecated
    public void setIncluded(boolean included) {
        this.included = included;
    }


    /**
     * Return descriptive information about this Response implementation and
     * the corresponding version number, in the format
     * <description>/<version>.
     */
    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;
    }


    /**
     * The facade associated with this response.
     */
    protected ResponseFacade facade = null;

    /**
     * Return the ServletResponse 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.
     */
    @Deprecated
    public OutputStream getStream() {
        if (outputStream == null) {
            outputStream = new CoyoteOutputStream(outputBuffer);
        }
        return outputStream;
    }


    /**
     * 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();
    }


    /**
     * Closed flag accessor.
     */
    public boolean isClosed() {
        return outputBuffer.isClosed();
    }


    /**
     * 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
     */
    @Deprecated
    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
        outputBuffer.close();
    }


    /**
     * 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 null if no content type was set.
     */
    @Override
    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
     */
    @Override
    public void flushBuffer()
        throws IOException {
        outputBuffer.flush();
    }


    /**
     * Return the actual buffer size used for this Response.
     */
    @Override
    public int getBufferSize() {
        return outputBuffer.getBufferSize();
    }


    /**
     * Return the character encoding used for this Response.
     */
    @Override
    public String getCharacterEncoding() {
        return (coyoteResponse.getCharacterEncoding());
    }


    /**
     * Return the servlet output stream associated with this Response.
     *
     * @exception IllegalStateException if getWriter 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;

    }


    /**
     * Return the Locale assigned to this response.
     */
    @Override
    public Locale getLocale() {
        return (coyoteResponse.getLocale());
    }


    /**
     * Return the writer associated with this Response.
     *
     * @exception IllegalStateException if getOutputStream 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 getCharacterEncoding (i.e., the method
             * just returns the default value ISO-8859-1),
             * getWriter updates it to ISO-8859-1
             * (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?
     */
    @Override
    public boolean isCommitted() {
        return (coyoteResponse.isCommitted());
    }


    /**
     * Clear any content written to the buffer.
     *
     * @exception IllegalStateException if this response has already
     *  been committed
     */
    @Override
    public void reset() {

        if (included)
         {
            return;     // Ignore any call from an included servlet
        }

        coyoteResponse.reset();
        outputBuffer.reset();
        usingOutputStream = false;
        usingWriter = false;
        isCharacterEncodingSet = false;
    }


    /**
     * 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.
     *
     * @param resetWriterStreamFlags true if the internal
     *        usingWriter, usingOutputStream,
     *        isCharacterEncodingSet 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.
     *
     * @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.
     *
     * @param length The new content length
     */
    @Override
    public void setContentLength(int length) {

        if (isCommitted()) {
            return;
        }

        // Ignore any call from an included servlet
        if (included) {
            return;
        }

        coyoteResponse.setContentLength(length);

    }


    /**
     * 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) {
            coyoteResponse.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.
            coyoteResponse.setContentTypeNoCharset(type);
            return;
        }

        coyoteResponse.setContentTypeNoCharset(m[0]);

        if (m[1] != null) {
            // Ignore charset if getWriter() has already been called
            if (!usingWriter) {
                coyoteResponse.setCharacterEncoding(m[1]);
                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().
     *
     * @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;
        }

        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
     */
    @Override
    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;
        }

        String charset = getContext().getCharset(locale);
        if (charset != null) {
            coyoteResponse.setCharacterEncoding(charset);
        }
    }


    // --------------------------------------------------- HttpResponse Methods


    @Override
    public String getHeader(String name) {
        return coyoteResponse.getMimeHeaders().getHeader(name);
    }


    @Override
    public Collection getHeaderNames() {

        MimeHeaders headers = coyoteResponse.getMimeHeaders();
        int n = headers.size();
        List result = new ArrayList(n);
        for (int i = 0; i < n; i++) {
            result.add(headers.getName(i).toString());
        }
        return result;

    }


    @Override
    public Collection getHeaders(String name) {

        Enumeration enumeration =
            coyoteResponse.getMimeHeaders().values(name);
        Vector result = new Vector();
        while (enumeration.hasMoreElements()) {
            result.addElement(enumeration.nextElement());
        }
        return result;
    }


    /**
     * Return the error message that was set with sendError()
     * for this Response.
     */
    public String getMessage() {
        return coyoteResponse.getMessage();
    }


    @Override
    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
     */
    @Deprecated
    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
     */
    @Override
    public void addCookie(final Cookie cookie) {

        // Ignore any call from an included servlet
        if (included || isCommitted()) {
            return;
        }

        final StringBuffer sb = 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", sb.toString());
    }

    /**
     * Special method for adding a session cookie as we should be overriding
     * any previous
     * @param cookie
     */
    public void addSessionCookieInternal(final Cookie cookie) {
        if (isCommitted()) {
            return;
        }

        String name = cookie.getName();
        final String headername = "Set-Cookie";
        final String startsWith = name + "=";
        final StringBuffer sb = generateCookieString(cookie);
        boolean set = false;
        MimeHeaders headers = coyoteResponse.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(sb.toString());
                    set = true;
                }
            }
        }
        if (!set) {
            addHeader(headername, sb.toString());
        }


    }

    public StringBuffer generateCookieString(final Cookie cookie) {
        final StringBuffer sb = new StringBuffer();
        //web application code can receive a IllegalArgumentException
        //from the appendCookieValue invocation
        if (SecurityUtil.isPackageProtectionEnabled()) {
            AccessController.doPrivileged(new PrivilegedAction() {
                @Override
                public Void run(){
                    ServerCookie.appendCookieValue
                        (sb, cookie.getVersion(), cookie.getName(),
                         cookie.getValue(), cookie.getPath(),
                         cookie.getDomain(), cookie.getComment(),
                         cookie.getMaxAge(), cookie.getSecure(),
                         cookie.isHttpOnly());
                    return null;
                }
            });
        } else {
            ServerCookie.appendCookieValue
                (sb, cookie.getVersion(), cookie.getName(), cookie.getValue(),
                     cookie.getPath(), cookie.getDomain(), cookie.getComment(),
                     cookie.getMaxAge(), cookie.getSecure(),
                     cookie.isHttpOnly());
        }
        return sb;
    }


    /**
     * 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;
        }

        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
     */
    @Override
    public void addHeader(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;
        }

        coyoteResponse.addHeader(name, value);
    }


    /**
     * An extended version of this exists in {@link org.apache.coyote.Response}.
     * This check is required here to ensure that the usingWriter checks in
     * {@link #setContentType(String)} are applied since usingWriter is not
     * visible to {@link org.apache.coyote.Response}
     *
     * Called from set/addHeader.
     * Return true 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.
     *
     * @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?
     *
     * @param name Name of the header to check
     */
    @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 (coyoteResponse.getContentType() != null);
            }
            if(name.equalsIgnoreCase("Content-Length")) {
                // -1 means not known and is not sent to client
                return (coyoteResponse.getContentLengthLong() != -1);
            }
        }

        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
     */
    @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.
     *
     * @param url URL to be encoded
     *
     * @deprecated As of Version 2.1 of the Java Servlet API, use
     *  encodeRedirectURL() 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.
     *
     * @param url URL to be 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.
     *
     * @param url URL to be encoded
     *
     * @deprecated As of Version 2.1 of the Java Servlet API, use
     *  encodeURL() instead.
     */
    @Override
    @Deprecated
    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
     */
    @Override
    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
     */
    @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;
        }

        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
     */
    @Override
    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(true);

        // Generate a temporary redirect to the specified location
        try {
            String absolute = toAbsolute(location);
            setStatus(SC_FOUND);
            setHeader("Location", absolute);
            if (getContext().getSendRedirectBody()) {
                PrintWriter writer = getWriter();
                writer.print(sm.getString("coyoteResponse.sendRedirect.note",
                        RequestUtil.filter(absolute)));
                flushBuffer();
            }
        } 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
     */
    @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;
        }

        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
     */
    @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;
        }

        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
     */
    @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.
     *
     * @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.
     *
     * @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;
        }

        coyoteResponse.setStatus(status);
        coyoteResponse.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 *
* * @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 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()) { return ( AccessController.doPrivileged(new PrivilegedAction() { @Override public Boolean run(){ return Boolean.valueOf(doIsEncodeable(hreq, session, location)); } })).booleanValue(); } else { return doIsEncodeable(hreq, session, location); } } private boolean doIsEncodeable(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 = getContext().getPath(); if (contextPath != null) { String file = url.getFile(); if ((file == null) || !file.startsWith(contextPath)) { return (false); } String tok = ";" + SessionConfig.getSessionUriParamName(request.getContext()) + "=" + 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. * * @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 */ 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 || !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; final String frelativePath = relativePath; final int fend = pos; if (SecurityUtil.isPackageProtectionEnabled() ){ try{ encodedURI = AccessController.doPrivileged( new PrivilegedExceptionAction(){ @Override public CharChunk run() throws IOException{ return urlEncoder.encodeURL(frelativePath, 0, fend); } }); } 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() */ 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) { for (int pos = 0; pos < len; pos++) { c[pos + dest] = c[pos + src]; } } /** * Determine if an absolute URL has a path component */ 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; } /** * Determine if a URI string has a scheme 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); } 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()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy