Skip to content

Commit

Permalink
Parse X-Forwarded-Prefix request header
Browse files Browse the repository at this point in the history
The X-Forwarded-Prefix can be obtained via
`HttpServerRequest#forwardedPrefix()`.

Resolves #3432
  • Loading branch information
chemicL committed Sep 18, 2024
1 parent 3df9ba5 commit 957375a
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2023 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2018-2024 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,6 +57,8 @@ public final class ConnectionInfo {

final boolean isInetAddress;

final String forwardedPrefix;

static ConnectionInfo from(Channel channel, HttpRequest request, boolean secured, SocketAddress remoteAddress,
@Nullable BiFunction<ConnectionInfo, HttpRequest, ConnectionInfo> forwardedHeaderHandler) {
String hostName = DEFAULT_HOST_NAME;
Expand Down Expand Up @@ -94,12 +96,18 @@ static ConnectionInfo from(Channel channel, HttpRequest request, boolean secured

ConnectionInfo(SocketAddress hostAddress, String hostName, int hostPort,
SocketAddress remoteAddress, String scheme, boolean isInetAddress) {
this(hostAddress, hostName, hostPort, remoteAddress, scheme, isInetAddress, "");
}

ConnectionInfo(SocketAddress hostAddress, String hostName, int hostPort,
SocketAddress remoteAddress, String scheme, boolean isInetAddress, String forwardedPrefix) {
this.hostAddress = hostAddress;
this.hostName = hostName;
this.hostPort = hostPort;
this.isInetAddress = isInetAddress;
this.remoteAddress = remoteAddress;
this.scheme = scheme;
this.forwardedPrefix = forwardedPrefix;
}

/**
Expand Down Expand Up @@ -173,6 +181,18 @@ public ConnectionInfo withScheme(String scheme) {
return new ConnectionInfo(this.hostAddress, this.hostName, this.hostPort, this.remoteAddress, scheme, this.isInetAddress);
}

/**
* Return a new {@link ConnectionInfo} with the forwardedPrefix set.
* @param forwardedPrefix the prefix provided via X-Forwarded-Prefix header
* @return a new {@link ConnectionInfo}
* @since 1.1.23
*/
public ConnectionInfo withForwardedPrefix(String forwardedPrefix) {
requireNonNull(forwardedPrefix, "forwardedPrefix");
return new ConnectionInfo(this.hostAddress, this.hostName, this.hostPort, this.remoteAddress, this.scheme,
this.isInetAddress, forwardedPrefix);
}

/**
* Returns the connection host name.
* @return the connection host name
Expand All @@ -191,6 +211,15 @@ public int getHostPort() {
return hostPort != -1 ? hostPort : getDefaultHostPort(scheme);
}

/**
* Returns the X-Forwarded-Prefix if it was part of the request headers.
* @return the X-Forwarded-Prefix
* @since 1.1.23
*/
public String getForwardedPrefix() {
return forwardedPrefix;
}

/**
* Returns the default host port number based on scheme.
* @param scheme a connection scheme like "http", "https", or "wss"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2023 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2020-2024 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,7 @@ final class DefaultHttpForwardedHeaderHandler implements BiFunction<ConnectionIn
static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host";
static final String X_FORWARDED_PORT_HEADER = "X-Forwarded-Port";
static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto";
static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix";

static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?");
static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?");
Expand Down Expand Up @@ -115,6 +116,11 @@ else if (DEFAULT_FORWARDED_HEADER_VALIDATION) {
throw new IllegalArgumentException("Failed to parse a port from " + portHeader);
}
}

String prefixHeader = request.headers().get(X_FORWARDED_PREFIX_HEADER);
if (prefixHeader != null && !prefixHeader.isEmpty()) {
connectionInfo = connectionInfo.withForwardedPrefix(prefixHeader);
}
return connectionInfo;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,11 @@ public ZonedDateTime timestamp() {
return timestamp;
}

@Override
public String forwardedPrefix() {
return connectionInfo.getForwardedPrefix();
}

@Override
@SuppressWarnings("unchecked")
public NettyOutbound send(Publisher<? extends ByteBuf> source) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2023 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2011-2024 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -158,4 +158,11 @@ default Flux<HttpContent> receiveContent() {
* @since 1.0.28
*/
ZonedDateTime timestamp();

/**
* Returns the X-Forwarded-Prefix if it was part of the request headers.
* @return the X-Forwarded-Prefix
* @since 1.1.23
*/
String forwardedPrefix();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018-2023 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2018-2024 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -218,6 +218,7 @@ void xForwardedFor(boolean useCustomForwardedHandler) {
serverRequest -> {
Assertions.assertThat(serverRequest.remoteAddress().getHostString()).isEqualTo("1abc:2abc:3abc:0:0:0:5abc:6abc");
Assertions.assertThat(serverRequest.remoteAddress().getPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -234,6 +235,7 @@ void xForwardedHost(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(port);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("1abc:2abc:3abc:0:0:0:5abc:6abc");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(port);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -250,6 +252,7 @@ void xForwardedHostEmptyHostHeader(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(port);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("1abc:2abc:3abc:0:0:0:5abc:6abc");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(port);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -265,6 +268,7 @@ void xForwardedHostPortIncluded(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(9090);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("1abc:2abc:3abc:0:0:0:5abc:6abc");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(9090);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -282,6 +286,7 @@ void xForwardedHostAndPort(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("192.168.0.1");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -299,6 +304,31 @@ void xForwardedHostPortIncludedAndXForwardedPort(boolean useCustomForwardedHandl
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("192.168.0.1");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}

@ParameterizedTest(name = "{displayName}({arguments})")
@ValueSource(booleans = {true, false})
void xForwardedPrefix(boolean useCustomForwardedHandler) {
testClientRequest(
clientRequestHeaders -> {
clientRequestHeaders.add("X-Forwarded-Prefix", "test-prefix");
},
serverRequest -> {
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("test-prefix");
},
useCustomForwardedHandler);
}

@ParameterizedTest(name = "{displayName}({arguments})")
@ValueSource(booleans = {true, false})
void xForwardedPrefixEmpty(boolean useCustomForwardedHandler) {
testClientRequest(
clientRequestHeaders -> {},
serverRequest -> {
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -314,13 +344,15 @@ void xForwardedMultipleHeaders(boolean useCustomForwardedHandler) {
clientRequestHeaders.add("X-Forwarded-Port", "8081");
clientRequestHeaders.add("X-Forwarded-Proto", "http");
clientRequestHeaders.add("X-Forwarded-Proto", "https");
clientRequestHeaders.add("X-Forwarded-Prefix", "test-prefix");
},
serverRequest -> {
Assertions.assertThat(serverRequest.hostAddress().getHostString()).isEqualTo("192.168.0.1");
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("192.168.0.1");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.scheme()).isEqualTo("http");
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("test-prefix");
},
useCustomForwardedHandler);
}
Expand All @@ -339,6 +371,7 @@ void xForwardedHostAndEmptyPort(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(port);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("192.168.0.1");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(port);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
getForwardedHandler(useCustomForwardedHandler),
httpClient -> httpClient,
Expand All @@ -359,6 +392,7 @@ void xForwardedHostAndNonNumericPort(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("192.168.0.1");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
getForwardedHandler(useCustomForwardedHandler),
httpClient -> httpClient,
Expand All @@ -382,6 +416,7 @@ void xForwardedForHostAndPort(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostAddress().getPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("a.example.com");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -403,6 +438,7 @@ void xForwardedForHostAndPortAndProto(boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostName()).isEqualTo("a.example.com");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.scheme()).isEqualTo("http");
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -424,6 +460,7 @@ void xForwardedForMultipleHostAndPortAndProto(boolean useCustomForwardedHandler)
Assertions.assertThat(serverRequest.hostName()).isEqualTo("a.example.com");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8080);
Assertions.assertThat(serverRequest.scheme()).isEqualTo("http");
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand All @@ -449,6 +486,7 @@ void xForwardedForAndPortOnly(boolean useCustomForwardedHandler) throws SSLExcep
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(8443);
Assertions.assertThat(serverRequest.hostName()).isEqualTo("a.example.com");
Assertions.assertThat(serverRequest.scheme()).isEqualTo("https");
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
getForwardedHandler(useCustomForwardedHandler),
httpClient -> httpClient.secure(ssl -> ssl.sslContext(clientSslContext)),
Expand All @@ -471,6 +509,7 @@ void xForwardedProtoOnly(String protocol, boolean useCustomForwardedHandler) {
Assertions.assertThat(serverRequest.hostName()).isEqualTo("a.example.com");
Assertions.assertThat(serverRequest.hostPort()).isEqualTo(getDefaultHostPort(protocol));
Assertions.assertThat(serverRequest.scheme()).isEqualTo(protocol);
Assertions.assertThat(serverRequest.forwardedPrefix()).isEqualTo("");
},
useCustomForwardedHandler);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 VMware, Inc. or its affiliates, All Rights Reserved.
* Copyright (c) 2023-2024 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,7 @@ public final class CustomXForwardedHeadersHandler {
static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host";
static final String X_FORWARDED_PORT_HEADER = "X-Forwarded-Port";
static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto";
static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix";

private CustomXForwardedHeadersHandler() {
}
Expand Down Expand Up @@ -74,6 +75,12 @@ private ConnectionInfo parseXForwardedInfo(ConnectionInfo connectionInfo, HttpRe
throw new IllegalArgumentException("Failed to parse a port from " + portHeader);
}
}

String prefixHeader = request.headers().get(X_FORWARDED_PREFIX_HEADER);
if (prefixHeader != null && !prefixHeader.isEmpty()) {
connectionInfo = connectionInfo.withForwardedPrefix(prefixHeader);
}

return connectionInfo;
}
}

0 comments on commit 957375a

Please sign in to comment.