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>#"); + } + }