Handle port and IPv6 in forwarded headers (#3651)

* Handle port and IPv6 in forwarded headers

* More IPv6...

* Test proper quoted host:port for Forwarded case
This commit is contained in:
Trask Stalnaker 2021-07-23 00:59:55 -07:00 committed by GitHub
parent d7dcc70119
commit 83b5121f75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 225 additions and 33 deletions

View File

@ -206,7 +206,7 @@ public abstract class HttpServerTracer<REQUEST, RESPONSE, CONNECTION, STORAGE> e
// try Forwarded // try Forwarded
String forwarded = requestHeader(request, "Forwarded"); String forwarded = requestHeader(request, "Forwarded");
if (forwarded != null) { if (forwarded != null) {
forwarded = extractForwardedFor(forwarded); forwarded = extractForwarded(forwarded);
if (forwarded != null) { if (forwarded != null) {
return forwarded; return forwarded;
} }
@ -215,12 +215,8 @@ public abstract class HttpServerTracer<REQUEST, RESPONSE, CONNECTION, STORAGE> e
// try X-Forwarded-For // try X-Forwarded-For
forwarded = requestHeader(request, "X-Forwarded-For"); forwarded = requestHeader(request, "X-Forwarded-For");
if (forwarded != null) { if (forwarded != null) {
// may be split by , forwarded = extractForwardedFor(forwarded);
int endIndex = forwarded.indexOf(','); if (forwarded != null) {
if (endIndex > 0) {
forwarded = forwarded.substring(0, endIndex);
}
if (!forwarded.isEmpty()) {
return forwarded; return forwarded;
} }
} }
@ -230,7 +226,7 @@ public abstract class HttpServerTracer<REQUEST, RESPONSE, CONNECTION, STORAGE> e
} }
// VisibleForTesting // VisibleForTesting
static String extractForwardedFor(String forwarded) { static String extractForwarded(String forwarded) {
int start = forwarded.toLowerCase().indexOf("for="); int start = forwarded.toLowerCase().indexOf("for=");
if (start < 0) { if (start < 0) {
return null; return null;
@ -239,9 +235,42 @@ public abstract class HttpServerTracer<REQUEST, RESPONSE, CONNECTION, STORAGE> e
if (start >= forwarded.length() - 1) { // the value after for= must not be empty if (start >= forwarded.length() - 1) { // the value after for= must not be empty
return null; return null;
} }
return extractIpAddress(forwarded, start);
}
// VisibleForTesting
static String extractForwardedFor(String forwarded) {
return extractIpAddress(forwarded, 0);
}
// from https://www.rfc-editor.org/rfc/rfc7239
// "Note that IPv6 addresses may not be quoted in
// X-Forwarded-For and may not be enclosed by square brackets, but they
// are quoted and enclosed in square brackets in Forwarded"
// and also (applying to Forwarded but not X-Forwarded-For)
// "It is important to note that an IPv6 address and any nodename with
// node-port specified MUST be quoted, since ':' is not an allowed
// character in 'token'."
private static String extractIpAddress(String forwarded, int start) {
if (forwarded.length() == start) {
return null;
}
if (forwarded.charAt(start) == '"') {
return extractIpAddress(forwarded, start + 1);
}
if (forwarded.charAt(start) == '[') {
int end = forwarded.indexOf(']', start + 1);
if (end == -1) {
return null;
}
return forwarded.substring(start + 1, end);
}
boolean inIpv4 = false;
for (int i = start; i < forwarded.length() - 1; i++) { for (int i = start; i < forwarded.length() - 1; i++) {
char c = forwarded.charAt(i); char c = forwarded.charAt(i);
if (c == ',' || c == ';') { if (c == '.') {
inIpv4 = true;
} else if (c == ',' || c == ';' || c == '"' || (inIpv4 && c == ':')) {
if (i == start) { // empty string if (i == start) { // empty string
return null; return null;
} }

View File

@ -11,19 +11,161 @@ import static org.junit.Assert.assertNull;
import org.junit.Test; import org.junit.Test;
public class HttpServerTracerTest { public class HttpServerTracerTest {
@Test
public void extractForwarded() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwarded("for=1.1.1.1"));
}
@Test
public void extractForwardedIpv6() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwarded("for=\"[1111:1111:1111:1111:1111:1111:1111:1111]\""));
}
@Test
public void extractForwardedWithPort() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwarded("for=\"1.1.1.1:2222\""));
}
@Test
public void extractForwardedIpv6WithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwarded(
"for=\"[1111:1111:1111:1111:1111:1111:1111:1111]:2222\""));
}
@Test
public void extractForwardedCaps() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwarded("For=1.1.1.1"));
}
@Test
public void extractForwardedMalformed() {
assertNull(HttpServerTracer.extractForwarded("for=;for=1.1.1.1"));
}
@Test
public void extractForwardedEmpty() {
assertNull(HttpServerTracer.extractForwarded(""));
}
@Test
public void extractForwardedEmptyValue() {
assertNull(HttpServerTracer.extractForwarded("for="));
}
@Test
public void extractForwardedEmptyValueWithSemicolon() {
assertNull(HttpServerTracer.extractForwarded("for=;"));
}
@Test
public void extractForwardedNoFor() {
assertNull(HttpServerTracer.extractForwarded("by=1.1.1.1;test=1.1.1.1"));
}
@Test
public void extractForwardedMultiple() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwarded("for=1.1.1.1;for=1.2.3.4"));
}
@Test
public void extractForwardedMultipleIpV6() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwarded(
"for=\"[1111:1111:1111:1111:1111:1111:1111:1111]\";for=1.2.3.4"));
}
@Test
public void extractForwardedMultipleWithPort() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwarded("for=\"1.1.1.1:2222\";for=1.2.3.4"));
}
@Test
public void extractForwardedMultipleIpV6WithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwarded(
"for=\"[1111:1111:1111:1111:1111:1111:1111:1111]:2222\";for=1.2.3.4"));
}
@Test
public void extractForwardedMixedSplitter() {
assertEquals(
"1.1.1.1",
HttpServerTracer.extractForwarded("test=abcd; by=1.2.3.4, for=1.1.1.1;for=1.2.3.4"));
}
@Test
public void extractForwardedMixedSplitterIpv6() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwarded(
"test=abcd; by=1.2.3.4, for=\"[1111:1111:1111:1111:1111:1111:1111:1111]\";for=1.2.3.4"));
}
@Test
public void extractForwardedMixedSplitterWithPort() {
assertEquals(
"1.1.1.1",
HttpServerTracer.extractForwarded(
"test=abcd; by=1.2.3.4, for=\"1.1.1.1:2222\";for=1.2.3.4"));
}
@Test
public void extractForwardedMixedSplitterIpv6WithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwarded(
"test=abcd; by=1.2.3.4, for=\"[1111:1111:1111:1111:1111:1111:1111:1111]:2222\";for=1.2.3.4"));
}
@Test @Test
public void extractForwardedFor() { public void extractForwardedFor() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("for=1.1.1.1")); assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("1.1.1.1"));
} }
@Test @Test
public void extractForwardedForCaps() { public void extractForwardedForIpv6() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("For=1.1.1.1")); assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("\"[1111:1111:1111:1111:1111:1111:1111:1111]\""));
} }
@Test @Test
public void extractForwardedForMalformed() { public void extractForwardedForIpv6Unquoted() {
assertNull(HttpServerTracer.extractForwardedFor("for=;for=1.1.1.1")); assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("[1111:1111:1111:1111:1111:1111:1111:1111]"));
}
@Test
public void extractForwardedForIpv6Unbracketed() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("1111:1111:1111:1111:1111:1111:1111:1111"));
}
@Test
public void extractForwardedForWithPort() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("1.1.1.1:2222"));
}
@Test
public void extractForwardedForIpv6WithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("\"[1111:1111:1111:1111:1111:1111:1111:1111]:2222\""));
}
@Test
public void extractForwardedForIpv6UnquotedWithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("[1111:1111:1111:1111:1111:1111:1111:1111]:2222"));
} }
@Test @Test
@ -31,30 +173,51 @@ public class HttpServerTracerTest {
assertNull(HttpServerTracer.extractForwardedFor("")); assertNull(HttpServerTracer.extractForwardedFor(""));
} }
@Test
public void extractForwardedForEmptyValue() {
assertNull(HttpServerTracer.extractForwardedFor("for="));
}
@Test
public void extractForwardedForEmptyValueWithSemicolon() {
assertNull(HttpServerTracer.extractForwardedFor("for=;"));
}
@Test
public void extractForwardedForNoFor() {
assertNull(HttpServerTracer.extractForwardedFor("by=1.1.1.1;test=1.1.1.1"));
}
@Test @Test
public void extractForwardedForMultiple() { public void extractForwardedForMultiple() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("for=1.1.1.1;for=1.2.3.4")); assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("1.1.1.1,1.2.3.4"));
} }
@Test @Test
public void extractForwardedForMixedSplitter() { public void extractForwardedForMultipleIpv6() {
assertEquals( assertEquals(
"1.1.1.1", "1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("test=abcd; by=1.2.3.4, for=1.1.1.1;for=1.2.3.4")); HttpServerTracer.extractForwardedFor(
"\"[1111:1111:1111:1111:1111:1111:1111:1111]\",1.2.3.4"));
}
@Test
public void extractForwardedForMultipleIpv6Unquoted() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("[1111:1111:1111:1111:1111:1111:1111:1111],1.2.3.4"));
}
@Test
public void extractForwardedForMultipleIpv6Unbracketed() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor("1111:1111:1111:1111:1111:1111:1111:1111,1.2.3.4"));
}
@Test
public void extractForwardedForMultipleWithPort() {
assertEquals("1.1.1.1", HttpServerTracer.extractForwardedFor("1.1.1.1:2222,1.2.3.4"));
}
@Test
public void extractForwardedForMultipleIpv6WithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor(
"\"[1111:1111:1111:1111:1111:1111:1111:1111]:2222\",1.2.3.4"));
}
@Test
public void extractForwardedForMultipleIpv6UnquotedWithPort() {
assertEquals(
"1111:1111:1111:1111:1111:1111:1111:1111",
HttpServerTracer.extractForwardedFor(
"[1111:1111:1111:1111:1111:1111:1111:1111]:2222,1.2.3.4"));
} }
} }