001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027package org.apache.http.impl.cookie; 028 029import java.util.BitSet; 030import java.util.Calendar; 031import java.util.Locale; 032import java.util.Map; 033import java.util.TimeZone; 034import java.util.concurrent.ConcurrentHashMap; 035import java.util.regex.Matcher; 036import java.util.regex.Pattern; 037 038import org.apache.http.annotation.Contract; 039import org.apache.http.annotation.ThreadingBehavior; 040import org.apache.http.cookie.ClientCookie; 041import org.apache.http.cookie.CommonCookieAttributeHandler; 042import org.apache.http.cookie.MalformedCookieException; 043import org.apache.http.cookie.SetCookie; 044import org.apache.http.message.ParserCursor; 045import org.apache.http.util.Args; 046 047/** 048 * 049 * @since 4.4 050 */ 051@Contract(threading = ThreadingBehavior.IMMUTABLE) 052public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements CommonCookieAttributeHandler { 053 054 static final TimeZone UTC = TimeZone.getTimeZone("UTC"); 055 056 private static final BitSet DELIMS; 057 static { 058 final BitSet bitSet = new BitSet(); 059 bitSet.set(0x9); 060 for (int b = 0x20; b <= 0x2f; b++) { 061 bitSet.set(b); 062 } 063 for (int b = 0x3b; b <= 0x40; b++) { 064 bitSet.set(b); 065 } 066 for (int b = 0x5b; b <= 0x60; b++) { 067 bitSet.set(b); 068 } 069 for (int b = 0x7b; b <= 0x7e; b++) { 070 bitSet.set(b); 071 } 072 DELIMS = bitSet; 073 } 074 private static final Map<String, Integer> MONTHS; 075 static { 076 final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>(12); 077 map.put("jan", Calendar.JANUARY); 078 map.put("feb", Calendar.FEBRUARY); 079 map.put("mar", Calendar.MARCH); 080 map.put("apr", Calendar.APRIL); 081 map.put("may", Calendar.MAY); 082 map.put("jun", Calendar.JUNE); 083 map.put("jul", Calendar.JULY); 084 map.put("aug", Calendar.AUGUST); 085 map.put("sep", Calendar.SEPTEMBER); 086 map.put("oct", Calendar.OCTOBER); 087 map.put("nov", Calendar.NOVEMBER); 088 map.put("dec", Calendar.DECEMBER); 089 MONTHS = map; 090 } 091 092 private final static Pattern TIME_PATTERN = Pattern.compile( 093 "^([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})([^0-9].*)?$"); 094 private final static Pattern DAY_OF_MONTH_PATTERN = Pattern.compile( 095 "^([0-9]{1,2})([^0-9].*)?$"); 096 private final static Pattern MONTH_PATTERN = Pattern.compile( 097 "^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)(.*)?$", Pattern.CASE_INSENSITIVE); 098 private final static Pattern YEAR_PATTERN = Pattern.compile( 099 "^([0-9]{2,4})([^0-9].*)?$"); 100 101 public LaxExpiresHandler() { 102 super(); 103 } 104 105 @Override 106 public void parse(final SetCookie cookie, final String value) throws MalformedCookieException { 107 Args.notNull(cookie, "Cookie"); 108 final ParserCursor cursor = new ParserCursor(0, value.length()); 109 final StringBuilder content = new StringBuilder(); 110 111 int second = 0, minute = 0, hour = 0, day = 0, month = 0, year = 0; 112 boolean foundTime = false, foundDayOfMonth = false, foundMonth = false, foundYear = false; 113 try { 114 while (!cursor.atEnd()) { 115 skipDelims(value, cursor); 116 content.setLength(0); 117 copyContent(value, cursor, content); 118 119 if (content.length() == 0) { 120 break; 121 } 122 if (!foundTime) { 123 final Matcher matcher = TIME_PATTERN.matcher(content); 124 if (matcher.matches()) { 125 foundTime = true; 126 hour = Integer.parseInt(matcher.group(1)); 127 minute = Integer.parseInt(matcher.group(2)); 128 second =Integer.parseInt(matcher.group(3)); 129 continue; 130 } 131 } 132 if (!foundDayOfMonth) { 133 final Matcher matcher = DAY_OF_MONTH_PATTERN.matcher(content); 134 if (matcher.matches()) { 135 foundDayOfMonth = true; 136 day = Integer.parseInt(matcher.group(1)); 137 continue; 138 } 139 } 140 if (!foundMonth) { 141 final Matcher matcher = MONTH_PATTERN.matcher(content); 142 if (matcher.matches()) { 143 foundMonth = true; 144 month = MONTHS.get(matcher.group(1).toLowerCase(Locale.ROOT)); 145 continue; 146 } 147 } 148 if (!foundYear) { 149 final Matcher matcher = YEAR_PATTERN.matcher(content); 150 if (matcher.matches()) { 151 foundYear = true; 152 year = Integer.parseInt(matcher.group(1)); 153 continue; 154 } 155 } 156 } 157 } catch (final NumberFormatException ignore) { 158 throw new MalformedCookieException("Invalid 'expires' attribute: " + value); 159 } 160 if (!foundTime || !foundDayOfMonth || !foundMonth || !foundYear) { 161 throw new MalformedCookieException("Invalid 'expires' attribute: " + value); 162 } 163 if (year >= 70 && year <= 99) { 164 year = 1900 + year; 165 } 166 if (year >= 0 && year <= 69) { 167 year = 2000 + year; 168 } 169 if (day < 1 || day > 31 || year < 1601 || hour > 23 || minute > 59 || second > 59) { 170 throw new MalformedCookieException("Invalid 'expires' attribute: " + value); 171 } 172 173 final Calendar c = Calendar.getInstance(); 174 c.setTimeZone(UTC); 175 c.setTimeInMillis(0L); 176 c.set(Calendar.SECOND, second); 177 c.set(Calendar.MINUTE, minute); 178 c.set(Calendar.HOUR_OF_DAY, hour); 179 c.set(Calendar.DAY_OF_MONTH, day); 180 c.set(Calendar.MONTH, month); 181 c.set(Calendar.YEAR, year); 182 cookie.setExpiryDate(c.getTime()); 183 } 184 185 private void skipDelims(final CharSequence buf, final ParserCursor cursor) { 186 int pos = cursor.getPos(); 187 final int indexFrom = cursor.getPos(); 188 final int indexTo = cursor.getUpperBound(); 189 for (int i = indexFrom; i < indexTo; i++) { 190 final char current = buf.charAt(i); 191 if (DELIMS.get(current)) { 192 pos++; 193 } else { 194 break; 195 } 196 } 197 cursor.updatePos(pos); 198 } 199 200 private void copyContent(final CharSequence buf, final ParserCursor cursor, final StringBuilder dst) { 201 int pos = cursor.getPos(); 202 final int indexFrom = cursor.getPos(); 203 final int indexTo = cursor.getUpperBound(); 204 for (int i = indexFrom; i < indexTo; i++) { 205 final char current = buf.charAt(i); 206 if (DELIMS.get(current)) { 207 break; 208 } else { 209 pos++; 210 dst.append(current); 211 } 212 } 213 cursor.updatePos(pos); 214 } 215 216 @Override 217 public String getAttributeName() { 218 return ClientCookie.EXPIRES_ATTR; 219 } 220 221}