diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 000000000..b6c5d02b2 --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,5 @@ +Firebase Admin Java SDK +Copyright 2019 Google Inc. + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/src/main/java/com/google/firebase/internal/DateUtils.java b/src/main/java/com/google/firebase/internal/DateUtils.java new file mode 100644 index 000000000..6c4eeb7b0 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/DateUtils.java @@ -0,0 +1,108 @@ +/* + * Licensed 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 com.google.firebase.internal; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * A utility class for parsing and formatting HTTP dates as used in cookies and + * other headers. This class handles dates as defined by RFC 2616 section + * 3.3.1 as well as some other common non-standard formats. + * + *
Most of this class was borrowed from the + * + * Apache HTTP client in order to avoid a direct dependency on it. We currently + * have a transitive dependency on this library (via Google API client), but the API + * client team is working towards removing it, so we won't have it in the classpath for long. + * + *
The original implementation of this class uses + * thread locals to cache the {@code SimpleDateFormat} instances. Instead, this implementation + * uses static constants and explicit locking to ensure thread safety. This is probably slower, + * but also simpler and avoids memory leaks that may result from unreleased thread locals. + */ +final class DateUtils { + + /** + * Date format pattern used to parse HTTP date headers in RFC 1123 format. + */ + static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in RFC 1036 format. + */ + static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in ANSI C + * {@code asctime()} format. + */ + static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; + + private static final SimpleDateFormat[] DEFAULT_PATTERNS = new SimpleDateFormat[] { + new SimpleDateFormat(PATTERN_RFC1123), + new SimpleDateFormat(PATTERN_RFC1036), + new SimpleDateFormat(PATTERN_ASCTIME) + }; + + static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + static { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(GMT); + calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + final Date defaultTwoDigitYearStart = calendar.getTime(); + + for (final SimpleDateFormat datePattern : DEFAULT_PATTERNS) { + datePattern.set2DigitYearStart(defaultTwoDigitYearStart); + } + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @return the parsed date or null if input could not be parsed + */ + public static Date parseDate(final String dateValue) { + String v = checkNotNull(dateValue); + // trim single quotes around date if present + // see issue #5279 + if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) { + v = v.substring(1, v.length() - 1); + } + + for (final SimpleDateFormat datePattern : DEFAULT_PATTERNS) { + final ParsePosition pos = new ParsePosition(0); + synchronized (datePattern) { + final Date result = datePattern.parse(v, pos); + if (pos.getIndex() != 0) { + return result; + } + } + } + return null; + } + + /** This class should not be instantiated. */ + private DateUtils() { + } +} diff --git a/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java b/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java index ba06e59a1..dd00d2bfb 100644 --- a/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java +++ b/src/main/java/com/google/firebase/internal/RetryUnsuccessfulResponseHandler.java @@ -28,7 +28,6 @@ import com.google.common.base.Strings; import java.io.IOException; import java.util.Date; -import org.apache.http.client.utils.DateUtils; /** * An {@code HttpUnsuccessfulResponseHandler} that retries failing requests after an interval. The diff --git a/src/test/java/com/google/firebase/internal/DateUtilsTest.java b/src/test/java/com/google/firebase/internal/DateUtilsTest.java new file mode 100644 index 000000000..7c2dfd089 --- /dev/null +++ b/src/test/java/com/google/firebase/internal/DateUtilsTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Google Inc. + * + * Licensed 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 com.google.firebase.internal; + +import java.util.Calendar; +import java.util.Date; +import org.junit.Assert; +import org.junit.Test; + +/** + * Unit tests for the {@link DateUtils}. Adapted from the tests available in the + * + * Apache HTTP client library. + */ +public class DateUtilsTest { + + @Test + public void testBasicDateParse() { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(DateUtils.GMT); + calendar.set(2005, Calendar.OCTOBER, 14, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + final Date date1 = calendar.getTime(); + + Date date2 = DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT"); + Assert.assertEquals(date1, date2); + date2 = DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT"); + Assert.assertEquals(date1, date2); + date2 = DateUtils.parseDate("Fri, 14 Oct 2005 00:00:00 GMT"); + Assert.assertEquals(date1, date2); + } + + @Test + public void testInvalidInput() { + try { + DateUtils.parseDate(null); + Assert.fail("NullPointerException should have been thrown"); + } catch (NullPointerException ex) { + // expected + } + } + + @Test + public void testTwoDigitYearDateParse() { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(DateUtils.GMT); + calendar.set(2005, Calendar.OCTOBER, 14, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + Date date1 = calendar.getTime(); + + Date date2 = DateUtils.parseDate("Friday, 14-Oct-05 00:00:00 GMT"); + Assert.assertEquals(date1, date2); + } + + @Test + public void testParseQuotedDate() { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(DateUtils.GMT); + calendar.set(2005, Calendar.OCTOBER, 14, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + final Date date1 = calendar.getTime(); + + final Date date2 = DateUtils.parseDate("'Fri, 14 Oct 2005 00:00:00 GMT'"); + Assert.assertEquals(date1, date2); + } +}