1 /* 2 * Copyright (c) 2011, 2018, 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 com.sun.javafx.logging.PlatformLogger; 29 import com.sun.javafx.logging.PlatformLogger.Level; 30 31 import java.text.ParseException; 32 import java.util.Calendar; 33 import java.util.Collections; 34 import java.util.Date; 35 import java.util.HashMap; 36 import java.util.Locale; 37 import java.util.Map; 38 import java.util.TimeZone; 39 import java.util.regex.Matcher; 40 import java.util.regex.Pattern; 41 42 /** 43 * An RFC 6265-compliant date parser. 44 */ 45 final class DateParser { 46 47 private static final PlatformLogger logger = 48 PlatformLogger.getLogger(DateParser.class.getName()); 49 50 private static final Pattern DELIMITER_PATTERN = Pattern.compile( 51 "[\\x09\\x20-\\x2F\\x3B-\\x40\\x5B-\\x60\\x7B-\\x7E]+"); 52 private static final Pattern TIME_PATTERN = Pattern.compile( 53 "(\\d{1,2}):(\\d{1,2}):(\\d{1,2})(?:[^\\d].*)*"); 54 private static final Pattern DAY_OF_MONTH_PATTERN = Pattern.compile( 55 "(\\d{1,2})(?:[^\\d].*)*"); 56 private static final Pattern YEAR_PATTERN = Pattern.compile( 57 "(\\d{2,4})(?:[^\\d].*)*"); 58 private static final Map<String,Integer> MONTH_MAP; 59 static { 60 Map<String,Integer> map = new HashMap<String,Integer>(12); 61 map.put("jan", 0); 62 map.put("feb", 1); 63 map.put("mar", 2); 64 map.put("apr", 3); 65 map.put("may", 4); 66 map.put("jun", 5); 67 map.put("jul", 6); 68 map.put("aug", 7); 69 map.put("sep", 8); 70 map.put("oct", 9); 71 map.put("nov", 10); 72 map.put("dec", 11); 73 MONTH_MAP = Collections.unmodifiableMap(map); 74 } 75 76 77 /** 78 * The private default constructor. Ensures non-instantiability. 79 */ 80 private DateParser() { 81 throw new AssertionError(); 82 } 83 84 85 /** 86 * Parses a given date string as required by RFC 6265. 87 * @param date the string to parse 88 * @return the difference, measured in milliseconds, between the parsed 89 * date and midnight, January 1, 1970 UTC 90 * @throws ParseException if {@code date} cannot be parsed 91 */ 92 static long parse(String date) throws ParseException { 93 logger.finest("date: [{0}]", date); 94 95 Time time = null; 96 Integer dayOfMonth = null; 97 Integer month = null; 98 Integer year = null; 99 String[] tokens = DELIMITER_PATTERN.split(date, 0); 100 for (String token : tokens) { 101 if (token.length() == 0) { 102 continue; 103 } 104 105 Time timeTmp; 106 if (time == null && (timeTmp = parseTime(token)) != null) { 107 time = timeTmp; 108 continue; 109 } 110 111 Integer dayOfMonthTmp; 112 if (dayOfMonth == null 113 && (dayOfMonthTmp = parseDayOfMonth(token)) != null) 114 { 115 dayOfMonth = dayOfMonthTmp; 116 continue; 117 } 118 119 Integer monthTmp; 120 if (month == null && (monthTmp = parseMonth(token)) != null) { 121 month = monthTmp; 122 continue; 123 } 124 125 Integer yearTmp; 126 if (year == null && (yearTmp = parseYear(token)) != null) { 127 year = yearTmp; 128 continue; 129 } 130 } 131 132 if (year != null) { 133 if (year >= 70 && year <= 99) { 134 year += 1900; 135 } else if (year >= 0 && year <= 69) { 136 year += 2000; 137 } 138 } 139 140 if (time == null || dayOfMonth == null || month == null || year == null 141 || dayOfMonth < 1 || dayOfMonth > 31 142 || year < 1601 143 || time.hour > 23 144 || time.minute > 59 145 || time.second > 59) 146 { 147 throw new ParseException("Error parsing date", 0); 148 } 149 150 Calendar calendar = Calendar.getInstance( 151 TimeZone.getTimeZone("UTC"), Locale.US); 152 calendar.setLenient(false); 153 calendar.clear(); 154 calendar.set(year, month, dayOfMonth, 155 time.hour, time.minute, time.second); 156 157 try { 158 long result = calendar.getTimeInMillis(); 159 if (logger.isLoggable(Level.FINEST)) { 160 logger.finest("result: [{0}]", 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 }