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

io.quarkus.vertx.http.runtime.ForwardedParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Red Hat, Inc.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */

// This code was Heavily influenced from spring forward header parser
// https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java#L849

package io.quarkus.vertx.http.runtime;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jboss.logging.Logger;

import io.netty.util.AsciiString;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.net.impl.SocketAddressImpl;

class ForwardedParser {
    private static final Logger log = Logger.getLogger(ForwardedParser.class);

    private static final String HTTP_SCHEME = "http";
    private static final String HTTPS_SCHEME = "https";
    private static final AsciiString FORWARDED = AsciiString.cached("Forwarded");
    private static final AsciiString X_FORWARDED_SSL = AsciiString.cached("X-Forwarded-Ssl");
    private static final AsciiString X_FORWARDED_PROTO = AsciiString.cached("X-Forwarded-Proto");
    private static final AsciiString X_FORWARDED_PORT = AsciiString.cached("X-Forwarded-Port");
    private static final AsciiString X_FORWARDED_FOR = AsciiString.cached("X-Forwarded-For");
    private static final AsciiString X_FORWARDED_TRUSTED_PROXY = AsciiString.cached("X-Forwarded-Trusted-Proxy");

    private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?", Pattern.CASE_INSENSITIVE);
    private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?", Pattern.CASE_INSENSITIVE);
    private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("for=\"?([^;,\"]+)\"?", Pattern.CASE_INSENSITIVE);

    private final static int PORT_MIN_VALID_VALUE = 0;
    private final static int PORT_MAX_VALID_VALUE = 65535;

    private final HttpServerRequest delegate;
    private final ForwardingProxyOptions forwardingProxyOptions;
    private final TrustedProxyCheck trustedProxyCheck;

    /**
     * Does not use the Netty constant (`host`) to enforce the header name convention.
     */
    private final static AsciiString HOST_HEADER = AsciiString.cached("Host");

    private boolean calculated;
    private String host;
    private int port = -1;
    private String scheme;
    private String uri;
    private String absoluteURI;
    private SocketAddress remoteAddress;

    private HostAndPort authority;

    ForwardedParser(HttpServerRequest delegate, ForwardingProxyOptions forwardingProxyOptions,
            TrustedProxyCheck trustedProxyCheck) {
        this.delegate = delegate;
        this.forwardingProxyOptions = forwardingProxyOptions;
        this.trustedProxyCheck = trustedProxyCheck;
    }

    public String scheme() {
        if (!calculated)
            calculate();
        return scheme;
    }

    String host() {
        if (!calculated)
            calculate();
        return host;
    }

    boolean isSSL() {
        if (!calculated)
            calculate();

        return scheme.equals(HTTPS_SCHEME);
    }

    HostAndPort authority() {
        if (!calculated) {
            calculate();
        }
        return authority;
    }

    String absoluteURI() {
        if (!calculated)
            calculate();

        return absoluteURI;
    }

    SocketAddress remoteAddress() {
        if (!calculated)
            calculate();

        return remoteAddress;
    }

    String uri() {
        if (!calculated)
            calculate();

        return uri;
    }

    private void calculate() {
        calculated = true;
        remoteAddress = delegate.remoteAddress();
        scheme = delegate.scheme();
        setHostAndPort(delegate.host(), port);
        uri = delegate.uri();

        boolean isProxyAllowed = trustedProxyCheck.isProxyAllowed();
        if (isProxyAllowed) {
            String forwarded = delegate.getHeader(FORWARDED);
            if (forwardingProxyOptions.allowForwarded && forwarded != null) {
                Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwarded);
                if (matcher.find()) {
                    scheme = (matcher.group(1).trim());
                    port = -1;
                }

                matcher = FORWARDED_HOST_PATTERN.matcher(forwarded);
                if (matcher.find()) {
                    setHostAndPort(matcher.group(1).trim(), port);
                }

                matcher = FORWARDED_FOR_PATTERN.matcher(forwarded);
                if (matcher.find()) {
                    remoteAddress = parseFor(matcher.group(1).trim(), remoteAddress != null ? remoteAddress.port() : port);
                }
            } else if (forwardingProxyOptions.allowXForwarded) {
                String protocolHeader = delegate.getHeader(X_FORWARDED_PROTO);
                if (protocolHeader != null) {
                    scheme = getFirstElement(protocolHeader);
                    port = -1;
                }

                String forwardedSsl = delegate.getHeader(X_FORWARDED_SSL);
                boolean isForwardedSslOn = forwardedSsl != null && forwardedSsl.equalsIgnoreCase("on");
                if (isForwardedSslOn) {
                    scheme = HTTPS_SCHEME;
                    port = -1;
                }

                if (forwardingProxyOptions.enableForwardedHost) {
                    String hostHeader = delegate.getHeader(forwardingProxyOptions.forwardedHostHeader);
                    if (hostHeader != null) {
                        setHostAndPort(getFirstElement(hostHeader), port);
                    }
                }

                if (forwardingProxyOptions.enableForwardedPrefix) {
                    String prefixHeader = delegate.getHeader(forwardingProxyOptions.forwardedPrefixHeader);
                    if (prefixHeader != null) {
                        uri = appendPrefixToUri(prefixHeader, uri);
                    }
                }

                String portHeader = delegate.getHeader(X_FORWARDED_PORT);
                if (portHeader != null) {
                    port = parsePort(getFirstElement(portHeader), port);
                }

                String forHeader = delegate.getHeader(X_FORWARDED_FOR);
                if (forHeader != null) {
                    remoteAddress = parseFor(getFirstElement(forHeader), remoteAddress != null ? remoteAddress.port() : port);
                }
            }
        }

        if (((scheme.equals(HTTP_SCHEME) && port == 80) || (scheme.equals(HTTPS_SCHEME) && port == 443))) {
            port = -1;
        }

        authority = HostAndPort.create(host, port >= 0 ? port : -1);
        host = host + (port >= 0 ? ":" + port : "");
        delegate.headers().set(HOST_HEADER, host);
        if (forwardingProxyOptions.enableTrustedProxyHeader) {
            // Verify that the header was not already set.
            if (delegate.headers().contains(X_FORWARDED_TRUSTED_PROXY)) {
                log.warn("The header " + X_FORWARDED_TRUSTED_PROXY + " was already set. Overwriting it.");
            }
            delegate.headers().set(X_FORWARDED_TRUSTED_PROXY, Boolean.toString(isProxyAllowed));
        } else {
            // Verify that the header was not already set - to avoid forgery.
            if (delegate.headers().contains(X_FORWARDED_TRUSTED_PROXY)) {
                log.warn("The header " + X_FORWARDED_TRUSTED_PROXY + " was already set. Removing it.");
                delegate.headers().remove(X_FORWARDED_TRUSTED_PROXY);
            }
        }

        absoluteURI = scheme + "://" + host + uri;
        log.debug("Recalculated absoluteURI to " + absoluteURI);
    }

    private void setHostAndPort(String hostToParse, int defaultPort) {
        if (hostToParse == null) {
            hostToParse = "";
        }
        String[] hostAndPort = parseHostAndPort(hostToParse);
        host = hostAndPort[0];
        delegate.headers().set(HOST_HEADER, host);
        port = parsePort(hostAndPort[1], defaultPort);
    }

    private SocketAddress parseFor(String forToParse, int defaultPort) {
        String[] hostAndPort = parseHostAndPort(forToParse);
        String host = hostAndPort[0];
        int port = parsePort(hostAndPort[1], defaultPort);
        return new SocketAddressImpl(port, host);
    }

    private String getFirstElement(String value) {
        int index = value.indexOf(',');
        return index == -1 ? value : value.substring(0, index);
    }

    /**
     * Returns a String[] of 2 elements, with the first being the host and the second the port
     */
    private String[] parseHostAndPort(String hostToParse) {
        String[] hostAndPort = { hostToParse, "" };
        int portSeparatorIdx = hostToParse.lastIndexOf(':');
        int squareBracketIdx = hostToParse.lastIndexOf(']');
        if ((squareBracketIdx > -1 && portSeparatorIdx > squareBracketIdx)) {
            // ipv6 with port
            hostAndPort[0] = hostToParse.substring(0, portSeparatorIdx);
            hostAndPort[1] = hostToParse.substring(portSeparatorIdx + 1);
        } else {
            long numberOfColons = hostToParse.chars().filter(ch -> ch == ':').count();
            if (numberOfColons == 1 && !hostToParse.endsWith(":")) {
                // ipv4 with port
                hostAndPort[0] = hostToParse.substring(0, portSeparatorIdx);
                hostAndPort[1] = hostToParse.substring(portSeparatorIdx + 1);
            }
        }
        return hostAndPort;
    }

    private int parsePort(String portToParse, int defaultPort) {
        if (portToParse != null && portToParse.length() > 0) {
            try {
                int port = Integer.parseInt(portToParse);
                if (port < PORT_MIN_VALID_VALUE || port > PORT_MAX_VALID_VALUE) {
                    log.errorf("Failed to validate a port from \"forwarded\"-type headers, using the default port %d",
                            defaultPort);
                    return defaultPort;
                }
                return port;
            } catch (NumberFormatException ignored) {
                log.errorf("Failed to parse a port from \"forwarded\"-type headers, using the default port %d", defaultPort);
            }
        }
        return defaultPort;
    }

    private String appendPrefixToUri(String prefix, String uri) {
        String parsed = stripSlashes(prefix);
        return parsed.isEmpty() ? uri : '/' + parsed + uri;
    }

    private String stripSlashes(String uri) {
        String result;
        if (!uri.isEmpty()) {
            int beginIndex = 0;
            if (uri.startsWith("/")) {
                beginIndex = 1;
            }

            int endIndex = uri.length();
            if (uri.endsWith("/") && uri.length() > 1) {
                endIndex = uri.length() - 1;
            }
            result = uri.substring(beginIndex, endIndex);
        } else {
            result = uri;
        }

        return result;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy