Skip to content
Permalink
Browse files

Removed more unsafe concurrent accesses to SimpleDateFormat instances.

SimpleDateFormat must not be used by concurrent threads without
synchronization for parsing or formating dates as it is not thread-safe
(internally holds a calendar instance that is not synchronized).

Prefer now DateTimeFormatter when possible as it is thread-safe without
concurrent access performance bottleneck (does not internally use
synchronization locks).
  • Loading branch information...
luccioman committed Jun 29, 2018
1 parent 5c6c618 commit f895745e1cd0dea933e73cd779f749a2224d583d
@@ -24,7 +24,8 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
@@ -34,6 +35,7 @@
import java.util.Map;
import java.util.Set;

import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.encoding.UTF8;
import net.yacy.cora.document.id.Punycode.PunycodeException;
@@ -350,11 +352,17 @@ public static serverObjects respond(final RequestHeader header, serverObjects po
return prop;
}

private static SimpleDateFormat dayFormatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US);
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter
.ofPattern("uuuu/MM/dd", Locale.US).withZone(ZoneId.systemDefault());

/**
* @param date a date to render as a String
* @return the date formatted using the DAY_FORMATTER pattern.
*/
private static String daydate(final Date date) {
if (date == null) {
return "";
}
return dayFormatter.format(date);
return GenericFormatter.formatSafely(date.toInstant(), DAY_FORMATTER);
}
}
@@ -1,6 +1,7 @@

import java.net.MalformedURLException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
@@ -12,6 +13,7 @@
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.protocol.Domains;
@@ -28,10 +30,18 @@

public class IndexCreateQueues_p {

private static SimpleDateFormat dayFormatter = new SimpleDateFormat("yyyy/MM/dd", Locale.US);
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter
.ofPattern("uuuu/MM/dd", Locale.US).withZone(ZoneId.systemDefault());

/**
* @param date a date to render as a String
* @return the date formatted using the DAY_FORMATTER pattern.
*/
private static String daydate(final Date date) {
if (date == null) return "";
return dayFormatter.format(date);
if (date == null) {
return "";
}
return GenericFormatter.formatSafely(date.toInstant(), DAY_FORMATTER);
}

private static final int INVALID = 0;
@@ -25,6 +25,7 @@
package net.yacy.cora.document.feed;

import java.text.ParseException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
@@ -156,7 +157,7 @@ public RSSMessage(final String title, final String description, final String lin
if (title.length() > 0) this.map.put(Token.title.name(), title);
if (description.length() > 0) this.map.put(Token.description.name(), description);
if (link.length() > 0) this.map.put(Token.link.name(), link);
this.map.put(Token.pubDate.name(), HeaderFramework.FORMAT_RFC1123.format(new Date()));
this.map.put(Token.pubDate.name(), HeaderFramework.formatNowRFC1123());
this.map.put(Token.guid.name(), artificialGuidPrefix + Integer.toHexString((title + description + link).hashCode()));
}

@@ -165,7 +166,7 @@ public RSSMessage(final String title, final String description, final MultiProto
if (title.length() > 0) this.map.put(Token.title.name(), title);
if (description.length() > 0) this.map.put(Token.description.name(), description);
this.map.put(Token.link.name(), link.toNormalform(true));
this.map.put(Token.pubDate.name(), HeaderFramework.FORMAT_RFC1123.format(new Date()));
this.map.put(Token.pubDate.name(), HeaderFramework.formatNowRFC1123());
if (guid.length() > 0) {
this.map.put(Token.guid.name(), guid);
}
@@ -261,8 +262,8 @@ public Date getPubDate() {
if (!dateString.isEmpty()) { // skip parse exception on empty string
Date date;
try {
date = HeaderFramework.FORMAT_RFC1123.parse(dateString);
} catch (final ParseException e) {
date = Date.from(ZonedDateTime.parse(dateString, HeaderFramework.RFC1123_FORMATTER).toInstant());
} catch (final RuntimeException e) {
try {
date = GenericFormatter.SHORT_SECOND_FORMATTER.parse(dateString, 0).getTime();
} catch (final ParseException e1) {
@@ -401,7 +402,7 @@ public void setLink(final String link) {

@Override
public void setPubDate(final Date pubdate) {
setValue(Token.pubDate, HeaderFramework.FORMAT_RFC1123.format(pubdate));
setValue(Token.pubDate, HeaderFramework.formatNowRFC1123());
}

@Override
@@ -26,6 +26,10 @@
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
@@ -230,20 +234,43 @@ public HeaderFramework(final Map<String, String> othermap) {
/** Date formatter/parser for standard compliant HTTP header dates (RFC 1123) */
private static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss Z"; // with numeric time zone indicator as defined in RFC5322
private static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
public static final SimpleDateFormat FORMAT_RFC1123 = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
public static final SimpleDateFormat FORMAT_RFC1036 = new SimpleDateFormat(PATTERN_RFC1036, Locale.US);
private static final SimpleDateFormat FORMAT_RFC1123 = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
private static final TimeZone TZ_GMT = TimeZone.getTimeZone("GMT");
private static final Calendar CAL_GMT = Calendar.getInstance(TZ_GMT, Locale.US);

/**
* A thread-safe date formatter using the
* {@link HeaderFramework#PATTERN_RFC1123} pattern with the US locale on the UTC
* time zone.
*/
public static final DateTimeFormatter RFC1123_FORMATTER = DateTimeFormatter
.ofPattern(PATTERN_RFC1123.replace("yyyy", "uuuu")).withLocale(Locale.US).withZone(ZoneOffset.UTC);

/**
* @return a new SimpleDateFormat instance using the
* {@link HeaderFramework#PATTERN_RFC1123} pattern with the US locale.
*/
public static SimpleDateFormat newRfc1123Format() {
return new SimpleDateFormat(HeaderFramework.PATTERN_RFC1123, Locale.US);
}

/**
* @return a new SimpleDateFormat instance using the
* {@link HeaderFramework#PATTERN_RFC1036} pattern with the US locale.
*/
public static SimpleDateFormat newRfc1036Format() {
return new SimpleDateFormat(HeaderFramework.PATTERN_RFC1036, Locale.US);
}

/**
* RFC 2616 requires that HTTP clients are able to parse all 3 different
* formats. All times MUST be in GMT/UTC, but ...
*/
private static final SimpleDateFormat[] FORMATS_HTTP = new SimpleDateFormat[] {
// RFC 1123/822 (Standard) "Mon, 12 Nov 2007 10:11:12 GMT"
FORMAT_RFC1123,
newRfc1123Format(),
// RFC 1036/850 (old) "Monday, 12-Nov-07 10:11:12 GMT"
FORMAT_RFC1036,
newRfc1036Format(),
// ANSI C asctime() "Mon Nov 12 10:11:12 2007"
GenericFormatter.newAnsicFormat(),
};
@@ -265,6 +292,34 @@ public static final String formatRFC1123(final Date date) {
return s;
}
}

/**
* @param epochMilli
* a time value as the number of milliseconds from Epoch
* (1970-01-01T00:00:00Z)
* @return the time formatted using the {@link HeaderFramework#PATTERN_RFC1123}
* pattern.
*/
public static final String formatRFC1123(final long epochMilli) {
try {
/* Prefer first using the thread-safe DateTimeFormatter shared instance */
return RFC1123_FORMATTER.format(Instant.ofEpochMilli(epochMilli));
} catch (final DateTimeException e) {
/*
* This should not happen, but rather than failing we prefer here to use
* formatting function using the synchronized SimpleDateFormat
*/
return formatRFC1123(new Date(epochMilli));
}
}

/**
* @return the current time formatted using the
* {@link HeaderFramework#PATTERN_RFC1123} pattern.
*/
public static final String formatNowRFC1123() {
return formatRFC1123(System.currentTimeMillis());
}

/** Initialization of static formats */
static {
@@ -277,6 +332,9 @@ public static final String formatRFC1123(final Date date) {
format.setTimeZone(TZ_GMT);
format.set2DigitYearStart(CAL_GMT.getTime());
}

FORMAT_RFC1123.setTimeZone(TZ_GMT);
FORMAT_RFC1123.set2DigitYearStart(CAL_GMT.getTime());
}

/**
@@ -1,6 +1,29 @@
// HeaderFrameworkTest.java
// Copyright 2006-2018 by theli, f1ori, Michael Peter Christen; mc@yacy.net, reger; reger18@arcor.de, luccioman; https://github.com/luccioman
//
// This is a part of YaCy, a peer-to-peer based web search engine
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package net.yacy.cora.protocol;

import java.time.Instant;
import java.util.Date;

import junit.framework.TestCase;
import org.junit.Test;

@@ -18,6 +41,22 @@ public void testParseHTTPDate() {

// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());

parsedDate = HeaderFramework.parseHTTPDate("Monday, 12-Nov-07 10:11:12 GMT");

// returned date must not be null
assertNotNull(parsedDate);

// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());

parsedDate = HeaderFramework.parseHTTPDate("Mon Nov 12 10:11:12 2007");

// returned date must not be null
assertNotNull(parsedDate);

// Print Result
System.out.println("testParseHTTPDate: " + parsedDate.toString());
}

/**
@@ -31,4 +70,16 @@ public void testGetCharacterEncoding() {
assertEquals("utf-8", HeaderFramework.getCharacterEncoding("Text/HTML;Charset=\"utf-8\""));
assertEquals("utf-8", HeaderFramework.getCharacterEncoding("text/html; charset=\"utf-8\""));
}

/**
* Unit test for date formatting with RFC 1123 format
*/
@Test
public void testFormatRfc1123() {
assertEquals("", HeaderFramework.formatRFC1123(null));

final Instant time = Instant.parse("2018-06-29T13:04:55.00Z");
assertEquals("Fri, 29 Jun 2018 13:04:55 +0000", HeaderFramework.formatRFC1123(time.toEpochMilli()));
assertEquals("Fri, 29 Jun 2018 13:04:55 +0000", HeaderFramework.formatRFC1123(Date.from(time)));
}
}

0 comments on commit f895745

Please sign in to comment.
You can’t perform that action at this time.