1 /* 2 * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.webkit.network; 27 28 import java.text.ParseException; 29 import java.util.Calendar; 30 import java.util.Collections; 31 import java.util.Date; 32 import java.util.HashMap; 33 import java.util.Locale; 34 import java.util.Map; 35 import java.util.TimeZone; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 import java.util.regex.Matcher; 39 import java.util.regex.Pattern; 40 41 /** 42 * An RFC 6265-compliant date parser. 43 */ 44 final class DateParser { 45 46 private static final Logger logger = 47 Logger.getLogger(DateParser.class.getName()); 48 49 private static final Pattern DELIMITER_PATTERN = Pattern.compile( 50 "[\\x09\\x20-\\x2F\\x3B-\\x40\\x5B-\\x60\\x7B-\\x7E]+"); 51 private static final Pattern TIME_PATTERN = Pattern.compile( 52 "(\\d{1,2}):(\\d{1,2}):(\\d{1,2})(?:[^\\d].*)*"); 53 private static final Pattern DAY_OF_MONTH_PATTERN = Pattern.compile( 54 "(\\d{1,2})(?:[^\\d].*)*"); 55 private static final Pattern YEAR_PATTERN = Pattern.compile( 56 "(\\d{2,4})(?:[^\\d].*)*"); 57 private static final Map<String,Integer> MONTH_MAP; 58 static { 59 Map<String,Integer> map = new HashMap<String,Integer>(12); 60 map.put("jan", 0); 61 map.put("feb", 1); 62 map.put("mar", 2); 63 map.put("apr", 3); 64 map.put("may", 4); 65 map.put("jun", 5); 66 map.put("jul", 6); 67 map.put("aug", 7); 68 map.put("sep", 8); 69 map.put("oct", 9); 70 map.put("nov", 10); 71 map.put("dec", 11); 72 MONTH_MAP = Collections.unmodifiableMap(map); 73 } 74 75 76 /** 77 * The private default constructor. Ensures non-instantiability. 78 */ 79 private DateParser() { 80 throw new AssertionError(); 81 } 82 83 84 /** 85 * Parses a given date string as required by RFC 6265. 86 * @param date the string to parse 87 * @return the difference, measured in milliseconds, between the parsed 88 * date and midnight, January 1, 1970 UTC 89 * @throws ParseException if {@code date} cannot be parsed 90 */ 91 static long parse(String date) throws ParseException { 92 logger.log(Level.FINEST, "date: [{0}]", date); 93 94 Time time = null; 95 Integer dayOfMonth = null; 96 Integer month = null; 97 Integer year = null; 98 String[] tokens = DELIMITER_PATTERN.split(date, 0); 99 for (String token : tokens) { 100 if (token.length() == 0) { 101 continue; 102 } 103 104 Time timeTmp; 105 if (time == null && (timeTmp = parseTime(token)) != null) { 106 time = timeTmp; 107 continue; 108 } 109 110 Integer dayOfMonthTmp; 111 if (dayOfMonth == null 112 && (dayOfMonthTmp = parseDayOfMonth(token)) != null) 113 { 114 dayOfMonth = dayOfMonthTmp; 115 continue; 116 } 117 118 Integer monthTmp; 119 if (month == null && (monthTmp = parseMonth(token)) != null) { 120 month = monthTmp; 121 continue; 122 } 123 124 Integer yearTmp; 125 if (year == null && (yearTmp = parseYear(token)) != null) { 126 year = yearTmp; 127 continue; 128 } 129 } 130 131 if (year != null) { 132 if (year >= 70 && year <= 99) { 133 year += 1900; 134 } else if (year >= 0 && year <= 69) { 135 year += 2000; 136 } 137 } 138 139 if (time == null || dayOfMonth == null || month == null || year == null 140 || dayOfMonth < 1 || dayOfMonth > 31 141 || year < 1601 142 || time.hour > 23 143 || time.minute > 59 144 || time.second > 59) 145 { 146 throw new ParseException("Error parsing date", 0); 147 } 148 149 Calendar calendar = Calendar.getInstance( 150 TimeZone.getTimeZone("UTC"), Locale.US); 151 calendar.setLenient(false); 152 calendar.clear(); 153 calendar.set(year, month, dayOfMonth, 154 time.hour, time.minute, time.second); 155 156 try { 157 long result = calendar.getTimeInMillis(); 158 if (logger.isLoggable(Level.FINEST)) { 159 logger.log(Level.FINEST, "result: [{0}]", 160 new Date(result).toString()); 161 } 162 return result; 163 } catch (Exception ex) { 164 ParseException pe = new ParseException("Error parsing date", 0); 165 pe.initCause(ex); 166 throw pe; 167 } 168 } 169 170 /** 171 * Parses a token as a time string. 172 */ 173 private static Time parseTime(String token) { 174 Matcher matcher = TIME_PATTERN.matcher(token); 175 if (matcher.matches()) { 176 return new Time( 177 Integer.parseInt(matcher.group(1)), 178 Integer.parseInt(matcher.group(2)), 179 Integer.parseInt(matcher.group(3))); 180 } else { 181 return null; 182 } 183 } 184 185 /** 186 * Container for parsed time. 187 */ 188 private static final class Time { 189 private final int hour; 190 private final int minute; 191 private final int second; 192 193 private Time(int hour, int minute, int second) { 194 this.hour = hour; 195 this.minute = minute; 196 this.second = second; 197 } 198 } 199 200 /** 201 * Parses a token as a day of month. 202 */ 203 private static Integer parseDayOfMonth(String token) { 204 Matcher matcher = DAY_OF_MONTH_PATTERN.matcher(token); 205 if (matcher.matches()) { 206 return Integer.parseInt(matcher.group(1)); 207 } else { 208 return null; 209 } 210 } 211 212 /** 213 * Parses a token as a month. 214 */ 215 private static Integer parseMonth(String token) { 216 if (token.length() >= 3) { 217 return MONTH_MAP.get(token.substring(0, 3).toLowerCase()); 218 } else { 219 return null; 220 } 221 } 222 223 /** 224 * Parses a token as a year. 225 */ 226 private static Integer parseYear(String token) { 227 Matcher matcher = YEAR_PATTERN.matcher(token); 228 if (matcher.matches()) { 229 return Integer.parseInt(matcher.group(1)); 230 } else { 231 return null; 232 } 233 } 234 }