Skip to content

Commit 8627c22

Browse files
authored
Merge pull request #21 from FusionAuth/degroff/better-baseURL-guess
Be smarter about resolve the port on the baseURL.
2 parents 511dc9c + f54cfe9 commit 8627c22

File tree

5 files changed

+147
-20
lines changed

5 files changed

+147
-20
lines changed

build.savant

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jackson5Version = "3.0.1"
1717
restifyVersion = "4.2.0"
1818
testngVersion = "7.8.0"
1919

20-
project(group: "io.fusionauth", name: "java-http", version: "0.3.1", licenses: ["ApacheV2_0"]) {
20+
project(group: "io.fusionauth", name: "java-http", version: "0.3.2", licenses: ["ApacheV2_0"]) {
2121
workflow {
2222
fetch {
2323
// Dependency resolution order:

java-http.ipr

+13-11
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
<component name="InspectionProjectProfileManager">
1313
<profile version="1.0">
1414
<option name="myName" value="Project Default" />
15+
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
1516
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true">
1617
<option name="ignoredTypes">
1718
<set>
19+
<option value="java.lang.String" />
1820
<option value="java.util.List" />
1921
</set>
2022
</option>
@@ -184,7 +186,7 @@
184186
</option>
185187
<option name="RIGHT_MARGIN" value="140" />
186188
<option name="WRAP_COMMENTS" value="true" />
187-
<DB2CodeStyleSettings version="6">
189+
<DB2CodeStyleSettings version="7">
188190
<option name="USE_GENERAL_STYLE" value="false" />
189191
<option name="TYPE_CASE" value="3" />
190192
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -221,7 +223,7 @@
221223
<option name="EXPR_CASE_THEN_WRAP" value="true" />
222224
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
223225
</DB2CodeStyleSettings>
224-
<DerbyCodeStyleSettings version="6">
226+
<DerbyCodeStyleSettings version="7">
225227
<option name="USE_GENERAL_STYLE" value="false" />
226228
<option name="TYPE_CASE" value="3" />
227229
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -262,7 +264,7 @@
262264
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
263265
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
264266
</GroovyCodeStyleSettings>
265-
<H2CodeStyleSettings version="6">
267+
<H2CodeStyleSettings version="7">
266268
<option name="USE_GENERAL_STYLE" value="false" />
267269
<option name="TYPE_CASE" value="3" />
268270
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -299,7 +301,7 @@
299301
<option name="EXPR_CASE_THEN_WRAP" value="true" />
300302
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
301303
</H2CodeStyleSettings>
302-
<HSQLCodeStyleSettings version="6">
304+
<HSQLCodeStyleSettings version="7">
303305
<option name="USE_GENERAL_STYLE" value="false" />
304306
<option name="TYPE_CASE" value="3" />
305307
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -359,7 +361,7 @@
359361
</option>
360362
<option name="JD_INDENT_ON_CONTINUATION" value="true" />
361363
</JavaCodeStyleSettings>
362-
<MSSQLCodeStyleSettings version="6">
364+
<MSSQLCodeStyleSettings version="7">
363365
<option name="USE_GENERAL_STYLE" value="false" />
364366
<option name="TYPE_CASE" value="3" />
365367
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -402,7 +404,7 @@
402404
<MarkdownNavigatorCodeStyleSettings>
403405
<option name="RIGHT_MARGIN" value="72" />
404406
</MarkdownNavigatorCodeStyleSettings>
405-
<MySQLCodeStyleSettings version="6">
407+
<MySQLCodeStyleSettings version="7">
406408
<option name="USE_GENERAL_STYLE" value="false" />
407409
<option name="TYPE_CASE" value="3" />
408410
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -439,7 +441,7 @@
439441
<option name="EXPR_CASE_THEN_WRAP" value="true" />
440442
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
441443
</MySQLCodeStyleSettings>
442-
<OracleCodeStyleSettings version="6">
444+
<OracleCodeStyleSettings version="7">
443445
<option name="USE_GENERAL_STYLE" value="false" />
444446
<option name="TYPE_CASE" value="3" />
445447
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -476,7 +478,7 @@
476478
<option name="EXPR_CASE_THEN_WRAP" value="true" />
477479
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
478480
</OracleCodeStyleSettings>
479-
<PostgresCodeStyleSettings version="6">
481+
<PostgresCodeStyleSettings version="7">
480482
<option name="USE_GENERAL_STYLE" value="false" />
481483
<option name="TYPE_CASE" value="3" />
482484
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -513,7 +515,7 @@
513515
<option name="EXPR_CASE_THEN_WRAP" value="true" />
514516
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
515517
</PostgresCodeStyleSettings>
516-
<SQLiteCodeStyleSettings version="6">
518+
<SQLiteCodeStyleSettings version="7">
517519
<option name="USE_GENERAL_STYLE" value="false" />
518520
<option name="TYPE_CASE" value="3" />
519521
<option name="CUSTOM_TYPE_CASE" value="3" />
@@ -550,7 +552,7 @@
550552
<option name="EXPR_CASE_THEN_WRAP" value="true" />
551553
<option name="PRIMARY_KEY_NAME_TEMPLATE" value="{table}_{columns}_pk" />
552554
</SQLiteCodeStyleSettings>
553-
<SqlCodeStyleSettings version="6">
555+
<SqlCodeStyleSettings version="7">
554556
<option name="TYPE_CASE" value="3" />
555557
<option name="CUSTOM_TYPE_CASE" value="3" />
556558
<option name="ALIAS_CASE" value="4" />
@@ -591,7 +593,7 @@
591593
<option name="WRAP_INSIDE_WHERE" value="5" />
592594
<option name="WRAP_INSIDE_SET" value="5" />
593595
</SqlCodeStyleSettings>
594-
<SybaseCodeStyleSettings version="6">
596+
<SybaseCodeStyleSettings version="7">
595597
<option name="USE_GENERAL_STYLE" value="false" />
596598
<option name="TYPE_CASE" value="3" />
597599
<option name="CUSTOM_TYPE_CASE" value="3" />

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<modelVersion>4.0.0</modelVersion>
33
<groupId>io.fusionauth</groupId>
44
<artifactId>java-http</artifactId>
5-
<version>0.3.1</version>
5+
<version>0.3.2</version>
66
<packaging>jar</packaging>
77

88
<name>Java HTTP library (client and server)</name>

src/main/java/io/fusionauth/http/server/HTTPRequest.java

+52-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022-2023, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2022-2024, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717

1818
import java.io.IOException;
1919
import java.io.InputStream;
20+
import java.net.URI;
2021
import java.nio.charset.Charset;
2122
import java.nio.charset.StandardCharsets;
2223
import java.time.Instant;
@@ -249,11 +250,7 @@ public String getBaseURL() {
249250
}
250251

251252
String serverName = getHost().toLowerCase();
252-
int serverPort = getPort();
253-
// Ignore port 80 for http
254-
if (getScheme().equalsIgnoreCase("http") && serverPort == 80) {
255-
serverPort = -1;
256-
}
253+
int serverPort = getBaseURLServerPort();
257254

258255
String uri = scheme + "://" + serverName;
259256
if (serverPort > 0) {
@@ -768,4 +765,53 @@ private void decodeHeader(String name, String value) {
768765
break;
769766
}
770767
}
768+
769+
/**
770+
* Try and infer the port if the X-Forwarded-Port header is not present.
771+
*
772+
* @return the server port
773+
*/
774+
private int getBaseURLServerPort() {
775+
// Ignore port 80 for http
776+
int serverPort = getPort();
777+
if (scheme.equalsIgnoreCase("http") && serverPort == 80) {
778+
serverPort = -1;
779+
}
780+
781+
// See if we can infer a better choice for the port than the current serverPort.
782+
783+
// If we already have an X-Forwarded-Port header, nothing to do here.
784+
if (getHeader(Headers.XForwardedPort) != null) {
785+
return serverPort;
786+
}
787+
788+
// If we don't have a host header, nothing to do here.
789+
String xHost = getHeader(Headers.XForwardedHost);
790+
if (xHost == null) {
791+
return serverPort;
792+
}
793+
794+
// If we can pull the port from the X-Forwarded-Host header, let's do that.
795+
// - This is effectively the same as X-Forwarded-Port
796+
try {
797+
int hostPort = URI.create("https://" + xHost).getPort();
798+
if (hostPort != -1) {
799+
return hostPort;
800+
}
801+
} catch (Exception ignore) {
802+
// If we can't parse the hostHeader, keep the existing resolved port
803+
return serverPort;
804+
}
805+
806+
// If we don't have an X-Forwarded-Proto header, or it is not https, nothing to do here.
807+
// - We must have the X-Forwarded-Proto: https in order to assume 443
808+
if (!"https".equals(getHeader(Headers.XForwardedProto))) {
809+
return serverPort;
810+
}
811+
812+
// If we made this far, we have met all conditions for assuming port 443.
813+
// - We are missing the X-Forwarded-Port header, we have an X-Forwarded-Proto header of https, and we have an X-Forwarded-Host
814+
// header value that has not defined a port.
815+
return 443;
816+
}
771817
}

src/test/java/io/fusionauth/http/HTTPRequestTest.java

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2022-2024, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,12 +18,14 @@
1818
import java.nio.charset.StandardCharsets;
1919
import java.util.List;
2020
import java.util.Map;
21+
import java.util.function.Consumer;
2122

2223
import io.fusionauth.http.HTTPValues.Headers;
2324
import io.fusionauth.http.server.HTTPRequest;
2425
import org.testng.annotations.Test;
2526
import static org.testng.Assert.assertEquals;
2627
import static org.testng.Assert.assertTrue;
28+
import static org.testng.Assert.fail;
2729

2830
/**
2931
* Tests the HTTPRequest.
@@ -51,6 +53,57 @@ public void decodeHeaders() {
5153
assertEquals(request.getCharacterEncoding(), StandardCharsets.UTF_8);
5254
}
5355

56+
@Test
57+
public void getBaseURL() {
58+
// Use case 1: missing X-Forwarded-Port, infer it from https
59+
assertBaseURL("https://acme.com",
60+
"X-Forwarded-Host", "acme.com",
61+
"X-Forwarded-Proto", "https");
62+
63+
// Use case 2: Set a port on the Host, we will use that.
64+
assertBaseURL("https://acme.com:8192",
65+
"X-Forwarded-Host", "acme.com:8192",
66+
"X-Forwarded-Proto", "https");
67+
68+
// Use case 3: Set port from the X-Forwarded-Port header
69+
assertBaseURL("https://acme.com",
70+
"X-Forwarded-Host", "acme.com",
71+
"X-Forwarded-Port", "443",
72+
"X-Forwarded-Proto", "https");
73+
74+
// Use case 4: Set port from the X-Forwarded-Port header, non-standard
75+
assertBaseURL("https://acme.com:8192",
76+
"X-Forwarded-Host", "acme.com",
77+
"X-Forwarded-Port", "8192",
78+
"X-Forwarded-Proto", "https");
79+
80+
// Use case 5: Missing X-Forwarded-Proto header, cannot infer 443
81+
assertBaseURL("http://acme.com:8080",
82+
"X-Forwarded-Host", "acme.com");
83+
84+
// Use case 6: Malformed X-Forwarded-Host header, so we'll ignore the port on the -Host header.
85+
assertBaseURL("https://acme.com:8080",
86+
"X-Forwarded-Host", "acme.com:##",
87+
"X-Forwarded-Proto", "https");
88+
89+
// Use case 7: Missing X-Forwarded-Host
90+
assertBaseURL("https://localhost:8080",
91+
"X-Forwarded-Proto", "https");
92+
93+
// Use case 8: http and port 80
94+
assertBaseURL("https://localhost",
95+
request -> request.setPort(80),
96+
"X-Forwarded-Proto", "https");
97+
98+
// Use case 9: https and port 80
99+
assertBaseURL("http://localhost",
100+
request -> {
101+
request.setPort(80);
102+
request.setScheme("https");
103+
},
104+
"X-Forwarded-Proto", "http");
105+
}
106+
54107
@Test
55108
public void hostHeaderPortHandling() {
56109
// positive cases
@@ -124,6 +177,32 @@ public void queryString() {
124177
assertEquals(request.getURLParameters(), Map.of("name", List.of("")));
125178
}
126179

180+
private void assertBaseURL(String expected, Consumer<HTTPRequest> consumer, String... headers) {
181+
HTTPRequest request = new HTTPRequest();
182+
183+
request.setScheme("http");
184+
request.setHost("localhost");
185+
request.setPort(8080);
186+
187+
if (consumer != null) {
188+
consumer.accept(request);
189+
}
190+
191+
if (headers.length % 2 != 0) {
192+
fail("You need to provide pairs.");
193+
}
194+
195+
for (int i = 0; i < headers.length; i = i + 2) {
196+
request.setHeader(headers[i], headers[i + 1]);
197+
}
198+
199+
assertEquals(request.getBaseURL(), expected);
200+
}
201+
202+
private void assertBaseURL(String expected, String... headers) {
203+
assertBaseURL(expected, null, headers);
204+
}
205+
127206
private void assertURLs(String scheme, String source, String host, int port, String baseURL) {
128207
HTTPRequest request = new HTTPRequest();
129208
request.setScheme(scheme);

0 commit comments

Comments
 (0)