Skip to content
Merged
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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Latest versions

* Latest stable version: `1.3.0`
* Latest stable version: `1.3.1`
* Now with 100% more virtual threads!
* Prior stable version `0.3.7`

Expand All @@ -27,20 +27,20 @@ To add this library to your project, you can include this dependency in your Mav
<dependency>
<groupId>io.fusionauth</groupId>
<artifactId>java-http</artifactId>
<version>1.3.0</version>
<version>1.3.1</version>
</dependency>
```

If you are using Gradle, you can add this to your build file:

```groovy
implementation 'io.fusionauth:java-http:1.3.0'
implementation 'io.fusionauth:java-http:1.3.1'
```

If you are using Savant, you can add this to your build file:

```groovy
dependency(id: "io.fusionauth:java-http:1.3.0")
dependency(id: "io.fusionauth:java-http:1.3.1")
```

## Examples Usages:
Expand Down
2 changes: 1 addition & 1 deletion build.savant
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ restifyVersion = "4.2.1"
slf4jVersion = "2.0.17"
testngVersion = "7.11.0"

project(group: "io.fusionauth", name: "java-http", version: "1.3.0", licenses: ["ApacheV2_0"]) {
project(group: "io.fusionauth", name: "java-http", version: "1.3.1", licenses: ["ApacheV2_0"]) {
workflow {
fetch {
// Dependency resolution order:
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/fusionauth/http/Cookie.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -245,7 +246,7 @@ public void addAttribute(String name, String value) {
return;
}

switch (name.toLowerCase()) {
switch (name.toLowerCase(Locale.ROOT)) {
case HTTPValues.CookieAttributes.DomainLower:
domain = value;
break;
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/fusionauth/http/io/MultipartStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -264,7 +265,7 @@ private void readHeaders(Map<String, HeaderValue> headers) throws IOException, P
var nextState = state.next(b);
if (nextState != state) {
switch (state) {
case HeaderName -> headerName = build.toString().toLowerCase();
case HeaderName -> headerName = build.toString().toLowerCase(Locale.ROOT);
case HeaderValue -> headers.put(headerName, HTTPTools.parseHeaderValue(build.toString()));
}

Expand Down
22 changes: 11 additions & 11 deletions src/main/java/io/fusionauth/http/server/HTTPRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ public void addCookies(Collection<Cookie> cookies) {
}

public void addHeader(String name, String value) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
headers.computeIfAbsent(name, key -> new ArrayList<>()).add(value);
decodeHeader(name, value);
}

public void addHeaders(String name, String... values) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
headers.computeIfAbsent(name, key -> new ArrayList<>()).addAll(List.of(values));

for (String value : values) {
Expand All @@ -179,7 +179,7 @@ public void addHeaders(String name, String... values) {
}

public void addHeaders(String name, Collection<String> values) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
headers.computeIfAbsent(name, key -> new ArrayList<>()).addAll(values);

for (String value : values) {
Expand Down Expand Up @@ -254,12 +254,12 @@ public Map<String, Object> getAttributes() {
public String getBaseURL() {
// Setting the wrong value in the X-Forwarded-Proto header seems to be a common issue that causes an exception during URI.create.
// Assuming request.getScheme() is not the problem, and it is related to the proxy configuration.
String scheme = getScheme().toLowerCase();
String scheme = getScheme().toLowerCase(Locale.ROOT);
if (!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
throw new IllegalArgumentException("The request scheme is invalid. Only http or https are valid schemes. The X-Forwarded-Proto header has a value of [" + getHeader(Headers.XForwardedProto) + "], this is likely an issue in your proxy configuration.");
}

String serverName = getHost().toLowerCase();
String serverName = getHost().toLowerCase(Locale.ROOT);
int serverPort = getBaseURLServerPort();

String uri = scheme + "://" + serverName;
Expand Down Expand Up @@ -381,7 +381,7 @@ public String getHeader(String name) {
}

public List<String> getHeaders(String name) {
return headers.get(name.toLowerCase());
return headers.get(name.toLowerCase(Locale.ROOT));
}

public Map<String, List<String>> getHeaders() {
Expand Down Expand Up @@ -650,11 +650,11 @@ public Object removeAttribute(String name) {
}

public void removeHeader(String name) {
headers.remove(name.toLowerCase());
headers.remove(name.toLowerCase(Locale.ROOT));
}

public void removeHeader(String name, String... values) {
List<String> actual = headers.get(name.toLowerCase());
List<String> actual = headers.get(name.toLowerCase(Locale.ROOT));
if (actual != null) {
actual.removeAll(List.of(values));
}
Expand All @@ -671,13 +671,13 @@ public void setAttribute(String name, Object value) {
}

public void setHeader(String name, String value) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
this.headers.put(name, new ArrayList<>(List.of(value)));
decodeHeader(name, value);
}

public void setHeaders(String name, String... values) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
this.headers.put(name, new ArrayList<>(List.of(values)));

for (String value : values) {
Expand All @@ -686,7 +686,7 @@ public void setHeaders(String name, String... values) {
}

public void setHeaders(String name, Collection<String> values) {
name = name.toLowerCase();
name = name.toLowerCase(Locale.ROOT);
this.headers.put(name, new ArrayList<>(values));

for (String value : values) {
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/io/fusionauth/http/server/HTTPResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -82,7 +83,7 @@ public void addHeader(String name, String value) {
return;
}

headers.computeIfAbsent(name.toLowerCase(), key -> new ArrayList<>()).add(value);
headers.computeIfAbsent(name.toLowerCase(Locale.ROOT), key -> new ArrayList<>()).add(value);
}

public void clearHeaders() {
Expand All @@ -102,7 +103,7 @@ public void close() throws IOException {
}

public boolean containsHeader(String name) {
String key = name.toLowerCase();
String key = name.toLowerCase(Locale.ROOT);
return headers.containsKey(key) && !headers.get(key).isEmpty();
}

Expand Down Expand Up @@ -174,12 +175,12 @@ public void setException(Throwable exception) {
}

public String getHeader(String name) {
String key = name.toLowerCase();
String key = name.toLowerCase(Locale.ROOT);
return headers.containsKey(key) && !headers.get(key).isEmpty() ? headers.get(key).getFirst() : null;
}

public List<String> getHeaders(String key) {
return headers.get(key.toLowerCase());
return headers.get(key.toLowerCase(Locale.ROOT));
}

public Map<String, List<String>> getHeadersMap() {
Expand Down Expand Up @@ -260,7 +261,7 @@ public void removeCookie(String name) {
*/
public void removeHeader(String name) {
if (name != null) {
headers.remove(name.toLowerCase());
headers.remove(name.toLowerCase(Locale.ROOT));
}
}

Expand Down Expand Up @@ -304,7 +305,7 @@ public void setHeader(String name, String value) {
return;
}

headers.put(name.toLowerCase(), new ArrayList<>(List.of(value)));
headers.put(name.toLowerCase(Locale.ROOT), new ArrayList<>(List.of(value)));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -493,7 +494,8 @@ public HTTPServerConfiguration withMaxRequestBodySize(Map<String, Integer> maxRe
this.maxRequestBodySize.clear();
// Add back a default to ensure we always have a fallback, can still be modified by the incoming configuration.
this.maxRequestBodySize.put("*", DefaultMaxRequestSizes.get("*"));
this.maxRequestBodySize.putAll(maxRequestBodySize);
// Store lower case keys
maxRequestBodySize.forEach((k, v) -> this.maxRequestBodySize.put(k.toLowerCase(Locale.ROOT), v));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ public void run() {
logger.trace("[{}] Closing socket. Client closed the connection. Reason [{}].", Thread.currentThread().threadId(), e.getMessage());
closeSocketOnly(CloseSocketReason.Expected);
} catch (HTTPProcessingException e) {
// Note that I am only tracing this, because this exception is mostly expected. Use closeSocketOnError so we can attempt to write a response.
logger.trace("[{}] Closing socket with status [{}]. An unhandled [{}] exception was taken. Reason [{}].", Thread.currentThread().threadId(), e.getStatus(), e.getClass().getSimpleName(), e.getMessage());
// These are expected, but are things the client may want to know about. Use closeSocketOnError so we can attempt to write a response.
logger.debug("[{}] Closing socket with status [{}]. An unhandled [{}] exception was taken. Reason [{}].", Thread.currentThread().threadId(), e.getStatus(), e.getClass().getSimpleName(), e.getMessage());
closeSocketOnError(response, e.getStatus());
} catch (TooManyBytesToDrainException e) {
// The request handler did not read the entire InputStream, we tried to drain it but there were more bytes remaining than the configured maximum.
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/io/fusionauth/http/util/HTTPTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.HexFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -58,6 +59,7 @@ public static int getMaxRequestBodySize(String contentType, Map<String, Integer>
}

// Exact match
contentType = contentType.toLowerCase(Locale.ROOT);
Integer maximumSize = maxRequestBodySize.get(contentType);
if (maximumSize != null) {
return maximumSize;
Expand Down Expand Up @@ -426,10 +428,10 @@ private static void parseHeaderParameter(char[] chars, int start, int end, Map<S
for (int i = start; i < end; i++) {
if (name == null && chars[i] == '*') {
encoded = true;
name = new String(chars, start, i - start).toLowerCase();
name = new String(chars, start, i - start).toLowerCase(Locale.ROOT);
start = i + 2;
} else if (name == null && chars[i] == '=') {
name = new String(chars, start, i - start).toLowerCase();
name = new String(chars, start, i - start).toLowerCase(Locale.ROOT);
start = i + 1;
} else if (name != null && encoded && charset == null && chars[i] == '\'') {
String charsetName = new String(chars, start, i - start);
Expand Down
4 changes: 3 additions & 1 deletion src/test/java/io/fusionauth/http/FormDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;

Expand Down Expand Up @@ -76,7 +77,8 @@ public void post_server_configuration_max_form_data(String scheme, boolean chunk
.withBodyParameterCount(42 * 1024)
.withBodyParameterSize(128)
// 4k * 33 > 128k
.withConfiguration(config -> config.withMaxRequestBodySize(Map.of(ContentTypes.Form, 128 * 1024)))
// - Use a UC Content-Type to make sure it still works
.withConfiguration(config -> config.withMaxRequestBodySize(Map.of(ContentTypes.Form.toUpperCase(Locale.ROOT), 128 * 1024)))
.expectResponse("""
HTTP/1.1 413 \r
connection: close\r
Expand Down
8 changes: 6 additions & 2 deletions src/test/java/io/fusionauth/http/util/HTTPToolsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import com.inversoft.json.ToString;
Expand Down Expand Up @@ -64,6 +65,9 @@ public void getMaxRequestBodySize() {
assertMaxConfiguredSize("text/css", 6, configuration);
assertMaxConfiguredSize("text/html", 7, configuration);

// Mixed case
assertMaxConfiguredSize("Application/JSON", 3, configuration);

// We don't expect this at runtime, but ideally we won't explode. These would be invalid values.
// - Some of these are legit Content-Type headers, but at runtime we will have already parsed the header so we do
// not expect any attributes;
Expand Down Expand Up @@ -283,15 +287,15 @@ private String hex(byte[] bytes) {
List<String> result = new ArrayList<>();
for (byte b : bytes) {

result.add(Integer.toHexString(0xFF & b).toUpperCase());
result.add(Integer.toHexString(0xFF & b).toUpperCase(Locale.ROOT));
}
return String.join(" ", result);
}

private String hex(String s) {
List<String> result = new ArrayList<>();
for (char ch : s.toCharArray()) {
result.add(Integer.toHexString(ch).toUpperCase());
result.add(Integer.toHexString(ch).toUpperCase(Locale.ROOT));
}
return String.join(" ", result);
}
Expand Down