Search in sources :

Example 66 with MessageBytes

use of org.apache.tomcat.util.buf.MessageBytes in project tomcat by apache.

the class Http11Processor method prepareResponse.

/**
 * When committing the response, we have to validate the set of headers, as
 * well as setup the response filters.
 */
@Override
protected final void prepareResponse() throws IOException {
    boolean entityBody = true;
    contentDelimitation = false;
    OutputFilter[] outputFilters = outputBuffer.getFilters();
    if (http09 == true) {
        // HTTP/0.9
        outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
        outputBuffer.commit();
        return;
    }
    int statusCode = response.getStatus();
    if (statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304) {
        // No entity body
        outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
        entityBody = false;
        contentDelimitation = true;
        if (statusCode == 205) {
            // RFC 7231 requires the server to explicitly signal an empty
            // response in this case
            response.setContentLength(0);
        } else {
            response.setContentLength(-1);
        }
    }
    MessageBytes methodMB = request.method();
    if (methodMB.equals("HEAD")) {
        // No entity body
        outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
        contentDelimitation = true;
    }
    // Sendfile support
    if (protocol.getUseSendfile()) {
        prepareSendfile(outputFilters);
    }
    // Check for compression
    boolean useCompression = false;
    if (entityBody && sendfileData == null) {
        useCompression = protocol.useCompression(request, response);
    }
    MimeHeaders headers = response.getMimeHeaders();
    // A SC_NO_CONTENT response may include entity headers
    if (entityBody || statusCode == HttpServletResponse.SC_NO_CONTENT) {
        String contentType = response.getContentType();
        if (contentType != null) {
            headers.setValue("Content-Type").setString(contentType);
        }
        String contentLanguage = response.getContentLanguage();
        if (contentLanguage != null) {
            headers.setValue("Content-Language").setString(contentLanguage);
        }
    }
    long contentLength = response.getContentLengthLong();
    boolean connectionClosePresent = isConnectionToken(headers, Constants.CLOSE);
    if (http11 && response.getTrailerFields() != null) {
        // If trailer fields are set, always use chunking
        outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
        contentDelimitation = true;
        headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
    } else if (contentLength != -1) {
        headers.setValue("Content-Length").setLong(contentLength);
        outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
        contentDelimitation = true;
    } else {
        // HTTP 1.1 then we chunk unless we have a Connection: close header
        if (http11 && entityBody && !connectionClosePresent) {
            outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
            contentDelimitation = true;
            headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
        } else {
            outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
        }
    }
    if (useCompression) {
        outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
    }
    // Caching Filter)
    if (headers.getValue("Date") == null) {
        headers.addValue("Date").setString(FastHttpDateFormat.getCurrentDate());
    }
    if ((entityBody) && (!contentDelimitation) || connectionClosePresent) {
        // Disable keep-alive if:
        // - there is a response body but way for the client to determine
        // the content length information; or
        // - there is a "connection: close" header present
        // This will cause the "connection: close" header to be added if it
        // is not already present.
        keepAlive = false;
    }
    // This may disabled keep-alive to check before working out the
    // Connection header.
    checkExpectationAndResponseStatus();
    // This may disable keep-alive if there is more body to swallow
    // than the configuration allows
    checkMaxSwallowSize();
    // Connection: close header.
    if (keepAlive && statusDropsConnection(statusCode)) {
        keepAlive = false;
    }
    if (!keepAlive) {
        // Avoid adding the close header twice
        if (!connectionClosePresent) {
            headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);
        }
    } else if (!getErrorState().isError()) {
        if (!http11) {
            headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
        }
        if (protocol.getUseKeepAliveResponseHeader()) {
            boolean connectionKeepAlivePresent = isConnectionToken(request.getMimeHeaders(), Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
            if (connectionKeepAlivePresent) {
                int keepAliveTimeout = protocol.getKeepAliveTimeout();
                if (keepAliveTimeout > 0) {
                    String value = "timeout=" + keepAliveTimeout / 1000L;
                    headers.setValue(Constants.KEEP_ALIVE_HEADER_NAME).setString(value);
                    if (http11) {
                        // Append if there is already a Connection header,
                        // else create the header
                        MessageBytes connectionHeaderValue = headers.getValue(Constants.CONNECTION);
                        if (connectionHeaderValue == null) {
                            headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
                        } else {
                            connectionHeaderValue.setString(connectionHeaderValue.getString() + ", " + Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN);
                        }
                    }
                }
            }
        }
    }
    // Add server header
    String server = protocol.getServer();
    if (server == null) {
        if (protocol.getServerRemoveAppProvidedValues()) {
            headers.removeHeader("server");
        }
    } else {
        // server always overrides anything the app might set
        headers.setValue("Server").setString(server);
    }
    // Build the response header
    try {
        outputBuffer.sendStatus();
        int size = headers.size();
        for (int i = 0; i < size; i++) {
            outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
        }
        outputBuffer.endHeaders();
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // If something goes wrong, reset the header buffer so the error
        // response can be written instead.
        outputBuffer.resetHeaderBuffer();
        throw t;
    }
    outputBuffer.commit();
}
Also used : ChunkedOutputFilter(org.apache.coyote.http11.filters.ChunkedOutputFilter) IdentityOutputFilter(org.apache.coyote.http11.filters.IdentityOutputFilter) GzipOutputFilter(org.apache.coyote.http11.filters.GzipOutputFilter) VoidOutputFilter(org.apache.coyote.http11.filters.VoidOutputFilter) MimeHeaders(org.apache.tomcat.util.http.MimeHeaders) MessageBytes(org.apache.tomcat.util.buf.MessageBytes)

Example 67 with MessageBytes

use of org.apache.tomcat.util.buf.MessageBytes in project tomcat by apache.

the class Http11Processor method prepareInputFilters.

private void prepareInputFilters(MimeHeaders headers) throws IOException {
    contentDelimitation = false;
    InputFilter[] inputFilters = inputBuffer.getFilters();
    // HTTP 1.x header from a 1.x client unless the specs says otherwise.
    if (!http09) {
        MessageBytes transferEncodingValueMB = headers.getValue("transfer-encoding");
        if (transferEncodingValueMB != null) {
            List<String> encodingNames = new ArrayList<>();
            if (TokenList.parseTokenList(headers.values("transfer-encoding"), encodingNames)) {
                for (String encodingName : encodingNames) {
                    addInputFilter(inputFilters, encodingName);
                }
            } else {
                // Invalid transfer encoding
                badRequest("http11processor.request.invalidTransferEncoding");
            }
        }
    }
    // Parse content-length header
    long contentLength = -1;
    try {
        contentLength = request.getContentLengthLong();
    } catch (NumberFormatException e) {
        badRequest("http11processor.request.nonNumericContentLength");
    } catch (IllegalArgumentException e) {
        badRequest("http11processor.request.multipleContentLength");
    }
    if (contentLength >= 0) {
        if (contentDelimitation) {
            // contentDelimitation being true at this point indicates that
            // chunked encoding is being used but chunked encoding should
            // not be used with a content length. RFC 2616, section 4.4,
            // bullet 3 states Content-Length must be ignored in this case -
            // so remove it.
            headers.removeHeader("content-length");
            request.setContentLength(-1);
            keepAlive = false;
        } else {
            inputBuffer.addActiveFilter(inputFilters[Constants.IDENTITY_FILTER]);
            contentDelimitation = true;
        }
    }
    if (!contentDelimitation) {
        // If there's no content length
        // (broken HTTP/1.0 or HTTP/1.1), assume
        // the client is not broken and didn't send a body
        inputBuffer.addActiveFilter(inputFilters[Constants.VOID_FILTER]);
        contentDelimitation = true;
    }
}
Also used : VoidInputFilter(org.apache.coyote.http11.filters.VoidInputFilter) BufferedInputFilter(org.apache.coyote.http11.filters.BufferedInputFilter) IdentityInputFilter(org.apache.coyote.http11.filters.IdentityInputFilter) ChunkedInputFilter(org.apache.coyote.http11.filters.ChunkedInputFilter) SavedRequestInputFilter(org.apache.coyote.http11.filters.SavedRequestInputFilter) ArrayList(java.util.ArrayList) MessageBytes(org.apache.tomcat.util.buf.MessageBytes)

Example 68 with MessageBytes

use of org.apache.tomcat.util.buf.MessageBytes in project tomcat by apache.

the class AjpProcessor method prepareResponse.

/**
 * When committing the response, we have to validate the set of headers, as
 * well as setup the response filters.
 */
@Override
protected final void prepareResponse() throws IOException {
    response.setCommitted(true);
    tmpMB.recycle();
    responseMsgPos = -1;
    responseMessage.reset();
    responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
    // Responses with certain status codes are not permitted to include a
    // response body.
    int statusCode = response.getStatus();
    if (statusCode < 200 || statusCode == 204 || statusCode == 205 || statusCode == 304) {
        // No entity body
        swallowResponse = true;
    }
    // Responses to HEAD requests are not permitted to include a response
    // body.
    MessageBytes methodMB = request.method();
    if (methodMB.equals("HEAD")) {
        // No entity body
        swallowResponse = true;
    }
    // HTTP header contents
    responseMessage.appendInt(statusCode);
    // Reason phrase is optional but mod_jk + httpd 2.x fails with a null
    // reason phrase - bug 45026
    tmpMB.setString(Integer.toString(response.getStatus()));
    responseMessage.appendBytes(tmpMB);
    // Special headers
    MimeHeaders headers = response.getMimeHeaders();
    String contentType = response.getContentType();
    if (contentType != null) {
        headers.setValue("Content-Type").setString(contentType);
    }
    String contentLanguage = response.getContentLanguage();
    if (contentLanguage != null) {
        headers.setValue("Content-Language").setString(contentLanguage);
    }
    long contentLength = response.getContentLengthLong();
    if (contentLength >= 0) {
        headers.setValue("Content-Length").setLong(contentLength);
    }
    // Other headers
    int numHeaders = headers.size();
    responseMessage.appendInt(numHeaders);
    for (int i = 0; i < numHeaders; i++) {
        MessageBytes hN = headers.getName(i);
        int hC = Constants.getResponseAjpIndex(hN.toString());
        if (hC > 0) {
            responseMessage.appendInt(hC);
        } else {
            responseMessage.appendBytes(hN);
        }
        MessageBytes hV = headers.getValue(i);
        responseMessage.appendBytes(hV);
    }
    // Write to buffer
    responseMessage.end();
    socketWrapper.write(true, responseMessage.getBuffer(), 0, responseMessage.getLen());
    socketWrapper.flush(true);
}
Also used : MimeHeaders(org.apache.tomcat.util.http.MimeHeaders) MessageBytes(org.apache.tomcat.util.buf.MessageBytes)

Example 69 with MessageBytes

use of org.apache.tomcat.util.buf.MessageBytes in project tomcat by apache.

the class AjpProcessor method prepareRequest.

/**
 * After reading the request headers, we have to setup the request filters.
 */
private void prepareRequest() {
    // Translate the HTTP method code to a String.
    byte methodCode = requestHeaderMessage.getByte();
    if (methodCode != Constants.SC_M_JK_STORED) {
        String methodName = Constants.getMethodForCode(methodCode - 1);
        request.method().setString(methodName);
    }
    requestHeaderMessage.getBytes(request.protocol());
    requestHeaderMessage.getBytes(request.requestURI());
    requestHeaderMessage.getBytes(request.remoteAddr());
    requestHeaderMessage.getBytes(request.remoteHost());
    requestHeaderMessage.getBytes(request.localName());
    request.setLocalPort(requestHeaderMessage.getInt());
    if (socketWrapper != null) {
        request.peerAddr().setString(socketWrapper.getRemoteAddr());
    }
    boolean isSSL = requestHeaderMessage.getByte() != 0;
    if (isSSL) {
        request.scheme().setString("https");
    }
    // Decode headers
    MimeHeaders headers = request.getMimeHeaders();
    // Set this every time in case limit has been changed via JMX
    headers.setLimit(protocol.getMaxHeaderCount());
    boolean contentLengthSet = false;
    int hCount = requestHeaderMessage.getInt();
    for (int i = 0; i < hCount; i++) {
        String hName = null;
        // Header names are encoded as either an integer code starting
        // with 0xA0, or as a normal string (in which case the first
        // two bytes are the length).
        int isc = requestHeaderMessage.peekInt();
        int hId = isc & 0xFF;
        MessageBytes vMB = null;
        isc &= 0xFF00;
        if (0xA000 == isc) {
            // To advance the read position
            requestHeaderMessage.getInt();
            hName = Constants.getHeaderForCode(hId - 1);
            vMB = headers.addValue(hName);
        } else {
            // reset hId -- if the header currently being read
            // happens to be 7 or 8 bytes long, the code below
            // will think it's the content-type header or the
            // content-length header - SC_REQ_CONTENT_TYPE=7,
            // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
            // behaviour.  see bug 5861 for more information.
            hId = -1;
            requestHeaderMessage.getBytes(tmpMB);
            ByteChunk bc = tmpMB.getByteChunk();
            vMB = headers.addValue(bc.getBuffer(), bc.getStart(), bc.getLength());
        }
        requestHeaderMessage.getBytes(vMB);
        if (hId == Constants.SC_REQ_CONTENT_LENGTH || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
            long cl = vMB.getLong();
            if (contentLengthSet) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                setErrorState(ErrorState.CLOSE_CLEAN, null);
            } else {
                contentLengthSet = true;
                // Set the content-length header for the request
                request.setContentLength(cl);
            }
        } else if (hId == Constants.SC_REQ_CONTENT_TYPE || (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
            // just read the content-type header, so set it
            ByteChunk bchunk = vMB.getByteChunk();
            request.contentType().setBytes(bchunk.getBytes(), bchunk.getOffset(), bchunk.getLength());
        }
    }
    // Decode extra attributes
    String secret = protocol.getSecret();
    boolean secretPresentInRequest = false;
    byte attributeCode;
    while ((attributeCode = requestHeaderMessage.getByte()) != Constants.SC_A_ARE_DONE) {
        switch(attributeCode) {
            case Constants.SC_A_REQ_ATTRIBUTE:
                requestHeaderMessage.getBytes(tmpMB);
                String n = tmpMB.toString();
                requestHeaderMessage.getBytes(tmpMB);
                String v = tmpMB.toString();
                /*
                 * AJP13 misses to forward the local IP address and the
                 * remote port. Allow the AJP connector to add this info via
                 * private request attributes.
                 * We will accept the forwarded data and remove it from the
                 * public list of request attributes.
                 */
                if (n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
                    request.localAddr().setString(v);
                } else if (n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
                    try {
                        request.setRemotePort(Integer.parseInt(v));
                    } catch (NumberFormatException nfe) {
                    // Ignore invalid value
                    }
                } else if (n.equals(Constants.SC_A_SSL_PROTOCOL)) {
                    request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v);
                } else if (n.equals("JK_LB_ACTIVATION")) {
                    request.setAttribute(n, v);
                } else if (jakartaAttributeMapping.containsKey(n)) {
                    // AJP uses the Java Servlet attribute names.
                    // Need to convert these to Jakarta Servlet.
                    request.setAttribute(jakartaAttributeMapping.get(n), v);
                } else if (iisTlsAttributes.contains(n)) {
                    // Allow IIS TLS attributes
                    request.setAttribute(n, v);
                } else {
                    // All 'known' attributes will be processed by the previous
                    // blocks. Any remaining attribute is an 'arbitrary' one.
                    Pattern pattern = protocol.getAllowedRequestAttributesPatternInternal();
                    if (pattern != null && pattern.matcher(n).matches()) {
                        request.setAttribute(n, v);
                    } else {
                        log.warn(sm.getString("ajpprocessor.unknownAttribute", n));
                        response.setStatus(403);
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                }
                break;
            case Constants.SC_A_CONTEXT:
                requestHeaderMessage.getBytes(tmpMB);
                // nothing
                break;
            case Constants.SC_A_SERVLET_PATH:
                requestHeaderMessage.getBytes(tmpMB);
                // nothing
                break;
            case Constants.SC_A_REMOTE_USER:
                boolean tomcatAuthorization = protocol.getTomcatAuthorization();
                if (tomcatAuthorization || !protocol.getTomcatAuthentication()) {
                    // Implies tomcatAuthentication == false
                    requestHeaderMessage.getBytes(request.getRemoteUser());
                    request.setRemoteUserNeedsAuthorization(tomcatAuthorization);
                } else {
                    // Ignore user information from reverse proxy
                    requestHeaderMessage.getBytes(tmpMB);
                }
                break;
            case Constants.SC_A_AUTH_TYPE:
                if (protocol.getTomcatAuthentication()) {
                    // ignore server
                    requestHeaderMessage.getBytes(tmpMB);
                } else {
                    requestHeaderMessage.getBytes(request.getAuthType());
                }
                break;
            case Constants.SC_A_QUERY_STRING:
                requestHeaderMessage.getBytes(request.queryString());
                break;
            case Constants.SC_A_JVM_ROUTE:
                requestHeaderMessage.getBytes(tmpMB);
                // nothing
                break;
            case Constants.SC_A_SSL_CERT:
                // SSL certificate extraction is lazy, moved to JkCoyoteHandler
                requestHeaderMessage.getBytes(certificates);
                break;
            case Constants.SC_A_SSL_CIPHER:
                requestHeaderMessage.getBytes(tmpMB);
                request.setAttribute(SSLSupport.CIPHER_SUITE_KEY, tmpMB.toString());
                break;
            case Constants.SC_A_SSL_SESSION:
                requestHeaderMessage.getBytes(tmpMB);
                request.setAttribute(SSLSupport.SESSION_ID_KEY, tmpMB.toString());
                break;
            case Constants.SC_A_SSL_KEY_SIZE:
                request.setAttribute(SSLSupport.KEY_SIZE_KEY, Integer.valueOf(requestHeaderMessage.getInt()));
                break;
            case Constants.SC_A_STORED_METHOD:
                requestHeaderMessage.getBytes(request.method());
                break;
            case Constants.SC_A_SECRET:
                requestHeaderMessage.getBytes(tmpMB);
                if (secret != null && secret.length() > 0) {
                    secretPresentInRequest = true;
                    if (!tmpMB.equals(secret)) {
                        response.setStatus(403);
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                }
                break;
            default:
                // Ignore unknown attribute for backward compatibility
                break;
        }
    }
    // Check if secret was submitted if required
    if (secret != null && secret.length() > 0 && !secretPresentInRequest) {
        response.setStatus(403);
        setErrorState(ErrorState.CLOSE_CLEAN, null);
    }
    // Check for a full URI (including protocol://host:port/)
    ByteChunk uriBC = request.requestURI().getByteChunk();
    if (uriBC.startsWithIgnoreCase("http", 0)) {
        int pos = uriBC.indexOf("://", 0, 3, 4);
        int uriBCStart = uriBC.getStart();
        int slashPos = -1;
        if (pos != -1) {
            byte[] uriB = uriBC.getBytes();
            slashPos = uriBC.indexOf('/', pos + 3);
            if (slashPos == -1) {
                slashPos = uriBC.getLength();
                // Set URI as "/"
                request.requestURI().setBytes(uriB, uriBCStart + pos + 1, 1);
            } else {
                request.requestURI().setBytes(uriB, uriBCStart + slashPos, uriBC.getLength() - slashPos);
            }
            MessageBytes hostMB = headers.setValue("host");
            hostMB.setBytes(uriB, uriBCStart + pos + 3, slashPos - pos - 3);
        }
    }
    MessageBytes valueMB = request.getMimeHeaders().getValue("host");
    parseHost(valueMB);
    if (!getErrorState().isIoAllowed()) {
        getAdapter().log(request, response, 0);
    }
}
Also used : MimeHeaders(org.apache.tomcat.util.http.MimeHeaders) Pattern(java.util.regex.Pattern) ByteChunk(org.apache.tomcat.util.buf.ByteChunk) MessageBytes(org.apache.tomcat.util.buf.MessageBytes)

Example 70 with MessageBytes

use of org.apache.tomcat.util.buf.MessageBytes in project tomcat by apache.

the class CoyoteAdapter method postParseRequest.

// ------------------------------------------------------ Protected Methods
/**
 * Perform the necessary processing after the HTTP headers have been parsed
 * to enable the request/response pair to be passed to the start of the
 * container pipeline for processing.
 *
 * @param req      The coyote request object
 * @param request  The catalina request object
 * @param res      The coyote response object
 * @param response The catalina response object
 *
 * @return <code>true</code> if the request should be passed on to the start
 *         of the container pipeline, otherwise <code>false</code>
 *
 * @throws IOException If there is insufficient space in a buffer while
 *                     processing headers
 * @throws ServletException If the supported methods of the target servlet
 *                          cannot be determined
 */
protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res, Response response) throws IOException, ServletException {
    // processor hasn't set it, use the settings from the connector
    if (req.scheme().isNull()) {
        // Use connector scheme and secure configuration, (defaults to
        // "http" and false respectively)
        req.scheme().setString(connector.getScheme());
        request.setSecure(connector.getSecure());
    } else {
        // Use processor specified scheme to determine secure state
        request.setSecure(req.scheme().equals("https"));
    }
    // At this point the Host header has been processed.
    // Override if the proxyPort/proxyHost are set
    String proxyName = connector.getProxyName();
    int proxyPort = connector.getProxyPort();
    if (proxyPort != 0) {
        req.setServerPort(proxyPort);
    } else if (req.getServerPort() == -1) {
        // Not explicitly set. Use default ports based on the scheme
        if (req.scheme().equals("https")) {
            req.setServerPort(443);
        } else {
            req.setServerPort(80);
        }
    }
    if (proxyName != null) {
        req.serverName().setString(proxyName);
    }
    MessageBytes undecodedURI = req.requestURI();
    // Check for ping OPTIONS * request
    if (undecodedURI.equals("*")) {
        if (req.method().equalsIgnoreCase("OPTIONS")) {
            StringBuilder allow = new StringBuilder();
            allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
            // Trace if allowed
            if (connector.getAllowTrace()) {
                allow.append(", TRACE");
            }
            res.setHeader("Allow", allow.toString());
            // Access log entry as processing won't reach AccessLogValve
            connector.getService().getContainer().logAccess(request, response, 0, true);
            return false;
        } else {
            response.sendError(400, "Invalid URI");
        }
    }
    MessageBytes decodedURI = req.decodedURI();
    if (undecodedURI.getType() == MessageBytes.T_BYTES) {
        if (connector.getRejectSuspiciousURIs()) {
            if (checkSuspiciousURIs(undecodedURI.getByteChunk())) {
                response.sendError(400, "Invalid URI");
            }
        }
        // Copy the raw URI to the decodedURI
        decodedURI.duplicate(undecodedURI);
        // Parse (and strip out) the path parameters
        parsePathParameters(req, request);
        // %xx decoding of the URL
        try {
            req.getURLDecoder().convert(decodedURI.getByteChunk(), connector.getEncodedSolidusHandlingInternal());
        } catch (IOException ioe) {
            response.sendError(400, "Invalid URI: " + ioe.getMessage());
        }
        // Normalization
        if (normalize(req.decodedURI(), connector.getAllowBackslash())) {
            // Character decoding
            convertURI(decodedURI, request);
        // URIEncoding values are limited to US-ASCII supersets.
        // Therefore it is not necessary to check that the URI remains
        // normalized after character decoding
        } else {
            response.sendError(400, "Invalid URI");
        }
    } else {
        /* The URI is chars or String, and has been sent using an in-memory
             * protocol handler. The following assumptions are made:
             * - req.requestURI() has been set to the 'original' non-decoded,
             *   non-normalized URI
             * - req.decodedURI() has been set to the decoded, normalized form
             *   of req.requestURI()
             * - 'suspicious' URI filtering - if required - has already been
             *   performed
             */
        decodedURI.toChars();
        // Remove all path parameters; any needed path parameter should be set
        // using the request object rather than passing it in the URL
        CharChunk uriCC = decodedURI.getCharChunk();
        int semicolon = uriCC.indexOf(';');
        if (semicolon > 0) {
            decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(), semicolon);
        }
    }
    // Request mapping.
    MessageBytes serverName;
    if (connector.getUseIPVHosts()) {
        serverName = req.localName();
        if (serverName.isNull()) {
            // well, they did ask for it
            res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
        }
    } else {
        serverName = req.serverName();
    }
    // Version for the second mapping loop and
    // Context that we expect to get for that version
    String version = null;
    Context versionContext = null;
    boolean mapRequired = true;
    if (response.isError()) {
        // An error this early means the URI is invalid. Ensure invalid data
        // is not passed to the mapper. Note we still want the mapper to
        // find the correct host.
        decodedURI.recycle();
    }
    while (mapRequired) {
        // This will map the the latest version by default
        connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
        // so no context could be mapped.
        if (request.getContext() == null) {
            // body.
            return true;
        }
        // Now we have the context, we can parse the session ID from the URL
        // (if any). Need to do this before we redirect in case we need to
        // include the session id in the redirect
        String sessionID;
        if (request.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {
            // Get the session ID if there was one
            sessionID = request.getPathParameter(SessionConfig.getSessionUriParamName(request.getContext()));
            if (sessionID != null) {
                request.setRequestedSessionId(sessionID);
                request.setRequestedSessionURL(true);
            }
        }
        // Look for session ID in cookies and SSL session
        try {
            parseSessionCookiesId(request);
        } catch (IllegalArgumentException e) {
            // Too many cookies
            if (!response.isError()) {
                response.setError();
                response.sendError(400);
            }
            return true;
        }
        parseSessionSslId(request);
        sessionID = request.getRequestedSessionId();
        mapRequired = false;
        if (version != null && request.getContext() == versionContext) {
        // We got the version that we asked for. That is it.
        } else {
            version = null;
            versionContext = null;
            Context[] contexts = request.getMappingData().contexts;
            // No session ID means no possibility of remap
            if (contexts != null && sessionID != null) {
                // Find the context associated with the session
                for (int i = contexts.length; i > 0; i--) {
                    Context ctxt = contexts[i - 1];
                    if (ctxt.getManager().findSession(sessionID) != null) {
                        // already been mapped?
                        if (!ctxt.equals(request.getMappingData().context)) {
                            // Set version so second time through mapping
                            // the correct context is found
                            version = ctxt.getWebappVersion();
                            versionContext = ctxt;
                            // Reset mapping
                            request.getMappingData().recycle();
                            mapRequired = true;
                            // Recycle cookies and session info in case the
                            // correct context is configured with different
                            // settings
                            request.recycleSessionInfo();
                            request.recycleCookieInfo(true);
                        }
                        break;
                    }
                }
            }
        }
        if (!mapRequired && request.getContext().getPaused()) {
            // point.
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            // Should never happen
            }
            // Reset mapping
            request.getMappingData().recycle();
            mapRequired = true;
        }
    }
    // Possible redirect
    MessageBytes redirectPathMB = request.getMappingData().redirectPath;
    if (!redirectPathMB.isNull()) {
        String redirectPath = URLEncoder.DEFAULT.encode(redirectPathMB.toString(), StandardCharsets.UTF_8);
        String query = request.getQueryString();
        if (request.isRequestedSessionIdFromURL()) {
            // This is not optimal, but as this is not very common, it
            // shouldn't matter
            redirectPath = redirectPath + ";" + SessionConfig.getSessionUriParamName(request.getContext()) + "=" + request.getRequestedSessionId();
        }
        if (query != null) {
            // This is not optimal, but as this is not very common, it
            // shouldn't matter
            redirectPath = redirectPath + "?" + query;
        }
        response.sendRedirect(redirectPath);
        request.getContext().logAccess(request, response, 0, true);
        return false;
    }
    // Filter trace method
    if (!connector.getAllowTrace() && req.method().equalsIgnoreCase("TRACE")) {
        Wrapper wrapper = request.getWrapper();
        String header = null;
        if (wrapper != null) {
            String[] methods = wrapper.getServletMethods();
            if (methods != null) {
                for (String method : methods) {
                    if ("TRACE".equals(method)) {
                        continue;
                    }
                    if (header == null) {
                        header = method;
                    } else {
                        header += ", " + method;
                    }
                }
            }
        }
        if (header != null) {
            res.addHeader("Allow", header);
        }
        response.sendError(405, "TRACE method is not allowed");
        // Safe to skip the remainder of this method.
        return true;
    }
    doConnectorAuthenticationAuthorization(req, request);
    return true;
}
Also used : Context(org.apache.catalina.Context) Wrapper(org.apache.catalina.Wrapper) MessageBytes(org.apache.tomcat.util.buf.MessageBytes) IOException(java.io.IOException) CharChunk(org.apache.tomcat.util.buf.CharChunk)

Aggregations

MessageBytes (org.apache.tomcat.util.buf.MessageBytes)73 MimeHeaders (org.apache.tomcat.util.http.MimeHeaders)15 ByteChunk (org.apache.tomcat.util.buf.ByteChunk)14 IOException (java.io.IOException)11 Test (org.junit.Test)11 Context (org.apache.catalina.Context)10 LoggingBaseTest (org.apache.catalina.startup.LoggingBaseTest)8 Pattern (java.util.regex.Pattern)6 AbstractEndpoint (org.apache.tomcat.util.net.AbstractEndpoint)6 Principal (java.security.Principal)5 Wrapper (org.apache.catalina.Wrapper)5 CharChunk (org.apache.tomcat.util.buf.CharChunk)5 Host (org.apache.catalina.Host)4 ServletException (jakarta.servlet.ServletException)3 Cookie (jakarta.servlet.http.Cookie)3 UnsupportedEncodingException (java.io.UnsupportedEncodingException)3 HashSet (java.util.HashSet)3 ServletException (javax.servlet.ServletException)3 Cookie (javax.servlet.http.Cookie)3 Container (org.apache.catalina.Container)3