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 }