Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

M3u add parsing of forwarded header wip #716

Open
wants to merge 4 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/freenet/clients/http/FProxyToadlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,6 @@ private String getSchemeHostAndPort(ToadletContext ctx) {

// get uri host and headers
MultiValueTable<String, String> headers = ctx.getHeaders();
// TODO: parse the Forwarded header, too. Skipped here to reduce the scope.
String uriScheme = ctx.getUri().getScheme();
String uriHost = ctx.getUri().getHost();

Expand Down
46 changes: 39 additions & 7 deletions src/freenet/clients/http/utils/UriFilterProxyHeaderParser.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package freenet.clients.http.utils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -12,13 +14,13 @@
public class UriFilterProxyHeaderParser {
private UriFilterProxyHeaderParser() {}

public static SchemeAndHostWithPort parse (
public static SchemeAndHostWithPort parse(
Option<?> fProxyPortConfig,
Option<?> fProxyBindToConfig,
String uriScheme,
String uriHost,
MultiValueTable<String, String> headers
) {
) {
Set<String> safeProtocols = new HashSet<>(Arrays.asList("http", "https"));

List<String> bindToHosts = Arrays.stream(fProxyBindToConfig.getValueString().split(","))
Expand All @@ -38,14 +40,14 @@ public static SchemeAndHostWithPort parse (
safeHosts.addAll(safeHosts.stream()
.map(host -> host + ":" + port)
.collect(Collectors.toList()));

// check uri host and headers
String protocol = headers.containsKey("x-forwarded-proto")
Map<String, String> forwarded = parseForwardedHeader(headers.get("forwarded"));
String protocol = forwarded.getOrDefault("proto", headers.containsKey("x-forwarded-proto")
? headers.get("x-forwarded-proto")
: uriScheme != null && !uriScheme.trim().isEmpty() ? uriScheme : "http";
String host = headers.containsKey("x-forwarded-host")
: uriScheme != null && !uriScheme.trim().isEmpty() ? uriScheme : "http");
String host = forwarded.getOrDefault("host", headers.containsKey("x-forwarded-host")
? headers.get("x-forwarded-host")
: uriHost != null && !uriHost.trim().isEmpty() ? uriHost : headers.get("host");
: uriHost != null && !uriHost.trim().isEmpty() ? uriHost : headers.get("host"));
// check allow list
if (!safeProtocols.contains(protocol)) {
protocol = "http";
Expand All @@ -72,4 +74,34 @@ public String toString() {
return scheme + "://" + host;
}
}

static Map<String, String> parseForwardedHeader(String forwarded) {
if (forwarded == null || forwarded.trim().isEmpty()) {
return new HashMap<>();
}
Map<String, String> headerParams = new HashMap<>();

// if a multi-value header is given, only use the first value.
int indexOfComma = forwarded.indexOf(',');
if (indexOfComma != -1) {
forwarded = forwarded.substring(0, indexOfComma);
}
boolean hasAtLeastOneKey = forwarded.indexOf('=') != -1;
boolean hasMultipleKeys = forwarded.indexOf(';') != -1;
String[] fields;
if (hasMultipleKeys) {
fields = forwarded.split(";");
} else if (hasAtLeastOneKey) {
fields = new String[]{ forwarded };
} else {
return headerParams;
}
for (String field : fields) {
if (field.indexOf('=') != 1) {
String[] keyAndValue = field.split("=");
headerParams.put(keyAndValue[0].toLowerCase(), keyAndValue[1]);
}
}
return headerParams;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package freenet.clients.http.utils;

import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import java.util.HashMap;
import java.util.Map;

import org.hamcrest.Matchers;
import org.junit.Test;

import freenet.config.Config;
import freenet.config.InvalidConfigValueException;
import freenet.config.NodeNeedRestartException;
import freenet.config.StringOption;
import freenet.support.MultiValueTable;
import freenet.support.api.StringCallback;
Expand Down Expand Up @@ -275,6 +278,60 @@ public void disallowedUriWithAllowedHostButDisallowedPortIsIgnored()
"http://127.0.0.1:8888");
}

@Test
public void forwardedHeaderProvidesIp() {
String forwarded = "for=192.0.2.172";
Map<String, String> parsedHeader = UriFilterProxyHeaderParser.parseForwardedHeader(
forwarded);
assertThat(parsedHeader.keySet(), Matchers.containsInAnyOrder("for"));;
assertThat(parsedHeader.values(), Matchers.containsInAnyOrder("192.0.2.172"));
}

@Test
public void forwardedHeaderProvidesIpv6AndPort() {
String forwarded = "for=[2001:db8:cafe::17]:4711";
Map<String, String> parsedHeader = UriFilterProxyHeaderParser.parseForwardedHeader(
forwarded);
assertThat(parsedHeader.keySet(), Matchers.containsInAnyOrder("for"));;
assertThat(parsedHeader.values(), Matchers.containsInAnyOrder("[2001:db8:cafe::17]:4711"));
}

@Test
public void forwardedHeaderProvidesIpCaseInsensitive() {
String forwarded = "For=192.0.2.172";
Map<String, String> parsedHeader = UriFilterProxyHeaderParser.parseForwardedHeader(
forwarded);
assertThat(parsedHeader.keySet(), Matchers.containsInAnyOrder("for"));;
assertThat(parsedHeader.values(), Matchers.containsInAnyOrder("192.0.2.172"));
}

@Test
public void forwardedHeaderProvidesIpProtoBy() {
String forwarded = "for=192.0.2.60;proto=http;by=203.0.113.43";
Map<String, String> parsedHeader = UriFilterProxyHeaderParser.parseForwardedHeader(
forwarded);
assertThat(parsedHeader.keySet(), Matchers.containsInAnyOrder("for", "proto", "by"));;
assertThat(parsedHeader.values(), Matchers.containsInAnyOrder("192.0.2.60", "http", "203.0.113.43"));
}

@Test
public void forwardedHeaderProvidesIpProtoByOnlyAllowTheFirst() {
String forwarded = "for=192.0.2.60;proto=http;by=203.0.113.43, for=198.51.100.17;proto=http;by=203.0.113.43";
Map<String, String> parsedHeader = UriFilterProxyHeaderParser.parseForwardedHeader(
forwarded);
assertThat(parsedHeader.keySet(), Matchers.containsInAnyOrder("for", "proto", "by"));;
assertThat(parsedHeader.values(), Matchers.containsInAnyOrder("192.0.2.60", "http", "203.0.113.43"));
}

@Test
public void forwardedHeaderProvidesIpOnlyAllowTheFirst() {
String forwarded = "for=192.0.2.60, for=198.51.100.17;proto=http;by=203.0.113.43";
Map<String, String> parsedHeader = UriFilterProxyHeaderParser.parseForwardedHeader(
forwarded);
assertThat(parsedHeader.keySet(), Matchers.containsInAnyOrder("for"));;
assertThat(parsedHeader.values(), Matchers.containsInAnyOrder("192.0.2.60"));
}

private void testUriPrefixMatchesExpected(
String fProxyPort,
String fProxyBindTo,
Expand Down