/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.net;
import com.twelvemonkeys.lang.
DateUtil;
import com.twelvemonkeys.lang.
StringUtil;
import java.text.
DateFormat;
import java.text.
ParseException;
import java.text.
SimpleDateFormat;
import java.util.
Date;
import java.util.
Locale;
import java.util.
TimeZone;
/**
* HTTPUtil
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HTTPUtil.java,v 1.0 08.09.13 13:57 haraldk Exp$
*/
public class
HTTPUtil {
/**
* RFC 1123 date format, as recommended by RFC 2616 (HTTP/1.1), sec 3.3
* NOTE: All date formats are private, to ensure synchronized access.
*/
private static final
SimpleDateFormat HTTP_RFC1123_FORMAT = new
SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z",
Locale.
US);
static {
HTTP_RFC1123_FORMAT.
setTimeZone(
TimeZone.
getTimeZone("GMT"));
}
/**
* RFC 850 date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
*/
private static final
SimpleDateFormat HTTP_RFC850_FORMAT = new
SimpleDateFormat("EEE, dd-MMM-yy HH:mm:ss z",
Locale.
US);
/**
* ANSI C asctime() date format, (almost) as described in RFC 2616 (HTTP/1.1), sec 3.3.
* USE FOR PARSING ONLY (format is not 100% correct, to be more robust).
*/
private static final
SimpleDateFormat HTTP_ASCTIME_FORMAT = new
SimpleDateFormat("EEE MMM d HH:mm:ss yy",
Locale.
US);
private static long
sNext50YearWindowChange =
DateUtil.
currentTimeDay();
static {
HTTP_RFC850_FORMAT.
setTimeZone(
TimeZone.
getTimeZone("GMT"));
HTTP_ASCTIME_FORMAT.
setTimeZone(
TimeZone.
getTimeZone("GMT"));
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3:
// - HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
// which appears to be more than 50 years in the future is in fact
// in the past (this helps solve the "year 2000" problem).
update50YearWindowIfNeeded();
}
private static void
update50YearWindowIfNeeded() {
// Avoid class synchronization
long
next =
sNext50YearWindowChange;
if (
next <
System.
currentTimeMillis()) {
// Next check in one day
next +=
DateUtil.
DAY;
sNext50YearWindowChange =
next;
Date startDate = new
Date(
next - (50l *
DateUtil.
CALENDAR_YEAR));
//System.out.println("next test: " + new Date(next) + ", 50 year start: " + startDate);
synchronized (
HTTP_RFC850_FORMAT) {
HTTP_RFC850_FORMAT.
set2DigitYearStart(
startDate);
}
synchronized (
HTTP_ASCTIME_FORMAT) {
HTTP_ASCTIME_FORMAT.
set2DigitYearStart(
startDate);
}
}
}
private
HTTPUtil() {}
/**
* Formats the time to a HTTP date, using the RFC 1123 format, as described
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
*
* @param pTime the time
* @return a {@code String} representation of the time
*/
public static
String formatHTTPDate(long
pTime) {
return
formatHTTPDate(new
Date(
pTime));
}
/**
* Formats the time to a HTTP date, using the RFC 1123 format, as described
* in <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>.
*
* @param pTime the time
* @return a {@code String} representation of the time
*/
public static
String formatHTTPDate(
Date pTime) {
synchronized (
HTTP_RFC1123_FORMAT) {
return
HTTP_RFC1123_FORMAT.
format(
pTime);
}
}
/**
* Parses a HTTP date string into a {@code long} representing milliseconds
* since January 1, 1970 GMT.
* <p>
* Use this method with headers that contain dates, such as
* {@code If-Modified-Since} or {@code Last-Modified}.
* <p>
* The date string may be in either RFC 1123, RFC 850 or ANSI C asctime()
* format, as described in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3"
* >RFC 2616 (HTTP/1.1), sec. 3.3</a>
*
* @param pDate the date to parse
*
* @return a {@code long} value representing the date, expressed as the
* number of milliseconds since January 1, 1970 GMT,
* @throws NumberFormatException if the date parameter is not parseable.
* @throws IllegalArgumentException if the date paramter is {@code null}
*/
public static long
parseHTTPDate(
String pDate) throws
NumberFormatException {
return
parseHTTPDateImpl(
pDate).
getTime();
}
/**
* ParseHTTPDate implementation
*
* @param pDate the date string to parse
*
* @return a {@code Date}
* @throws NumberFormatException if the date parameter is not parseable.
* @throws IllegalArgumentException if the date paramter is {@code null}
*/
private static
Date parseHTTPDateImpl(final
String pDate) throws
NumberFormatException {
if (
pDate == null) {
throw new
IllegalArgumentException("date == null");
}
if (
StringUtil.
isEmpty(
pDate)) {
throw new
NumberFormatException("Invalid HTTP date: \"" +
pDate + "\"");
}
DateFormat format;
if (
pDate.
indexOf('-') >= 0) {
format =
HTTP_RFC850_FORMAT;
update50YearWindowIfNeeded();
}
else if (
pDate.
indexOf(',') < 0) {
format =
HTTP_ASCTIME_FORMAT;
update50YearWindowIfNeeded();
}
else {
format =
HTTP_RFC1123_FORMAT;
// NOTE: RFC1123 always uses 4-digit years
}
Date date;
try {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (
format) {
date =
format.
parse(
pDate);
}
}
catch (
ParseException e) {
NumberFormatException nfe = new
NumberFormatException("Invalid HTTP date: \"" +
pDate + "\"");
nfe.
initCause(
e);
throw
nfe;
}
if (
date == null) {
throw new
NumberFormatException("Invalid HTTP date: \"" +
pDate + "\"");
}
return
date;
}
}