/* * 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 "TwelveMonkeys" 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 OWNER 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; } }