Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quarkus REST: Fix two Date issues regarding preconditions #41123

Merged
merged 1 commit into from
Jun 12, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.quarkus.resteasy.reactive.server.test.preconditions;

import static io.restassured.RestAssured.get;

import java.time.Instant;
import java.util.Date;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class DatePreconditionTests {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Resource.class));

// Make sure we test a subtype of Date, since that is what Hibernate ORM gives us most of the time (hah)
// Also make sure we have non-zero milliseconds, since that will be the case for most date values representing
// "now", and we want to make sure pre-conditions work (second-resolution)
static final Date date = new Date(Date.from(Instant.parse("2007-12-03T10:15:30.24Z")).getTime()) {
};

public static class Something {
}

@Test
public void test() {
get("/preconditions")
.then()
.statusCode(200)
.header("Last-Modified", "Mon, 03 Dec 2007 10:15:30 GMT")
.body(Matchers.equalTo("foo"));
RestAssured
.with()
.header("If-Modified-Since", "Mon, 03 Dec 2007 10:15:30 GMT")
.get("/preconditions")
.then()
.statusCode(304);
}

@Path("/preconditions")
public static class Resource {
@GET
public Response get(Request request) {
ResponseBuilder resp = request.evaluatePreconditions(date);
if (resp != null) {
return resp.build();
}
return Response.ok("foo").lastModified(date).build();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ public <T> HeaderDelegate<T> createHeaderDelegate(Class<T> type) throws IllegalA
}
if (type.equals(MediaType.class)) {
return (HeaderDelegate<T>) MediaTypeHeaderDelegate.INSTANCE;
} else if (type.equals(Date.class)) {
} else if (Date.class.isAssignableFrom(type)) {
// for Date, we do subtypes too, because ORM will instantiate java.util.Date as subtypes
// and it's extremely likely we get those here, and we still have to generate a valid
// date representation for them, rather than Object.toString which will be wrong
return (HeaderDelegate<T>) DateDelegate.INSTANCE;
} else if (type.equals(CacheControl.class)) {
return (HeaderDelegate<T>) CacheControlDelegate.INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private boolean isRfc7232preconditions() {
return true;//todo: do we need config for this?
}

@Override
public Variant selectVariant(List<Variant> variants) throws IllegalArgumentException {
if (variants == null || variants.size() == 0)
throw new IllegalArgumentException("Variant list must not be empty");
Expand All @@ -53,7 +54,7 @@ public Variant selectVariant(List<Variant> variants) throws IllegalArgumentExcep
return negotiation.getBestMatch(variants);
}

public List<EntityTag> convertEtag(List<String> tags) {
private List<EntityTag> convertEtag(List<String> tags) {
ArrayList<EntityTag> result = new ArrayList<EntityTag>();
for (String tag : tags) {
String[] split = tag.split(",");
Expand All @@ -64,7 +65,7 @@ public List<EntityTag> convertEtag(List<String> tags) {
return result;
}

public Response.ResponseBuilder ifMatch(List<EntityTag> ifMatch, EntityTag eTag) {
private Response.ResponseBuilder ifMatch(List<EntityTag> ifMatch, EntityTag eTag) {
boolean match = false;
for (EntityTag tag : ifMatch) {
if (tag.equals(eTag) || tag.getValue().equals("*")) {
Expand All @@ -78,7 +79,7 @@ public Response.ResponseBuilder ifMatch(List<EntityTag> ifMatch, EntityTag eTag)

}

public Response.ResponseBuilder ifNoneMatch(List<EntityTag> ifMatch, EntityTag eTag) {
private Response.ResponseBuilder ifNoneMatch(List<EntityTag> ifMatch, EntityTag eTag) {
boolean match = false;
for (EntityTag tag : ifMatch) {
if (tag.equals(eTag) || tag.getValue().equals("*")) {
Expand All @@ -96,6 +97,7 @@ public Response.ResponseBuilder ifNoneMatch(List<EntityTag> ifMatch, EntityTag e
return null;
}

@Override
public Response.ResponseBuilder evaluatePreconditions(EntityTag eTag) {
if (eTag == null)
throw new IllegalArgumentException("ETag was null");
Expand All @@ -118,26 +120,36 @@ public Response.ResponseBuilder evaluatePreconditions(EntityTag eTag) {
return builder;
}

public Response.ResponseBuilder ifModifiedSince(String strDate, Date lastModified) {
private Response.ResponseBuilder ifModifiedSince(String strDate, Date lastModified) {
Date date = DateUtil.parseDate(strDate);

if (date.getTime() >= lastModified.getTime()) {
if (date.getTime() >= millisecondsWithSecondsPrecision(lastModified)) {
return Response.notModified();
}
return null;

}

public Response.ResponseBuilder ifUnmodifiedSince(String strDate, Date lastModified) {
private Response.ResponseBuilder ifUnmodifiedSince(String strDate, Date lastModified) {
Date date = DateUtil.parseDate(strDate);

if (date.getTime() >= lastModified.getTime()) {
if (date.getTime() >= millisecondsWithSecondsPrecision(lastModified)) {
return null;
}
return Response.status(Response.Status.PRECONDITION_FAILED).lastModified(lastModified);

}

/**
* We must compare header dates (seconds-precision) with dates that have the same precision,
* otherwise they may include milliseconds and they will never match the Last-Modified
* values that we generate from them (since we drop their milliseconds when we write the headers)
*/
private long millisecondsWithSecondsPrecision(Date lastModified) {
return (lastModified.getTime() / 1000) * 1000;
}

@Override
public Response.ResponseBuilder evaluatePreconditions(Date lastModified) {
if (lastModified == null)
throw new IllegalArgumentException("Param cannot be null");
Expand All @@ -159,6 +171,7 @@ public Response.ResponseBuilder evaluatePreconditions(Date lastModified) {
return builder;
}

@Override
public Response.ResponseBuilder evaluatePreconditions(Date lastModified, EntityTag eTag) {
if (lastModified == null)
throw new IllegalArgumentException("Last modified was null");
Expand All @@ -182,6 +195,7 @@ else if (lastModifiedBuilder == null && etagBuilder != null)
return rtn;
}

@Override
public Response.ResponseBuilder evaluatePreconditions() {
List<String> ifMatch = requestContext.getHttpHeaders().getRequestHeaders().get(HttpHeaders.IF_MATCH);
if (ifMatch == null || ifMatch.size() == 0) {
Expand Down
Loading