Skip to content

Commit

Permalink
Merge pull request #21 from FusionAuth/degroff/better-baseURL-guess
Browse files Browse the repository at this point in the history
Be smarter about resolve the port on the baseURL.
  • Loading branch information
robotdan authored Apr 23, 2024
2 parents 511dc9c + f54cfe9 commit 8627c22
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 20 deletions.
2 changes: 1 addition & 1 deletion build.savant
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jackson5Version = "3.0.1"
restifyVersion = "4.2.0"
testngVersion = "7.8.0"

project(group: "io.fusionauth", name: "java-http", version: "0.3.1", licenses: ["ApacheV2_0"]) {
project(group: "io.fusionauth", name: "java-http", version: "0.3.2", licenses: ["ApacheV2_0"]) {
workflow {
fetch {
// Dependency resolution order:
Expand Down
24 changes: 13 additions & 11 deletions java-http.ipr
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredTypes">
<set>
<option value="java.lang.String" />
<option value="java.util.List" />
</set>
</option>
Expand Down Expand Up @@ -184,7 +186,7 @@
</option>
<option name="RIGHT_MARGIN" value="140" />
<option name="WRAP_COMMENTS" value="true" />
<DB2CodeStyleSettings version="6">
<DB2CodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -221,7 +223,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</DB2CodeStyleSettings>
<DerbyCodeStyleSettings version="6">
<DerbyCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -262,7 +264,7 @@
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
</GroovyCodeStyleSettings>
<H2CodeStyleSettings version="6">
<H2CodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -299,7 +301,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</H2CodeStyleSettings>
<HSQLCodeStyleSettings version="6">
<HSQLCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -359,7 +361,7 @@
</option>
<option name="JD_INDENT_ON_CONTINUATION" value="true" />
</JavaCodeStyleSettings>
<MSSQLCodeStyleSettings version="6">
<MSSQLCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -402,7 +404,7 @@
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<MySQLCodeStyleSettings version="6">
<MySQLCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -439,7 +441,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</MySQLCodeStyleSettings>
<OracleCodeStyleSettings version="6">
<OracleCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -476,7 +478,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</OracleCodeStyleSettings>
<PostgresCodeStyleSettings version="6">
<PostgresCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -513,7 +515,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</PostgresCodeStyleSettings>
<SQLiteCodeStyleSettings version="6">
<SQLiteCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down Expand Up @@ -550,7 +552,7 @@
<option name="EXPR_CASE_THEN_WRAP" value="true" />
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
</SQLiteCodeStyleSettings>
<SqlCodeStyleSettings version="6">
<SqlCodeStyleSettings version="7">
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
<option name="ALIAS_CASE" value="4" />
Expand Down Expand Up @@ -591,7 +593,7 @@
<option name="WRAP_INSIDE_WHERE" value="5" />
<option name="WRAP_INSIDE_SET" value="5" />
</SqlCodeStyleSettings>
<SybaseCodeStyleSettings version="6">
<SybaseCodeStyleSettings version="7">
<option name="USE_GENERAL_STYLE" value="false" />
<option name="TYPE_CASE" value="3" />
<option name="CUSTOM_TYPE_CASE" value="3" />
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>io.fusionauth</groupId>
<artifactId>java-http</artifactId>
<version>0.3.1</version>
<version>0.3.2</version>
<packaging>jar</packaging>

<name>Java HTTP library (client and server)</name>
Expand Down
58 changes: 52 additions & 6 deletions src/main/java/io/fusionauth/http/server/HTTPRequest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2023, FusionAuth, All Rights Reserved
* Copyright (c) 2022-2024, FusionAuth, 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 All @@ -17,6 +17,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
Expand Down Expand Up @@ -249,11 +250,7 @@ public String getBaseURL() {
}

String serverName = getHost().toLowerCase();
int serverPort = getPort();
// Ignore port 80 for http
if (getScheme().equalsIgnoreCase("http") && serverPort == 80) {
serverPort = -1;
}
int serverPort = getBaseURLServerPort();

String uri = scheme + "://" + serverName;
if (serverPort > 0) {
Expand Down Expand Up @@ -768,4 +765,53 @@ private void decodeHeader(String name, String value) {
break;
}
}

/**
* Try and infer the port if the X-Forwarded-Port header is not present.
*
* @return the server port
*/
private int getBaseURLServerPort() {
// Ignore port 80 for http
int serverPort = getPort();
if (scheme.equalsIgnoreCase("http") && serverPort == 80) {
serverPort = -1;
}

// See if we can infer a better choice for the port than the current serverPort.

// If we already have an X-Forwarded-Port header, nothing to do here.
if (getHeader(Headers.XForwardedPort) != null) {
return serverPort;
}

// If we don't have a host header, nothing to do here.
String xHost = getHeader(Headers.XForwardedHost);
if (xHost == null) {
return serverPort;
}

// If we can pull the port from the X-Forwarded-Host header, let's do that.
// - This is effectively the same as X-Forwarded-Port
try {
int hostPort = URI.create("https://" + xHost).getPort();
if (hostPort != -1) {
return hostPort;
}
} catch (Exception ignore) {
// If we can't parse the hostHeader, keep the existing resolved port
return serverPort;
}

// If we don't have an X-Forwarded-Proto header, or it is not https, nothing to do here.
// - We must have the X-Forwarded-Proto: https in order to assume 443
if (!"https".equals(getHeader(Headers.XForwardedProto))) {
return serverPort;
}

// If we made this far, we have met all conditions for assuming port 443.
// - We are missing the X-Forwarded-Port header, we have an X-Forwarded-Proto header of https, and we have an X-Forwarded-Host
// header value that has not defined a port.
return 443;
}
}
81 changes: 80 additions & 1 deletion src/test/java/io/fusionauth/http/HTTPRequestTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, FusionAuth, All Rights Reserved
* Copyright (c) 2022-2024, FusionAuth, 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 All @@ -18,12 +18,14 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import io.fusionauth.http.HTTPValues.Headers;
import io.fusionauth.http.server.HTTPRequest;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

/**
* Tests the HTTPRequest.
Expand Down Expand Up @@ -51,6 +53,57 @@ public void decodeHeaders() {
assertEquals(request.getCharacterEncoding(), StandardCharsets.UTF_8);
}

@Test
public void getBaseURL() {
// Use case 1: missing X-Forwarded-Port, infer it from https
assertBaseURL("https://acme.com",
"X-Forwarded-Host", "acme.com",
"X-Forwarded-Proto", "https");

// Use case 2: Set a port on the Host, we will use that.
assertBaseURL("https://acme.com:8192",
"X-Forwarded-Host", "acme.com:8192",
"X-Forwarded-Proto", "https");

// Use case 3: Set port from the X-Forwarded-Port header
assertBaseURL("https://acme.com",
"X-Forwarded-Host", "acme.com",
"X-Forwarded-Port", "443",
"X-Forwarded-Proto", "https");

// Use case 4: Set port from the X-Forwarded-Port header, non-standard
assertBaseURL("https://acme.com:8192",
"X-Forwarded-Host", "acme.com",
"X-Forwarded-Port", "8192",
"X-Forwarded-Proto", "https");

// Use case 5: Missing X-Forwarded-Proto header, cannot infer 443
assertBaseURL("http://acme.com:8080",
"X-Forwarded-Host", "acme.com");

// Use case 6: Malformed X-Forwarded-Host header, so we'll ignore the port on the -Host header.
assertBaseURL("https://acme.com:8080",
"X-Forwarded-Host", "acme.com:##",
"X-Forwarded-Proto", "https");

// Use case 7: Missing X-Forwarded-Host
assertBaseURL("https://localhost:8080",
"X-Forwarded-Proto", "https");

// Use case 8: http and port 80
assertBaseURL("https://localhost",
request -> request.setPort(80),
"X-Forwarded-Proto", "https");

// Use case 9: https and port 80
assertBaseURL("http://localhost",
request -> {
request.setPort(80);
request.setScheme("https");
},
"X-Forwarded-Proto", "http");
}

@Test
public void hostHeaderPortHandling() {
// positive cases
Expand Down Expand Up @@ -124,6 +177,32 @@ public void queryString() {
assertEquals(request.getURLParameters(), Map.of("name", List.of("")));
}

private void assertBaseURL(String expected, Consumer<HTTPRequest> consumer, String... headers) {
HTTPRequest request = new HTTPRequest();

request.setScheme("http");
request.setHost("localhost");
request.setPort(8080);

if (consumer != null) {
consumer.accept(request);
}

if (headers.length % 2 != 0) {
fail("You need to provide pairs.");
}

for (int i = 0; i < headers.length; i = i + 2) {
request.setHeader(headers[i], headers[i + 1]);
}

assertEquals(request.getBaseURL(), expected);
}

private void assertBaseURL(String expected, String... headers) {
assertBaseURL(expected, null, headers);
}

private void assertURLs(String scheme, String source, String host, int port, String baseURL) {
HTTPRequest request = new HTTPRequest();
request.setScheme(scheme);
Expand Down

0 comments on commit 8627c22

Please sign in to comment.