From dc059209309f153aec8890134404fa233a6dedb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Witkowski?=
<38762002+lwitkowski@users.noreply.github.com>
Date: Thu, 28 Mar 2024 14:47:05 +0100
Subject: [PATCH] Fix for: Exception occurring when encoding the path
containing unwise characters (i.e space) (#364)
---
.../resteasy/problem/GenericMappersIT.java | 9 +++++
.../resteasy/problem/InstanceUtils.java | 33 +++++++++++++++++++
.../jackson/JacksonProblemSerializer.java | 3 +-
.../problem/jsonb/JsonbProblemSerializer.java | 3 +-
.../ProblemDefaultsProvider.java | 7 ++--
.../jackson/JacksonProblemSerializerTest.java | 27 ++++++++++++---
.../jsonb/JsonbProblemSerializerTest.java | 27 ++++++++++++---
.../ProblemDefaultsProviderTest.java | 10 ++++++
8 files changed, 104 insertions(+), 15 deletions(-)
create mode 100644 runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/InstanceUtils.java
diff --git a/integration-test/src/test/java/com/tietoevry/quarkus/resteasy/problem/GenericMappersIT.java b/integration-test/src/test/java/com/tietoevry/quarkus/resteasy/problem/GenericMappersIT.java
index adccab6c..d2eb4ebe 100644
--- a/integration-test/src/test/java/com/tietoevry/quarkus/resteasy/problem/GenericMappersIT.java
+++ b/integration-test/src/test/java/com/tietoevry/quarkus/resteasy/problem/GenericMappersIT.java
@@ -3,6 +3,7 @@
import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
@@ -54,4 +55,12 @@ void shouldRegisterProblemPostProcessorCustomImplementationsFromCDI() {
.then()
.body("injected_from_custom_post_processor", equalTo("you called /throw/generic/runtime-exception"));
}
+
+ @Test
+ void instanceShouldHandleUnwiseCharactersProperly() {
+ given()
+ .get("/non|existing path /with unwisecharacters")
+ .then()
+ .body("instance", equalTo("/non|existing path /with unwisecharacters"));
+ }
}
diff --git a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/InstanceUtils.java b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/InstanceUtils.java
new file mode 100644
index 00000000..90fa89af
--- /dev/null
+++ b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/InstanceUtils.java
@@ -0,0 +1,33 @@
+package com.tietoevry.quarkus.resteasy.problem;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+public final class InstanceUtils {
+
+ public static URI pathToInstance(String path) {
+ if (path == null) {
+ return null;
+ }
+ try {
+ return new URI(encodeUnwiseCharacters(path));
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @see About unwise characters in RFC-2396
+ */
+ private static String encodeUnwiseCharacters(String path) {
+ return URLEncoder.encode(path, StandardCharsets.UTF_8);
+ }
+
+ public static String instanceToPath(URI instance) {
+ return URLDecoder.decode(instance.toString(), StandardCharsets.UTF_8);
+ }
+
+}
diff --git a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializer.java b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializer.java
index f8d17567..280a3c80 100644
--- a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializer.java
+++ b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializer.java
@@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.tietoevry.quarkus.resteasy.problem.HttpProblem;
+import com.tietoevry.quarkus.resteasy.problem.InstanceUtils;
import java.io.IOException;
import java.util.Map;
@@ -36,7 +37,7 @@ public void serialize(final HttpProblem problem, final JsonGenerator json, final
json.writeStringField("detail", problem.getDetail());
}
if (problem.getInstance() != null) {
- json.writeStringField("instance", problem.getInstance().toASCIIString());
+ json.writeStringField("instance", InstanceUtils.instanceToPath(problem.getInstance()));
}
for (Map.Entry entry : problem.getParameters().entrySet()) {
diff --git a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializer.java b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializer.java
index 6db26899..6f114eda 100644
--- a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializer.java
+++ b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializer.java
@@ -1,6 +1,7 @@
package com.tietoevry.quarkus.resteasy.problem.jsonb;
import com.tietoevry.quarkus.resteasy.problem.HttpProblem;
+import com.tietoevry.quarkus.resteasy.problem.InstanceUtils;
import jakarta.json.bind.serializer.JsonbSerializer;
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;
@@ -25,7 +26,7 @@ public void serialize(HttpProblem problem, JsonGenerator generator, Serializatio
generator.write("detail", problem.getDetail());
}
if (problem.getInstance() != null) {
- generator.write("instance", problem.getInstance().toASCIIString());
+ generator.write("instance", InstanceUtils.instanceToPath(problem.getInstance()));
}
problem.getParameters().forEach((key, value) -> ctx.serialize(key, value, generator));
diff --git a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProvider.java b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProvider.java
index 2f0fcad4..dc3082fd 100644
--- a/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProvider.java
+++ b/runtime/src/main/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProvider.java
@@ -1,6 +1,7 @@
package com.tietoevry.quarkus.resteasy.problem.postprocessing;
import com.tietoevry.quarkus.resteasy.problem.HttpProblem;
+import com.tietoevry.quarkus.resteasy.problem.InstanceUtils;
import java.net.URI;
/**
@@ -21,12 +22,8 @@ public HttpProblem apply(HttpProblem problem, ProblemContext context) {
}
return HttpProblem.builder(problem)
- .withInstance(defaultInstance(context))
+ .withInstance(InstanceUtils.pathToInstance(context.path))
.build();
}
- private URI defaultInstance(ProblemContext context) {
- return context.path == null ? null : URI.create(context.path);
- }
-
}
diff --git a/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializerTest.java b/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializerTest.java
index 978eb73b..b3c57907 100644
--- a/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializerTest.java
+++ b/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jackson/JacksonProblemSerializerTest.java
@@ -1,5 +1,6 @@
package com.tietoevry.quarkus.resteasy.problem.jackson;
+import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.core.JsonEncoding;
@@ -9,6 +10,7 @@
import com.tietoevry.quarkus.resteasy.problem.HttpProblemMother;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
@@ -34,8 +36,7 @@ void shouldSerializeAllFields() throws IOException {
serializer.serialize(problem, jsonGenerator, null);
- jsonGenerator.close();
- assertThat(outputStream.toString(StandardCharsets.UTF_8.name()))
+ assertThat(serializedProblem())
.isEqualTo(HttpProblemMother.SERIALIZED_COMPLEX_PROBLEM);
}
@@ -46,9 +47,27 @@ void shouldSerializeOnlyNotNullFields() throws IOException {
serializer.serialize(problem, jsonGenerator, null);
- jsonGenerator.close();
- assertThat(outputStream.toString(StandardCharsets.UTF_8.name()))
+ assertThat(serializedProblem())
.isEqualTo(HttpProblemMother.SERIALIZED_BAD_REQUEST_PROBLEM);
}
+ @Test
+ @DisplayName("Should decode uri for instance field")
+ void shouldDecodeUriForInstanceField() throws IOException {
+ HttpProblem problem = HttpProblem.builder()
+ .withStatus(NOT_FOUND)
+ .withInstance(URI.create("%2Fnon%7Cexisting%7Bpath+%2Fwith%7Bunwise%5Ccharacters%3E%23"))
+ .build();
+
+ serializer.serialize(problem, jsonGenerator, null);
+
+ assertThat(serializedProblem()).contains("""
+ "instance":"/non|existing{path /with{unwise\\\\characters>#"}""");
+ }
+
+ private String serializedProblem() throws IOException {
+ jsonGenerator.close();
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+
}
diff --git a/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializerTest.java b/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializerTest.java
index dd755d28..0f8dac7f 100644
--- a/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializerTest.java
+++ b/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/jsonb/JsonbProblemSerializerTest.java
@@ -1,5 +1,6 @@
package com.tietoevry.quarkus.resteasy.problem.jsonb;
+import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;
import static org.assertj.core.api.Assertions.assertThat;
import com.tietoevry.quarkus.resteasy.problem.HttpProblem;
@@ -9,6 +10,7 @@
import jakarta.json.bind.serializer.SerializationContext;
import jakarta.json.stream.JsonGenerator;
import java.io.ByteArrayOutputStream;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import org.eclipse.parsson.JsonProviderImpl;
import org.eclipse.yasson.internal.JsonbContext;
@@ -31,8 +33,7 @@ void shouldSerializeAllFields() {
serializer.serialize(problem, jsonGenerator, context);
- jsonGenerator.close();
- assertThat(outputStream.toString(StandardCharsets.UTF_8))
+ assertThat(serializedProblem())
.isEqualTo(HttpProblemMother.SERIALIZED_COMPLEX_PROBLEM);
}
@@ -43,9 +44,27 @@ void shouldSerializeOnlyNotNullFields() {
serializer.serialize(problem, jsonGenerator, context);
- jsonGenerator.close();
- assertThat(outputStream.toString(StandardCharsets.UTF_8))
+ assertThat(serializedProblem())
.isEqualTo(HttpProblemMother.SERIALIZED_BAD_REQUEST_PROBLEM);
}
+ @Test
+ @DisplayName("Should decode uri for instance field")
+ void shouldDecodeUriForInstanceField() {
+ HttpProblem problem = HttpProblem.builder()
+ .withStatus(NOT_FOUND)
+ .withInstance(URI.create("%2Fnon%7Cexisting%7Bpath+%2Fwith%7Bunwise%5Ccharacters%3E%23"))
+ .build();
+
+ serializer.serialize(problem, jsonGenerator, null);
+
+ assertThat(serializedProblem()).contains("""
+ "instance":"/non|existing{path /with{unwise\\\\characters>#"}""");
+ }
+
+ private String serializedProblem() {
+ jsonGenerator.close();
+ return outputStream.toString(StandardCharsets.UTF_8);
+ }
+
}
diff --git a/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProviderTest.java b/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProviderTest.java
index f54554ab..49935e2d 100644
--- a/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProviderTest.java
+++ b/runtime/src/test/java/com/tietoevry/quarkus/resteasy/problem/postprocessing/ProblemDefaultsProviderTest.java
@@ -34,4 +34,14 @@ void shouldNotOverrideExistingValues() {
assertThat(enhancedProblem.getInstance()).hasPath("/non-default-endpoint");
}
+ @Test
+ void shouldHandleUnwiseCharactersInPath() {
+ HttpProblem problemWithUnwiseCharactersInPath = processor.apply(
+ badRequestProblem(),
+ ProblemContext.of(new RuntimeException(), "/non|existing{path /with{unwise\\characters>#"));
+
+ assertThat(problemWithUnwiseCharactersInPath.getInstance())
+ .hasPath("/non|existing{path+/with{unwise\\characters>#");
+ }
+
}