View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.cookie;
28  
29  import java.util.BitSet;
30  import java.util.Calendar;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.TimeZone;
34  import java.util.concurrent.ConcurrentHashMap;
35  import java.util.regex.Matcher;
36  import java.util.regex.Pattern;
37  
38  import org.apache.http.annotation.Contract;
39  import org.apache.http.annotation.ThreadingBehavior;
40  import org.apache.http.cookie.ClientCookie;
41  import org.apache.http.cookie.CommonCookieAttributeHandler;
42  import org.apache.http.cookie.MalformedCookieException;
43  import org.apache.http.cookie.SetCookie;
44  import org.apache.http.message.ParserCursor;
45  import org.apache.http.util.Args;
46  import org.apache.http.util.TextUtils;
47  
48  /**
49   *
50   * @since 4.4
51   */
52  @Contract(threading = ThreadingBehavior.IMMUTABLE)
53  public class LaxExpiresHandler extends AbstractCookieAttributeHandler implements CommonCookieAttributeHandler {
54  
55      static final TimeZone UTC = TimeZone.getTimeZone("UTC");
56  
57      private static final BitSet DELIMS;
58      static {
59          final BitSet bitSet = new BitSet();
60          bitSet.set(0x9);
61          for (int b = 0x20; b <= 0x2f; b++) {
62              bitSet.set(b);
63          }
64          for (int b = 0x3b; b <= 0x40; b++) {
65              bitSet.set(b);
66          }
67          for (int b = 0x5b; b <= 0x60; b++) {
68              bitSet.set(b);
69          }
70          for (int b = 0x7b; b <= 0x7e; b++) {
71              bitSet.set(b);
72          }
73          DELIMS = bitSet;
74      }
75      private static final Map<String, Integer> MONTHS;
76      static {
77          final ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>(12);
78          map.put("jan", Calendar.JANUARY);
79          map.put("feb", Calendar.FEBRUARY);
80          map.put("mar", Calendar.MARCH);
81          map.put("apr", Calendar.APRIL);
82          map.put("may", Calendar.MAY);
83          map.put("jun", Calendar.JUNE);
84          map.put("jul", Calendar.JULY);
85          map.put("aug", Calendar.AUGUST);
86          map.put("sep", Calendar.SEPTEMBER);
87          map.put("oct", Calendar.OCTOBER);
88          map.put("nov", Calendar.NOVEMBER);
89          map.put("dec", Calendar.DECEMBER);
90          MONTHS = map;
91      }
92  
93      private final static Pattern TIME_PATTERN = Pattern.compile(
94              "^([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})([^0-9].*)?$");
95      private final static Pattern DAY_OF_MONTH_PATTERN = Pattern.compile(
96              "^([0-9]{1,2})([^0-9].*)?$");
97      private final static Pattern MONTH_PATTERN = Pattern.compile(
98              "^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)(.*)?$", Pattern.CASE_INSENSITIVE);
99      private final static Pattern YEAR_PATTERN = Pattern.compile(
100             "^([0-9]{2,4})([^0-9].*)?$");
101 
102     public LaxExpiresHandler() {
103         super();
104     }
105 
106     @Override
107     public void parse(final SetCookie cookie, final String value) throws MalformedCookieException {
108         Args.notNull(cookie, "Cookie");
109         if (TextUtils.isBlank(value)) {
110             return;
111         }
112         final ParserCursor cursor = new ParserCursor(0, value.length());
113         final StringBuilder content = new StringBuilder();
114 
115         int second = 0, minute = 0, hour = 0, day = 0, month = 0, year = 0;
116         boolean foundTime = false, foundDayOfMonth = false, foundMonth = false, foundYear = false;
117         try {
118             while (!cursor.atEnd()) {
119                 skipDelims(value, cursor);
120                 content.setLength(0);
121                 copyContent(value, cursor, content);
122 
123                 if (content.length() == 0) {
124                     break;
125                 }
126                 if (!foundTime) {
127                     final Matcher matcher = TIME_PATTERN.matcher(content);
128                     if (matcher.matches()) {
129                         foundTime = true;
130                         hour = Integer.parseInt(matcher.group(1));
131                         minute = Integer.parseInt(matcher.group(2));
132                         second =Integer.parseInt(matcher.group(3));
133                         continue;
134                     }
135                 }
136                 if (!foundDayOfMonth) {
137                     final Matcher matcher = DAY_OF_MONTH_PATTERN.matcher(content);
138                     if (matcher.matches()) {
139                         foundDayOfMonth = true;
140                         day = Integer.parseInt(matcher.group(1));
141                         continue;
142                     }
143                 }
144                 if (!foundMonth) {
145                     final Matcher matcher = MONTH_PATTERN.matcher(content);
146                     if (matcher.matches()) {
147                         foundMonth = true;
148                         month = MONTHS.get(matcher.group(1).toLowerCase(Locale.ROOT));
149                         continue;
150                     }
151                 }
152                 if (!foundYear) {
153                     final Matcher matcher = YEAR_PATTERN.matcher(content);
154                     if (matcher.matches()) {
155                         foundYear = true;
156                         year = Integer.parseInt(matcher.group(1));
157                         continue;
158                     }
159                 }
160             }
161         } catch (final NumberFormatException ignore) {
162             throw new MalformedCookieException("Invalid 'expires' attribute: " + value);
163         }
164         if (!foundTime || !foundDayOfMonth || !foundMonth || !foundYear) {
165             throw new MalformedCookieException("Invalid 'expires' attribute: " + value);
166         }
167         if (year >= 70 && year <= 99) {
168             year = 1900 + year;
169         }
170         if (year >= 0 && year <= 69) {
171             year = 2000 + year;
172         }
173         if (day < 1 || day > 31 || year < 1601 || hour > 23 || minute > 59 || second > 59) {
174             throw new MalformedCookieException("Invalid 'expires' attribute: " + value);
175         }
176 
177         final Calendar c = Calendar.getInstance();
178         c.setTimeZone(UTC);
179         c.setTimeInMillis(0L);
180         c.set(Calendar.SECOND, second);
181         c.set(Calendar.MINUTE, minute);
182         c.set(Calendar.HOUR_OF_DAY, hour);
183         c.set(Calendar.DAY_OF_MONTH, day);
184         c.set(Calendar.MONTH, month);
185         c.set(Calendar.YEAR, year);
186         cookie.setExpiryDate(c.getTime());
187     }
188 
189     private void skipDelims(final CharSequence buf, final ParserCursor cursor) {
190         int pos = cursor.getPos();
191         final int indexFrom = cursor.getPos();
192         final int indexTo = cursor.getUpperBound();
193         for (int i = indexFrom; i < indexTo; i++) {
194             final char current = buf.charAt(i);
195             if (DELIMS.get(current)) {
196                 pos++;
197             } else {
198                 break;
199             }
200         }
201         cursor.updatePos(pos);
202     }
203 
204     private void copyContent(final CharSequence buf, final ParserCursor cursor, final StringBuilder dst) {
205         int pos = cursor.getPos();
206         final int indexFrom = cursor.getPos();
207         final int indexTo = cursor.getUpperBound();
208         for (int i = indexFrom; i < indexTo; i++) {
209             final char current = buf.charAt(i);
210             if (DELIMS.get(current)) {
211                 break;
212             }
213             pos++;
214             dst.append(current);
215         }
216         cursor.updatePos(pos);
217     }
218 
219     @Override
220     public String getAttributeName() {
221         return ClientCookie.EXPIRES_ATTR;
222     }
223 
224 }