View Javadoc

1   /*
2    * Copyright 2009 Red Hat, Inc.
3    *
4    * Red Hat licenses this file to you under the Apache License, version 2.0
5    * (the "License"); you may not use this file except in compliance with the
6    * License.  You may obtain a copy of the License at:
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.handler.codec.http;
17  
18  import java.text.ParseException;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.Set;
23  import java.util.TreeSet;
24  import java.util.regex.Matcher;
25  import java.util.regex.Pattern;
26  
27  /**
28   * Decodes an HTTP header value into {@link Cookie}s.  This decoder can decode
29   * the HTTP cookie version 0, 1, and 2.
30   *
31   * <pre>
32   * {@link HttpRequest} req = ...;
33   * String value = req.getHeader("Cookie");
34   * Set&lt;{@link Cookie}&gt; cookies = new {@link CookieDecoder}().decode(value);
35   * </pre>
36   *
37   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
38   * @author Andy Taylor (andy.taylor@jboss.org)
39   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
40   * @version $Rev: 2122 $, $Date: 2010-02-02 11:00:04 +0900 (Tue, 02 Feb 2010) $
41   * @see CookieEncoder
42   *
43   * @apiviz.stereotype utility
44   * @apiviz.has        org.jboss.netty.handler.codec.http.Cookie oneway - - decodes
45   */
46  public class CookieDecoder {
47  
48      private final static Pattern PATTERN =
49          Pattern.compile("(?:\\s|[;,])*\\$*([^;=]+)(?:=(?:[\"']((?:\\\\.|[^\"])*)[\"']|([^;,]*)))?(\\s*(?:[;,]+\\s*|$))");
50  
51      private final static String COMMA = ",";
52  
53      /**
54       * Creates a new decoder.
55       */
56      public CookieDecoder() {
57          super();
58      }
59  
60      /**
61       * Decodes the specified HTTP header value into {@link Cookie}s.
62       *
63       * @return the decoded {@link Cookie}s
64       */
65      public Set<Cookie> decode(String header) {
66          List<String> names = new ArrayList<String>(8);
67          List<String> values = new ArrayList<String>(8);
68          extractKeyValuePairs(header, names, values);
69  
70          if (names.isEmpty()) {
71              return Collections.emptySet();
72          }
73  
74          int i;
75          int version = 0;
76  
77          // $Version is the only attribute that can appear before the actual
78          // cookie name-value pair.
79          if (names.get(0).equalsIgnoreCase(CookieHeaderNames.VERSION)) {
80              try {
81                  version = Integer.parseInt(values.get(0));
82              } catch (NumberFormatException e) {
83                  // Ignore.
84              }
85              i = 1;
86          } else {
87              i = 0;
88          }
89  
90          if (names.size() <= i) {
91              // There's a version attribute, but nothing more.
92              return Collections.emptySet();
93          }
94  
95          Set<Cookie> cookies = new TreeSet<Cookie>();
96          for (; i < names.size(); i ++) {
97              String name = names.get(i);
98              String value = values.get(i);
99              if (value == null) {
100                 value = "";
101             }
102 
103             Cookie c = new DefaultCookie(name, value);
104             cookies.add(c);
105 
106             boolean discard = false;
107             boolean secure = false;
108             boolean httpOnly = false;
109             String comment = null;
110             String commentURL = null;
111             String domain = null;
112             String path = null;
113             int maxAge = -1;
114             List<Integer> ports = new ArrayList<Integer>(2);
115 
116             for (int j = i + 1; j < names.size(); j++, i++) {
117                 name = names.get(j);
118                 value = values.get(j);
119 
120                 if (CookieHeaderNames.DISCARD.equalsIgnoreCase(name)) {
121                     discard = true;
122                 } else if (CookieHeaderNames.SECURE.equalsIgnoreCase(name)) {
123                     secure = true;
124                 } else if (CookieHeaderNames.HTTPONLY.equalsIgnoreCase(name)) {
125                    httpOnly = true;
126                 } else if (CookieHeaderNames.COMMENT.equalsIgnoreCase(name)) {
127                     comment = value;
128                 } else if (CookieHeaderNames.COMMENTURL.equalsIgnoreCase(name)) {
129                     commentURL = value;
130                 } else if (CookieHeaderNames.DOMAIN.equalsIgnoreCase(name)) {
131                     domain = value;
132                 } else if (CookieHeaderNames.PATH.equalsIgnoreCase(name)) {
133                     path = value;
134                 } else if (CookieHeaderNames.EXPIRES.equalsIgnoreCase(name)) {
135                     try {
136                         long maxAgeMillis =
137                             new CookieDateFormat().parse(value).getTime() -
138                             System.currentTimeMillis();
139                         if (maxAgeMillis <= 0) {
140                             maxAge = 0;
141                         } else {
142                             maxAge = (int) (maxAgeMillis / 1000) +
143                                      (maxAgeMillis % 1000 != 0? 1 : 0);
144                         }
145                     } catch (ParseException e) {
146                         // Ignore.
147                     }
148                 } else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) {
149                     maxAge = Integer.parseInt(value);
150                 } else if (CookieHeaderNames.VERSION.equalsIgnoreCase(name)) {
151                     version = Integer.parseInt(value);
152                 } else if (CookieHeaderNames.PORT.equalsIgnoreCase(name)) {
153                     String[] portList = value.split(COMMA);
154                     for (String s1: portList) {
155                         try {
156                             ports.add(Integer.valueOf(s1));
157                         } catch (NumberFormatException e) {
158                             // Ignore.
159                         }
160                     }
161                 } else {
162                     break;
163                 }
164             }
165 
166             c.setVersion(version);
167             c.setMaxAge(maxAge);
168             c.setPath(path);
169             c.setDomain(domain);
170             c.setSecure(secure);
171             c.setHttpOnly(httpOnly);
172             if (version > 0) {
173                 c.setComment(comment);
174             }
175             if (version > 1) {
176                 c.setCommentUrl(commentURL);
177                 c.setPorts(ports);
178                 c.setDiscard(discard);
179             }
180         }
181 
182         return cookies;
183     }
184 
185     private void extractKeyValuePairs(
186             String header, List<String> names, List<String> values) {
187         Matcher m = PATTERN.matcher(header);
188         int pos = 0;
189         String name = null;
190         String value = null;
191         String separator = null;
192         while (m.find(pos)) {
193             pos = m.end();
194 
195             // Extract name and value pair from the match.
196             String newName = m.group(1);
197             String newValue = m.group(3);
198             if (newValue == null) {
199                 newValue = decodeValue(m.group(2));
200             }
201             String newSeparator = m.group(4);
202 
203             if (name == null) {
204                 name = newName;
205                 value = newValue == null? "" : newValue;
206                 separator = newSeparator;
207                 continue;
208             }
209 
210             if (newValue == null &&
211                 !CookieHeaderNames.DISCARD.equalsIgnoreCase(newName) &&
212                 !CookieHeaderNames.SECURE.equalsIgnoreCase(newName) &&
213                 !CookieHeaderNames.HTTPONLY.equalsIgnoreCase(newName)) {
214                 value = value + separator + newName;
215                 separator = newSeparator;
216                 continue;
217             }
218 
219             names.add(name);
220             values.add(value);
221 
222             name = newName;
223             value = newValue;
224             separator = newSeparator;
225         }
226 
227         // The last entry
228         if (name != null) {
229             names.add(name);
230             values.add(value);
231         }
232     }
233 
234     private String decodeValue(String value) {
235         if (value == null) {
236             return value;
237         }
238         return value.replace("\\\"", "\"").replace("\\\\", "\\");
239     }
240 }