View Javadoc

    1   /*
    2    * Copyright 2012 The Netty Project
    3    *
    4    * The Netty Project licenses this file to you under the Apache License,
    5    * version 2.0 (the "License"); you may not use this file except in compliance
    6    * with the 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
    25  /**
    26   * Decodes an HTTP header value into {@link Cookie}s.  This decoder can decode
    27   * the HTTP cookie version 0, 1, and 2.
    28   *
    29   * <pre>
    30   * {@link HttpRequest} req = ...;
    31   * String value = req.getHeader("Cookie");
    32   * Set&lt;{@link Cookie}&gt; cookies = new {@link CookieDecoder}().decode(value);
    33   * </pre>
    34   *
    35   * @see CookieEncoder
    36   *
    37   * @apiviz.stereotype utility
    38   * @apiviz.has        org.jboss.netty.handler.codec.http.Cookie oneway - - decodes
    39   */
    40  public class CookieDecoder {
    41
    42      private static final String COMMA = ",";
    43
    44      /**
    45       * Creates a new decoder.
    46       */
    47      public CookieDecoder() {
    48          super();
    49      }
    50
    51      /**
    52       * @deprecated Use {@link #CookieDecoder()} instead.
    53       */
    54      @Deprecated
    55      public CookieDecoder(@SuppressWarnings("unused") boolean lenient) {
    56          super();
    57      }
    58
    59      /**
    60       * Decodes the specified HTTP header value into {@link Cookie}s.
    61       *
    62       * @return the decoded {@link Cookie}s
    63       */
    64      public Set<Cookie> decode(String header) {
    65          List<String> names = new ArrayList<String>(8);
    66          List<String> values = new ArrayList<String>(8);
    67          extractKeyValuePairs(header, names, values);
    68
    69          if (names.isEmpty()) {
    70              return Collections.emptySet();
    71          }
    72
    73          int i;
    74          int version = 0;
    75
    76          // $Version is the only attribute that can appear before the actual
    77          // cookie name-value pair.
    78          if (names.get(0).equalsIgnoreCase(CookieHeaderNames.VERSION)) {
    79              try {
    80                  version = Integer.parseInt(values.get(0));
    81              } catch (NumberFormatException e) {
    82                  // Ignore.
    83              }
    84              i = 1;
    85          } else {
    86              i = 0;
    87          }
    88
    89          if (names.size() <= i) {
    90              // There's a version attribute, but nothing more.
    91              return Collections.emptySet();
    92          }
    93
    94          Set<Cookie> cookies = new TreeSet<Cookie>();
    95          for (; i < names.size(); i ++) {
    96              String name = names.get(i);
    97              String value = values.get(i);
    98              if (value == null) {
    99                  value = "";
    100             }
    101
    102             Cookie c = new DefaultCookie(name, value);
    103
    104             boolean discard = false;
    105             boolean secure = false;
    106             boolean httpOnly = false;
    107             String comment = null;
    108             String commentURL = null;
    109             String domain = null;
    110             String path = null;
    111             int maxAge = Integer.MIN_VALUE;
    112             List<Integer> ports = new ArrayList<Integer>(2);
    113
    114             for (int j = i + 1; j < names.size(); j++, i++) {
    115                 name = names.get(j);
    116                 value = values.get(j);
    117
    118                 if (CookieHeaderNames.DISCARD.equalsIgnoreCase(name)) {
    119                     discard = true;
    120                 } else if (CookieHeaderNames.SECURE.equalsIgnoreCase(name)) {
    121                     secure = true;
    122                 } else if (CookieHeaderNames.HTTPONLY.equalsIgnoreCase(name)) {
    123                    httpOnly = true;
    124                 } else if (CookieHeaderNames.COMMENT.equalsIgnoreCase(name)) {
    125                     comment = value;
    126                 } else if (CookieHeaderNames.COMMENTURL.equalsIgnoreCase(name)) {
    127                     commentURL = value;
    128                 } else if (CookieHeaderNames.DOMAIN.equalsIgnoreCase(name)) {
    129                     domain = value;
    130                 } else if (CookieHeaderNames.PATH.equalsIgnoreCase(name)) {
    131                     path = value;
    132                 } else if (CookieHeaderNames.EXPIRES.equalsIgnoreCase(name)) {
    133                     try {
    134                         long maxAgeMillis =
    135                             new CookieDateFormat().parse(value).getTime() -
    136                             System.currentTimeMillis();
    137                         if (maxAgeMillis <= 0) {
    138                             maxAge = 0;
    139                         } else {
    140                             maxAge = (int) (maxAgeMillis / 1000) +
    141                                      (maxAgeMillis % 1000 != 0? 1 : 0);
    142                         }
    143                     } catch (ParseException e) {
    144                         // Ignore.
    145                     }
    146                 } else if (CookieHeaderNames.MAX_AGE.equalsIgnoreCase(name)) {
    147                     maxAge = Integer.parseInt(value);
    148                 } else if (CookieHeaderNames.VERSION.equalsIgnoreCase(name)) {
    149                     version = Integer.parseInt(value);
    150                 } else if (CookieHeaderNames.PORT.equalsIgnoreCase(name)) {
    151                     String[] portList = value.split(COMMA);
    152                     for (String s1: portList) {
    153                         try {
    154                             ports.add(Integer.valueOf(s1));
    155                         } catch (NumberFormatException e) {
    156                             // Ignore.
    157                         }
    158                     }
    159                 } else {
    160                     break;
    161                 }
    162             }
    163
    164             c.setVersion(version);
    165             c.setMaxAge(maxAge);
    166             c.setPath(path);
    167             c.setDomain(domain);
    168             c.setSecure(secure);
    169             c.setHttpOnly(httpOnly);
    170             if (version > 0) {
    171                 c.setComment(comment);
    172             }
    173             if (version > 1) {
    174                 c.setCommentUrl(commentURL);
    175                 c.setPorts(ports);
    176                 c.setDiscard(discard);
    177             }
    178
    179             cookies.add(c);
    180         }
    181
    182         return cookies;
    183     }
    184
    185
    186     private static void extractKeyValuePairs(
    187             final String header, final List<String> names, final List<String> values) {
    188
    189         final int headerLen  = header.length();
    190         loop: for (int i = 0;;) {
    191
    192             // Skip spaces and separators.
    193             for (;;) {
    194                 if (i == headerLen) {
    195                     break loop;
    196                 }
    197                 switch (header.charAt(i)) {
    198                 case '\t': case '\n': case 0x0b: case '\f': case '\r':
    199                 case ' ':  case ',':  case ';':
    200                     i ++;
    201                     continue;
    202                 }
    203                 break;
    204             }
    205
    206             // Skip '$'.
    207             for (;;) {
    208                 if (i == headerLen) {
    209                     break loop;
    210                 }
    211                 if (header.charAt(i) == '$') {
    212                     i ++;
    213                     continue;
    214                 }
    215                 break;
    216             }
    217
    218             String name;
    219             String value;
    220
    221             if (i == headerLen) {
    222                 name = null;
    223                 value = null;
    224             } else {
    225                 int newNameStart = i;
    226                 keyValLoop: for (;;) {
    227                     switch (header.charAt(i)) {
    228                     case ';':
    229                         // NAME; (no value till ';')
    230                         name = header.substring(newNameStart, i);
    231                         value = null;
    232                         break keyValLoop;
    233                     case '=':
    234                         // NAME=VALUE
    235                         name = header.substring(newNameStart, i);
    236                         i ++;
    237                         if (i == headerLen) {
    238                             // NAME= (empty value, i.e. nothing after '=')
    239                             value = "";
    240                             break keyValLoop;
    241                         }
    242
    243                         int newValueStart = i;
    244                         char c = header.charAt(i);
    245                         if (c == '"' || c == '\'') {
    246                             // NAME="VALUE" or NAME='VALUE'
    247                             StringBuilder newValueBuf = new StringBuilder(header.length() - i);
    248                             final char q = c;
    249                             boolean hadBackslash = false;
    250                             i ++;
    251                             for (;;) {
    252                                 if (i == headerLen) {
    253                                     value = newValueBuf.toString();
    254                                     break keyValLoop;
    255                                 }
    256                                 if (hadBackslash) {
    257                                     hadBackslash = false;
    258                                     c = header.charAt(i ++);
    259                                     switch (c) {
    260                                     case '\\': case '"': case '\'':
    261                                         // Escape last backslash.
    262                                         newValueBuf.setCharAt(newValueBuf.length() - 1, c);
    263                                         break;
    264                                     default:
    265                                         // Do not escape last backslash.
    266                                         newValueBuf.append(c);
    267                                     }
    268                                 } else {
    269                                     c = header.charAt(i ++);
    270                                     if (c == q) {
    271                                         value = newValueBuf.toString();
    272                                         break keyValLoop;
    273                                     }
    274                                     newValueBuf.append(c);
    275                                     if (c == '\\') {
    276                                         hadBackslash = true;
    277                                     }
    278                                 }
    279                             }
    280                         } else {
    281                             // NAME=VALUE;
    282                             int semiPos = header.indexOf(';', i);
    283                             if (semiPos > 0) {
    284                                 value = header.substring(newValueStart, semiPos);
    285                                 i = semiPos;
    286                             } else {
    287                                 value = header.substring(newValueStart);
    288                                 i = headerLen;
    289                             }
    290                         }
    291                         break keyValLoop;
    292                     default:
    293                         i ++;
    294                     }
    295
    296                     if (i == headerLen) {
    297                         // NAME (no value till the end of string)
    298                         name = header.substring(newNameStart);
    299                         value = null;
    300                         break;
    301                     }
    302                 }
    303             }
    304
    305             names.add(name);
    306             values.add(value);
    307         }
    308     }
    309 }