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.util.Date;
    19  import java.util.Set;
    20  import java.util.TreeSet;
    21
    22  /**
    23   * Encodes {@link Cookie}s into an HTTP header value.  This encoder can encode
    24   * the HTTP cookie version 0, 1, and 2.
    25   * <p>
    26   * This encoder is stateful.  It maintains an internal data structure that
    27   * holds the {@link Cookie}s added by the {@link #addCookie(String, String)}
    28   * method.  Once {@link #encode()} is called, all added {@link Cookie}s are
    29   * encoded into an HTTP header value and all {@link Cookie}s in the internal
    30   * data structure are removed so that the encoder can start over.
    31   * <pre>
    32   * // Client-side example
    33   * {@link HttpRequest} req = ...;
    34   * {@link CookieEncoder} encoder = new {@link CookieEncoder}(false);
    35   * encoder.addCookie("JSESSIONID", "1234");
    36   * res.setHeader("Cookie", encoder.encode());
    37   *
    38   * // Server-side example
    39   * {@link HttpResponse} res = ...;
    40   * {@link CookieEncoder} encoder = new {@link CookieEncoder}(true);
    41   * encoder.addCookie("JSESSIONID", "1234");
    42   * res.setHeader("Set-Cookie", encoder.encode());
    43   * </pre>
    44   *
    45   * @see CookieDecoder
    46   *
    47   * @apiviz.stereotype utility
    48   * @apiviz.has        org.jboss.netty.handler.codec.http.Cookie oneway - - encodes
    49   */
    50  public class CookieEncoder {
    51
    52      private final Set<Cookie> cookies = new TreeSet<Cookie>();
    53      private final boolean server;
    54
    55      /**
    56       * Creates a new encoder.
    57       *
    58       * @param server {@code true} if and only if this encoder is supposed to
    59       *               encode server-side cookies.  {@code false} if and only if
    60       *               this encoder is supposed to encode client-side cookies.
    61       */
    62      public CookieEncoder(boolean server) {
    63          this.server = server;
    64      }
    65
    66      /**
    67       * Adds a new {@link Cookie} created with the specified name and value to
    68       * this encoder.
    69       */
    70      public void addCookie(String name, String value) {
    71          cookies.add(new DefaultCookie(name, value));
    72      }
    73
    74      /**
    75       * Adds the specified {@link Cookie} to this encoder.
    76       */
    77      public void addCookie(Cookie cookie) {
    78          cookies.add(cookie);
    79      }
    80
    81      /**
    82       * Encodes the {@link Cookie}s which were added by {@link #addCookie(Cookie)}
    83       * so far into an HTTP header value.  If no {@link Cookie}s were added,
    84       * an empty string is returned.
    85       *
    86       * <strong>Be aware that calling this method will clear the content of the {@link CookieEncoder}</strong>
    87       */
    88      public String encode() {
    89          String answer;
    90          if (server) {
    91              answer = encodeServerSide();
    92          } else {
    93              answer = encodeClientSide();
    94          }
    95          cookies.clear();
    96          return answer;
    97      }
    98
    99      private String encodeServerSide() {
    100         if (cookies.size() > 1) {
    101             throw new IllegalStateException(
    102                     "encode() can encode only one cookie on server mode: " + cookies.size() + " cookies added");
    103         }
    104
    105         StringBuilder sb = new StringBuilder();
    106
    107         for (Cookie cookie: cookies) {
    108             add(sb, cookie.getName(), cookie.getValue());
    109
    110             if (cookie.getMaxAge() != Integer.MIN_VALUE) {
    111                 if (cookie.getVersion() == 0) {
    112                     addUnquoted(sb, CookieHeaderNames.EXPIRES,
    113                             new CookieDateFormat().format(
    114                                     new Date(System.currentTimeMillis() +
    115                                              cookie.getMaxAge() * 1000L)));
    116                 } else {
    117                     add(sb, CookieHeaderNames.MAX_AGE, cookie.getMaxAge());
    118                 }
    119             }
    120
    121             if (cookie.getPath() != null) {
    122                 if (cookie.getVersion() > 0) {
    123                     add(sb, CookieHeaderNames.PATH, cookie.getPath());
    124                 } else {
    125                     addUnquoted(sb, CookieHeaderNames.PATH, cookie.getPath());
    126                 }
    127             }
    128
    129             if (cookie.getDomain() != null) {
    130                 if (cookie.getVersion() > 0) {
    131                     add(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
    132                 } else {
    133                     addUnquoted(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
    134                 }
    135             }
    136             if (cookie.isSecure()) {
    137                 sb.append(CookieHeaderNames.SECURE);
    138                 sb.append((char) HttpConstants.SEMICOLON);
    139                 sb.append((char) HttpConstants.SP);
    140             }
    141             if (cookie.isHttpOnly()) {
    142                 sb.append(CookieHeaderNames.HTTPONLY);
    143                 sb.append((char) HttpConstants.SEMICOLON);
    144                 sb.append((char) HttpConstants.SP);
    145             }
    146             if (cookie.getVersion() >= 1) {
    147                 if (cookie.getComment() != null) {
    148                     add(sb, CookieHeaderNames.COMMENT, cookie.getComment());
    149                 }
    150
    151                 add(sb, CookieHeaderNames.VERSION, 1);
    152
    153                 if (cookie.getCommentUrl() != null) {
    154                     addQuoted(sb, CookieHeaderNames.COMMENTURL, cookie.getCommentUrl());
    155                 }
    156
    157                 if (!cookie.getPorts().isEmpty()) {
    158                     sb.append(CookieHeaderNames.PORT);
    159                     sb.append((char) HttpConstants.EQUALS);
    160                     sb.append((char) HttpConstants.DOUBLE_QUOTE);
    161                     for (int port: cookie.getPorts()) {
    162                         sb.append(port);
    163                         sb.append((char) HttpConstants.COMMA);
    164                     }
    165                     sb.setCharAt(sb.length() - 1, (char) HttpConstants.DOUBLE_QUOTE);
    166                     sb.append((char) HttpConstants.SEMICOLON);
    167                     sb.append((char) HttpConstants.SP);
    168
    169                 }
    170                 if (cookie.isDiscard()) {
    171                     sb.append(CookieHeaderNames.DISCARD);
    172                     sb.append((char) HttpConstants.SEMICOLON);
    173                     sb.append((char) HttpConstants.SP);
    174                 }
    175             }
    176         }
    177
    178         if (sb.length() > 0) {
    179             sb.setLength(sb.length() - 2);
    180         }
    181
    182         return sb.toString();
    183     }
    184
    185     private String encodeClientSide() {
    186         StringBuilder sb = new StringBuilder();
    187
    188         for (Cookie cookie: cookies) {
    189             if (cookie.getVersion() >= 1) {
    190                 add(sb, '$' + CookieHeaderNames.VERSION, 1);
    191             }
    192
    193             add(sb, cookie.getName(), cookie.getValue());
    194
    195             if (cookie.getPath() != null) {
    196                 add(sb, '$' + CookieHeaderNames.PATH, cookie.getPath());
    197             }
    198
    199             if (cookie.getDomain() != null) {
    200                 add(sb, '$' + CookieHeaderNames.DOMAIN, cookie.getDomain());
    201             }
    202
    203             if (cookie.getVersion() >= 1) {
    204                 if (!cookie.getPorts().isEmpty()) {
    205                     sb.append('$');
    206                     sb.append(CookieHeaderNames.PORT);
    207                     sb.append((char) HttpConstants.EQUALS);
    208                     sb.append((char) HttpConstants.DOUBLE_QUOTE);
    209                     for (int port: cookie.getPorts()) {
    210                         sb.append(port);
    211                         sb.append((char) HttpConstants.COMMA);
    212                     }
    213                     sb.setCharAt(sb.length() - 1, (char) HttpConstants.DOUBLE_QUOTE);
    214                     sb.append((char) HttpConstants.SEMICOLON);
    215                     sb.append((char) HttpConstants.SP);
    216                 }
    217             }
    218         }
    219
    220         if (sb.length() > 0) {
    221             sb.setLength(sb.length() - 2);
    222         }
    223         return sb.toString();
    224     }
    225
    226     private static void add(StringBuilder sb, String name, String val) {
    227         if (val == null) {
    228             addQuoted(sb, name, "");
    229             return;
    230         }
    231
    232         for (int i = 0; i < val.length(); i ++) {
    233             char c = val.charAt(i);
    234             switch (c) {
    235             case '\t': case ' ': case '"': case '(':  case ')': case ',':
    236             case '/':  case ':': case ';': case '<':  case '=': case '>':
    237             case '?':  case '@': case '[': case '\\': case ']':
    238             case '{':  case '}':
    239                 addQuoted(sb, name, val);
    240                 return;
    241             }
    242         }
    243
    244         addUnquoted(sb, name, val);
    245     }
    246
    247     private static void addUnquoted(StringBuilder sb, String name, String val) {
    248         sb.append(name);
    249         sb.append((char) HttpConstants.EQUALS);
    250         sb.append(val);
    251         sb.append((char) HttpConstants.SEMICOLON);
    252         sb.append((char) HttpConstants.SP);
    253     }
    254
    255     private static void addQuoted(StringBuilder sb, String name, String val) {
    256         if (val == null) {
    257             val = "";
    258         }
    259
    260         sb.append(name);
    261         sb.append((char) HttpConstants.EQUALS);
    262         sb.append((char) HttpConstants.DOUBLE_QUOTE);
    263         sb.append(val.replace("\\", "\\\\").replace("\"", "\\\""));
    264         sb.append((char) HttpConstants.DOUBLE_QUOTE);
    265         sb.append((char) HttpConstants.SEMICOLON);
    266         sb.append((char) HttpConstants.SP);
    267
    268     }
    269
    270     private static void add(StringBuilder sb, String name, int val) {
    271         sb.append(name);
    272         sb.append((char) HttpConstants.EQUALS);
    273         sb.append(val);
    274         sb.append((char) HttpConstants.SEMICOLON);
    275         sb.append((char) HttpConstants.SP);
    276     }
    277 }