diff --git a/core/src/main/java/org/jclouds/date/DateCodecFactory.java b/core/src/main/java/org/jclouds/date/DateCodecFactory.java index 19da52f5e0..1fb4ba0751 100644 --- a/core/src/main/java/org/jclouds/date/DateCodecFactory.java +++ b/core/src/main/java/org/jclouds/date/DateCodecFactory.java @@ -36,4 +36,5 @@ public interface DateCodecFactory { DateCodec iso8601Seconds(); + DateCodec asctime(); } diff --git a/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java b/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java index b652596f1f..24e443d6b6 100644 --- a/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java +++ b/core/src/main/java/org/jclouds/date/internal/DateServiceDateCodecFactory.java @@ -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; + } + } diff --git a/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java b/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java index 05de972bca..242bc5e393 100644 --- a/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java +++ b/core/src/main/java/org/jclouds/io/ContentMetadataCodec.java @@ -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 httpExpiresDateDecoders; @Inject public DefaultContentMetadataCodec(DateCodecFactory dateCodecs) { httpExpiresDateCodec = dateCodecs.rfc1123(); + httpExpiresDateDecoders = ImmutableList.of(dateCodecs.rfc1123(), dateCodecs.asctime()); } - + protected DateCodec getExpiresDateCodec() { return httpExpiresDateCodec; } - + + protected List getExpiresDateDecoders() { + return httpExpiresDateDecoders; + } + @Override public Multimap toHeaders(ContentMetadata md) { Builder builder = ImmutableMultimap.builder(); @@ -134,14 +142,31 @@ public interface ContentMetadataCodec { } } + /** + * Parses the date from the given Expires header. + *

+ * 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); } } } diff --git a/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java b/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java index 697925ab04..4a789f2f49 100644 --- a/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java +++ b/core/src/test/java/org/jclouds/date/internal/DateServiceDateCodecFactoryTest.java @@ -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) { + } + } } diff --git a/core/src/test/java/org/jclouds/io/DefaultContentMetadataCodecTest.java b/core/src/test/java/org/jclouds/io/DefaultContentMetadataCodecTest.java new file mode 100644 index 0000000000..3b18a62957 --- /dev/null +++ b/core/src/test/java/org/jclouds/io/DefaultContentMetadataCodecTest.java @@ -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)); + } +}