Skip to content

Commit

Permalink
Fix two Date issues regarding preconditions
Browse files Browse the repository at this point in the history
- Round Dates to second precision when comparing them
- Lookup header delegate for Date and subtypes

Fixes #41110
  • Loading branch information
FroMage committed Jun 11, 2024
1 parent 99d63a7 commit 07b3f94
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 8 deletions.
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

0 comments on commit 07b3f94

Please sign in to comment.