From 9018bf919af6ca15c941617321c06f92a4a24b90 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Mon, 11 Mar 2019 17:35:06 -0700 Subject: [PATCH 1/4] Copied DateUtils source from Apache HC --- .../google/firebase/internal/DateUtils.java | 182 ++++++++++++++++++ .../RetryUnsuccessfulResponseHandler.java | 1 - 2 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/google/firebase/internal/DateUtils.java 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..596642188 --- /dev/null +++ b/src/main/java/com/google/firebase/internal/DateUtils.java @@ -0,0 +1,182 @@ +/* + * 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.lang.ref.SoftReference; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.http.util.Args; + +/** + * 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. + * + *

This class was copied 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. + */ +final class DateUtils { + + /** + * Date format pattern used to parse HTTP date headers in RFC 1123 format. + */ + public 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. + */ + public 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. + */ + public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; + + private static final String[] DEFAULT_PATTERNS = new String[] { + PATTERN_RFC1123, + PATTERN_RFC1036, + PATTERN_ASCTIME + }; + + private static final Date DEFAULT_TWO_DIGIT_YEAR_START; + + public 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); + DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); + } + + /** + * Parses a date value. The formats used for parsing the date value are retrieved from + * the default http params. + * + * @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) { + return parseDate(dateValue, null, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * @param startDate During parsing, two digit years will be placed in the range + * {@code startDate} to {@code startDate + 100 years}. This value may + * be {@code null}. When {@code null} is given as a parameter, year + * {@code 2000} will be used. + * + * @return the parsed date or null if input could not be parsed + */ + public static Date parseDate( + final String dateValue, + final String[] dateFormats, + final Date startDate) { + Args.notNull(dateValue, "Date value"); + final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS; + final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START; + String v = 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 String dateFormat : localDateFormats) { + final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); + dateParser.set2DigitYearStart(localStartDate); + final ParsePosition pos = new ParsePosition(0); + final Date result = dateParser.parse(v, pos); + if (pos.getIndex() != 0) { + return result; + } + } + return null; + } + + /** + * Clears thread-local variable containing {@link java.text.DateFormat} cache. + */ + public static void clearThreadLocal() { + DateFormatHolder.clearThreadLocal(); + } + + /** This class should not be instantiated. */ + private DateUtils() { + } + + /** + * A factory for {@link SimpleDateFormat}s. The instances are stored in a + * threadlocal way because SimpleDateFormat is not threadsafe as noted in + * {@link SimpleDateFormat its javadoc}. + * + */ + static final class DateFormatHolder { + + private static final ThreadLocal>> + THREADLOCAL_FORMATS = new ThreadLocal<>(); + + /** + * creates a {@link SimpleDateFormat} for the requested format string. + * + * @param pattern a non-{@code null} format String according to + * {@link SimpleDateFormat}. The format is not checked against + * {@code null} since all paths go through {@link DateUtils}. + * @return the requested format. This simple dateformat should not be used + * to {@link SimpleDateFormat#applyPattern(String) apply} to a + * different pattern. + */ + public static SimpleDateFormat formatFor(final String pattern) { + final SoftReference> ref = THREADLOCAL_FORMATS.get(); + Map formats = ref == null ? null : ref.get(); + if (formats == null) { + formats = new HashMap<>(); + THREADLOCAL_FORMATS.set(new SoftReference<>(formats)); + } + + SimpleDateFormat format = formats.get(pattern); + if (format == null) { + format = new SimpleDateFormat(pattern, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + formats.put(pattern, format); + } + + return format; + } + + public static void clearThreadLocal() { + THREADLOCAL_FORMATS.remove(); + } + } +} 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 From f373d5dcd9d95d2e44db636294fbf0aafbd02f33 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 12 Mar 2019 13:34:41 -0700 Subject: [PATCH 2/4] Updated reference link --- src/main/java/com/google/firebase/internal/DateUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/firebase/internal/DateUtils.java b/src/main/java/com/google/firebase/internal/DateUtils.java index 596642188..0aed7edb2 100644 --- a/src/main/java/com/google/firebase/internal/DateUtils.java +++ b/src/main/java/com/google/firebase/internal/DateUtils.java @@ -34,7 +34,7 @@ * 3.3.1 as well as some other common non-standard formats. * *

This class was copied 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. From 1f5223e3fbb4b2eb763277c712e2d23a86fae840 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Tue, 12 Mar 2019 14:18:25 -0700 Subject: [PATCH 3/4] Used locks instead of thread locals; Added tests --- .../google/firebase/internal/DateUtils.java | 130 ++++-------------- .../firebase/internal/DateUtilsTest.java | 80 +++++++++++ 2 files changed, 109 insertions(+), 101 deletions(-) create mode 100644 src/test/java/com/google/firebase/internal/DateUtilsTest.java diff --git a/src/main/java/com/google/firebase/internal/DateUtils.java b/src/main/java/com/google/firebase/internal/DateUtils.java index 0aed7edb2..b63876322 100644 --- a/src/main/java/com/google/firebase/internal/DateUtils.java +++ b/src/main/java/com/google/firebase/internal/DateUtils.java @@ -16,167 +16,95 @@ package com.google.firebase.internal; -import java.lang.ref.SoftReference; +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.HashMap; -import java.util.Locale; -import java.util.Map; import java.util.TimeZone; -import org.apache.http.util.Args; - /** * 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. * - *

This class was copied from the + *

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. + * 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. */ - public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; + 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. */ - public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; + 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. */ - public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; + static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; - private static final String[] DEFAULT_PATTERNS = new String[] { - PATTERN_RFC1123, - PATTERN_RFC1036, - PATTERN_ASCTIME + private static final SimpleDateFormat[] DEFAULT_PATTERNS = new SimpleDateFormat[] { + new SimpleDateFormat(PATTERN_RFC1123), + new SimpleDateFormat(PATTERN_RFC1036), + new SimpleDateFormat(PATTERN_ASCTIME) }; - private static final Date DEFAULT_TWO_DIGIT_YEAR_START; - - public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + 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); - DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); - } + final Date defaultTwoDigitYearStart = calendar.getTime(); - /** - * Parses a date value. The formats used for parsing the date value are retrieved from - * the default http params. - * - * @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) { - return parseDate(dateValue, null, null); + 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 - * @param dateFormats the date formats to use - * @param startDate During parsing, two digit years will be placed in the range - * {@code startDate} to {@code startDate + 100 years}. This value may - * be {@code null}. When {@code null} is given as a parameter, year - * {@code 2000} will be used. - * * @return the parsed date or null if input could not be parsed */ - public static Date parseDate( - final String dateValue, - final String[] dateFormats, - final Date startDate) { - Args.notNull(dateValue, "Date value"); - final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS; - final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START; - String v = dateValue; + 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 String dateFormat : localDateFormats) { - final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); - dateParser.set2DigitYearStart(localStartDate); + for (final SimpleDateFormat datePattern : DEFAULT_PATTERNS) { final ParsePosition pos = new ParsePosition(0); - final Date result = dateParser.parse(v, pos); - if (pos.getIndex() != 0) { - return result; + synchronized (datePattern) { + final Date result = datePattern.parse(v, pos); + if (pos.getIndex() != 0) { + return result; + } } } return null; } - /** - * Clears thread-local variable containing {@link java.text.DateFormat} cache. - */ - public static void clearThreadLocal() { - DateFormatHolder.clearThreadLocal(); - } - /** This class should not be instantiated. */ private DateUtils() { } - - /** - * A factory for {@link SimpleDateFormat}s. The instances are stored in a - * threadlocal way because SimpleDateFormat is not threadsafe as noted in - * {@link SimpleDateFormat its javadoc}. - * - */ - static final class DateFormatHolder { - - private static final ThreadLocal>> - THREADLOCAL_FORMATS = new ThreadLocal<>(); - - /** - * creates a {@link SimpleDateFormat} for the requested format string. - * - * @param pattern a non-{@code null} format String according to - * {@link SimpleDateFormat}. The format is not checked against - * {@code null} since all paths go through {@link DateUtils}. - * @return the requested format. This simple dateformat should not be used - * to {@link SimpleDateFormat#applyPattern(String) apply} to a - * different pattern. - */ - public static SimpleDateFormat formatFor(final String pattern) { - final SoftReference> ref = THREADLOCAL_FORMATS.get(); - Map formats = ref == null ? null : ref.get(); - if (formats == null) { - formats = new HashMap<>(); - THREADLOCAL_FORMATS.set(new SoftReference<>(formats)); - } - - SimpleDateFormat format = formats.get(pattern); - if (format == null) { - format = new SimpleDateFormat(pattern, Locale.US); - format.setTimeZone(TimeZone.getTimeZone("GMT")); - formats.put(pattern, format); - } - - return format; - } - - public static void clearThreadLocal() { - THREADLOCAL_FORMATS.remove(); - } - } } 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); + } +} From 7aea234b6a95aa32c8054349e5e97cfbd4efe4f0 Mon Sep 17 00:00:00 2001 From: hiranya911 Date: Wed, 13 Mar 2019 10:51:05 -0700 Subject: [PATCH 4/4] Added a NOTICE file for third-party code --- NOTICE.txt | 5 +++++ src/main/java/com/google/firebase/internal/DateUtils.java | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 NOTICE.txt 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 index b63876322..6c4eeb7b0 100644 --- a/src/main/java/com/google/firebase/internal/DateUtils.java +++ b/src/main/java/com/google/firebase/internal/DateUtils.java @@ -1,6 +1,4 @@ /* - * 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