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

net.krotscheck.kangaroo.util.RequestUtil Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 Michael Krotscheck
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package net.krotscheck.kangaroo.util;

import com.google.common.base.Strings;
import com.google.common.net.HttpHeaders;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * This utility assists in extracting simple values from a Jersey request
 * context.
 *
 * @author Michael Krotscheck
 */
public final class RequestUtil {

    /**
     * Logger instance.
     */
    private static Logger logger = LoggerFactory.getLogger(RequestUtil.class);

    /**
     * Utility class, private constructor.
     */
    private RequestUtil() {

    }

    /**
     * Provided with a request context, will extract the CORS requested HTTP
     * Method.
     *
     * @param request The request to analyze.
     * @return The HTTP Request Method.
     */
    public static String getCORSRequestedMethod(
            final ContainerRequestContext request) {
        MultivaluedMap headers = request.getHeaders();
        return headers
                .getFirst(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
    }

    /**
     * Provided with a request context, will extract the CORS requested HTTP
     * Headers.
     *
     * @param request The request to analyze.
     * @return The HTTP Request Method.
     */
    public static List getCORSRequestedHeaders(
            final ContainerRequestContext request) {
        MultivaluedMap headers = request.getHeaders();
        String rawHeaders = headers
                .getFirst(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
        if (Strings.isNullOrEmpty(rawHeaders)) {
            return Collections.emptyList();
        }

        return Arrays.stream(rawHeaders.split(","))
                .map(String::trim)
                .map(String::toLowerCase)
                .filter(s -> !Strings.isNullOrEmpty(s))
                .collect(Collectors.toList());
    }

    /**
     * Provided with a request context, this method will extract the CORS
     * origin header.
     *
     * @param request The request to analyze.
     * @return Origin header as a URI.
     */
    public static URI getOrigin(final ContainerRequestContext request) {
        MultivaluedMap headers = request.getHeaders();
        String rawHost = headers.getFirst(HttpHeaders.ORIGIN);

        try {
            return new URIBuilder(rawHost).build();
        } catch (URISyntaxException | NullPointerException use) {
            return null;
        }
    }

    /**
     * Provided with a request context, this method will extract the
     * Referrer, or return null.
     *
     * @param request The request to analyze.
     * @return Origin header as a URI.
     */
    public static URI getReferer(final ContainerRequestContext request) {
        MultivaluedMap headers = request.getHeaders();
        String rawHost = headers.getFirst(HttpHeaders.REFERER);
        try {
            return new URIBuilder(rawHost).build();
        } catch (URISyntaxException | NullPointerException use) {
            return null;
        }
    }

    /**
     * Provided with a request context, this method will extract the host,
     * port, and protocol into a simple URI. If the request does not contain
     * any of these, it will throw an exception.
     *
     * @param request The request to analyze.
     * @return The host as a URI, or null.
     */
    public static URI getHost(final ContainerRequestContext request) {
        MultivaluedMap headers = request.getHeaders();
        String rawHost = headers.getFirst(HttpHeaders.HOST);

        try {
            // Split into host and port if necessary
            String[] parts = rawHost.split(":");

            URIBuilder builder = new URIBuilder();
            builder.setScheme(request.getUriInfo().getRequestUri().getScheme());

            builder.setHost(parts[0]);

            if (parts.length > 1) {
                builder.setPort(Integer.valueOf(parts[1]));
            }

            return builder.build();
        } catch (URISyntaxException | NumberFormatException
                | NullPointerException e) {
            throw new InvalidHostException(e);
        }
    }

    /**
     * Provided with a request, attempts to extract various X-Forwarded-*
     * headers into a full URI. This method will return null unless a
     * protocol and host are set.
     *
     * @param request The request from which to extract the X-Forwarded-Host.
     * @return The Forwarded Host URI, or null.
     */
    public static URI getForwardedHost(
            final ContainerRequestContext request) {
        MultivaluedMap headers = request.getHeaders();
        String rawForwardedHost =
                headers.getFirst(HttpHeaders.X_FORWARDED_HOST);
        String rawForwardedPort =
                headers.getFirst(HttpHeaders.X_FORWARDED_PORT);
        String rawForwardedProto =
                headers.getFirst(HttpHeaders.X_FORWARDED_PROTO);

        try {
            Integer forwardedPort = rawForwardedPort == null ? -1
                    : Integer.valueOf(rawForwardedPort, 10);

            URI result = new URIBuilder()
                    .setHost(rawForwardedHost)
                    .setPort(forwardedPort)
                    .setScheme(rawForwardedProto)
                    .build();
            return Strings.isNullOrEmpty(result.toString()) ? null : result;
        } catch (URISyntaxException | NumberFormatException use) {
            logger.debug("Cannot parse forwarded header.", use);
            return null;
        }
    }

    /**
     * This method attempts to determine if the request is a cross-origin
     * request. This is not a check for CORS, as that standard demands the
     * Origin header be set. Instead, it implements the OWASP Cross Origin
     * detection algorithm.
     *
     * @param request The request from which to extract the X-Forwarded-Host.
     * @return True if this is an OWASP-evaluated cross-origin request.
     * @throws InvalidHostException Thrown if the source or target cannot be
     *                              determined from the provided headers.
     * @see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers
     */
    public static Boolean isCrossOriginRequest(
            final ContainerRequestContext request)
            throws InvalidHostException {

        URI origin = getOrigin(request);
        URI referer = getReferer(request);
        URI host = getHost(request); // This will throw an exception.
        URI forwardedHost = getForwardedHost(request);

        try {
            referer = new URIBuilder()
                    .setScheme(referer.getScheme())
                    .setHost(referer.getHost())
                    .setPort(referer.getPort())
                    .build();
        } catch (NullPointerException | URISyntaxException e) {
            referer = null;
        }

        // Try to find at least one target and source.
        URI source = origin == null ? referer : origin;
        URI target = forwardedHost == null ? host : forwardedHost;

        // This request is bad, exit and return.
        if (source == null) {
            return false;
        }

        return !source.equals(target);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy