JCLOUDS-1295: Support deprecated date formats in the Expires header

This commit is contained in:
Ignasi Barrera 2017-05-26 10:31:50 +02:00 committed by Svetoslav Neykov
parent 28c3c33bf0
commit db2f86bcec
No known key found for this signature in database
GPG Key ID: 979090BE59D0A896
5 changed files with 138 additions and 8 deletions

View File

@ -36,4 +36,5 @@ public interface DateCodecFactory {
DateCodec iso8601Seconds();
DateCodec asctime();
}

View File

@ -35,6 +35,7 @@ public class DateServiceDateCodecFactory implements DateCodecFactory {
private final DateCodec rfc1123Codec;
private final DateCodec iso8601Codec;
private final DateCodec iso8601SecondsCodec;
private final DateCodec asctime;
@Inject
public DateServiceDateCodecFactory(DateService dateService) {
@ -43,6 +44,7 @@ public class DateServiceDateCodecFactory implements DateCodecFactory {
this.rfc1123Codec = new DateServiceRfc1123Codec(dateService);
this.iso8601Codec = new DateServiceIso8601Codec(dateService);
this.iso8601SecondsCodec = new DateServiceIso8601SecondsCodec(dateService);
this.asctime = new DateServiceAsctimeCodec(dateService);
}
@Singleton
@ -153,6 +155,33 @@ public class DateServiceDateCodecFactory implements DateCodecFactory {
}
@Singleton
public static class DateServiceAsctimeCodec implements DateCodec {
protected final DateService dateService;
@Inject
public DateServiceAsctimeCodec(DateService dateService) {
this.dateService = checkNotNull(dateService, "dateService");
}
@Override
public Date toDate(String date) throws IllegalArgumentException {
return dateService.cDateParse(date);
}
@Override
public String toString(Date date) {
return dateService.cDateFormat(date);
}
@Override
public String toString() {
return "asctime()";
}
}
@Override
public DateCodec rfc822() {
return rfc822Codec;
@ -173,4 +202,9 @@ public class DateServiceDateCodecFactory implements DateCodecFactory {
return iso8601SecondsCodec;
}
@Override
public DateCodec asctime() {
return asctime;
}
}

View File

@ -28,6 +28,7 @@ import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static com.google.common.net.HttpHeaders.EXPIRES;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import javax.annotation.Resource;
@ -38,6 +39,7 @@ import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.jclouds.logging.Logger;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultimap.Builder;
import com.google.common.collect.Multimap;
@ -73,16 +75,22 @@ public interface ContentMetadataCodec {
protected Logger logger = Logger.NULL;
private final DateCodec httpExpiresDateCodec;
private final List<DateCodec> httpExpiresDateDecoders;
@Inject
public DefaultContentMetadataCodec(DateCodecFactory dateCodecs) {
httpExpiresDateCodec = dateCodecs.rfc1123();
httpExpiresDateDecoders = ImmutableList.of(dateCodecs.rfc1123(), dateCodecs.asctime());
}
protected DateCodec getExpiresDateCodec() {
return httpExpiresDateCodec;
}
protected List<DateCodec> getExpiresDateDecoders() {
return httpExpiresDateDecoders;
}
@Override
public Multimap<String, String> toHeaders(ContentMetadata md) {
Builder<String, String> builder = ImmutableMultimap.builder();
@ -134,14 +142,31 @@ public interface ContentMetadataCodec {
}
}
/**
* Parses the date from the given Expires header.
* <p>
* According to the RFC, dates should always come in RFC-1123 format.
* However, clients should also support older and deprecated formats for
* compatibility, so this method will try to parse an RFC-1123 date, and
* fallback to the ANSI C format.
*
* @see https://tools.ietf.org/html/rfc2616#section-3.3
*/
public Date parseExpires(String expires) {
try {
return (expires != null) ? getExpiresDateCodec().toDate(expires) : null;
} catch (IllegalArgumentException e) {
logger.debug("Invalid Expires header (%s); should be in RFC-1123 format; treating as already expired: %s",
expires, e.getMessage());
return new Date(0);
if (expires == null)
return null;
for (DateCodec decoder : getExpiresDateDecoders()) {
try {
return decoder.toDate(expires);
} catch (IllegalArgumentException ex) {
logger.trace("Expires header (%s) is not in the expected %s format", expires, decoder);
// Continue trying the other decoders
}
}
logger.debug("Invalid Expires header (%s); should be in RFC-1123 format; treating as already expired", expires);
return new Date(0);
}
}
}

View File

@ -33,6 +33,7 @@ public class DateServiceDateCodecFactoryTest {
private DateCodec rfc1123Codec;
private DateCodec iso8601Codec;
private DateCodec iso8601SecondsCodec;
private DateCodec asctimeCodec;
@BeforeTest
public void setUp() {
@ -41,6 +42,7 @@ public class DateServiceDateCodecFactoryTest {
rfc1123Codec = simpleDateCodecFactory.rfc1123();
iso8601Codec = simpleDateCodecFactory.iso8601();
iso8601SecondsCodec = simpleDateCodecFactory.iso8601Seconds();
asctimeCodec = simpleDateCodecFactory.asctime();
}
@Test
@ -110,4 +112,21 @@ public class DateServiceDateCodecFactoryTest {
} catch (IllegalArgumentException e) {
}
}
@Test
public void testCodecForAsctime() {
Date date = new Date(1000);
assertEquals(asctimeCodec.toDate(asctimeCodec.toString(date)), date);
assertEquals(asctimeCodec.toDate("Thu Dec 01 16:00:00 GMT 1994"), new Date(786297600000L));
}
@Test
public void testCodecForAsctimeThrowsParseExceptionWhenMalformed() {
try {
asctimeCodec.toDate("-");
fail();
} catch (IllegalArgumentException e) {
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.io;
import static org.testng.Assert.assertEquals;
import java.util.Date;
import org.jclouds.date.internal.DateServiceDateCodecFactory;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.io.ContentMetadataCodec.DefaultContentMetadataCodec;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "DefaultContentMetadataCodecTest")
public class DefaultContentMetadataCodecTest {
private final DateServiceDateCodecFactory codecfactory = new DateServiceDateCodecFactory(
new SimpleDateFormatDateService());
public void testCanParseRFC1123Dates() {
DefaultContentMetadataCodec codec = new DefaultContentMetadataCodec(codecfactory);
Date parsed = codec.parseExpires("Thu, 01 Dec 1994 16:00:00 GMT");
assertEquals(parsed, new Date(786297600000L));
}
public void testCanParseAsctimeDates() {
DefaultContentMetadataCodec codec = new DefaultContentMetadataCodec(codecfactory);
Date parsed = codec.parseExpires("Thu Dec 01 16:00:00 GMT 1994");
assertEquals(parsed, new Date(786297600000L));
}
public void testFallbackToExpiredDate() {
DefaultContentMetadataCodec codec = new DefaultContentMetadataCodec(codecfactory);
Date parsed = codec.parseExpires("1994-12-01T16:00:00.000Z");
assertEquals(parsed, new Date(0));
}
}