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.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   * @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
    46   * @author Andy Taylor (andy.taylor@jboss.org)
    47   * @author <a href="http://gleamynode.net/">Trustin Lee</a>
    48   * @version $Rev: 2122 $, $Date: 2010-02-02 11:00:04 +0900 (Tue, 02 Feb 2010) $
    49   * @see CookieDecoder
    50   *
    51   * @apiviz.stereotype utility
    52   * @apiviz.has        org.jboss.netty.handler.codec.http.Cookie oneway - - encodes
    53   */
    54  public class CookieEncoder {
    55
    56      private final Set<Cookie> cookies = new TreeSet<Cookie>();
    57      private final boolean server;
    58
    59      /**
    60       * Creates a new encoder.
    61       *
    62       * @param server {@code true} if and only if this encoder is supposed to
    63       *               encode server-side cookies.  {@code false} if and only if
    64       *               this encoder is supposed to encode client-side cookies.
    65       */
    66      public CookieEncoder(boolean server) {
    67          this.server = server;
    68      }
    69
    70      /**
    71       * Adds a new {@link Cookie} created with the specified name and value to
    72       * this encoder.
    73       */
    74      public void addCookie(String name, String value) {
    75          cookies.add(new DefaultCookie(name, value));
    76      }
    77
    78      /**
    79       * Adds the specified {@link Cookie} to this encoder.
    80       */
    81      public void addCookie(Cookie cookie) {
    82          cookies.add(cookie);
    83      }
    84
    85      /**
    86       * Encodes the {@link Cookie}s which were added by {@link #addCookie(Cookie)}
    87       * so far into an HTTP header value.  If no {@link Cookie}s were added,
    88       * an empty string is returned.
    89       */
    90      public String encode() {
    91          String answer;
    92          if (server) {
    93              answer = encodeServerSide();
    94          } else {
    95              answer = encodeClientSide();
    96          }
    97          cookies.clear();
    98          return answer;
    99      }
    100
    101     private String encodeServerSide() {
    102         StringBuilder sb = new StringBuilder();
    103
    104         for (Cookie cookie: cookies) {
    105             add(sb, cookie.getName(), cookie.getValue());
    106
    107             if (cookie.getMaxAge() >= 0) {
    108                 if (cookie.getVersion() == 0) {
    109                     addUnquoted(sb, CookieHeaderNames.EXPIRES,
    110                             new CookieDateFormat().format(
    111                                     new Date(System.currentTimeMillis() +
    112                                              cookie.getMaxAge() * 1000L)));
    113                 } else {
    114                     add(sb, CookieHeaderNames.MAX_AGE, cookie.getMaxAge());
    115                 }
    116             }
    117
    118             if (cookie.getPath() != null) {
    119                 if (cookie.getVersion() > 0) {
    120                     add(sb, CookieHeaderNames.PATH, cookie.getPath());
    121                 } else {
    122                     addUnquoted(sb, CookieHeaderNames.PATH, cookie.getPath());
    123                 }
    124             }
    125
    126             if (cookie.getDomain() != null) {
    127                 if (cookie.getVersion() > 0) {
    128                     add(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
    129                 } else {
    130                     addUnquoted(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
    131                 }
    132             }
    133             if (cookie.isSecure()) {
    134                 sb.append(CookieHeaderNames.SECURE);
    135                 sb.append((char) HttpCodecUtil.SEMICOLON);
    136             }
    137             if (cookie.isHttpOnly()) {
    138                 sb.append(CookieHeaderNames.HTTPONLY);
    139                 sb.append((char) HttpCodecUtil.SEMICOLON);
    140             }
    141             if (cookie.getVersion() >= 1) {
    142                 if (cookie.getComment() != null) {
    143                     add(sb, CookieHeaderNames.COMMENT, cookie.getComment());
    144                 }
    145
    146                 add(sb, CookieHeaderNames.VERSION, 1);
    147
    148                 if (cookie.getCommentUrl() != null) {
    149                     addQuoted(sb, CookieHeaderNames.COMMENTURL, cookie.getCommentUrl());
    150                 }
    151
    152                 if(!cookie.getPorts().isEmpty()) {
    153                     sb.append(CookieHeaderNames.PORT);
    154                     sb.append((char) HttpCodecUtil.EQUALS);
    155                     sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
    156                     for (int port: cookie.getPorts()) {
    157                         sb.append(port);
    158                         sb.append((char) HttpCodecUtil.COMMA);
    159                     }
    160                     sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE);
    161                     sb.append((char) HttpCodecUtil.SEMICOLON);
    162                 }
    163                 if (cookie.isDiscard()) {
    164                     sb.append(CookieHeaderNames.DISCARD);
    165                     sb.append((char) HttpCodecUtil.SEMICOLON);
    166                 }
    167             }
    168         }
    169
    170         sb.setLength(sb.length() - 1);
    171         return sb.toString();
    172     }
    173
    174     private String encodeClientSide() {
    175         StringBuilder sb = new StringBuilder();
    176
    177         for (Cookie cookie: cookies) {
    178             if (cookie.getVersion() >= 1) {
    179                 add(sb, '$' + CookieHeaderNames.VERSION, 1);
    180             }
    181
    182             add(sb, cookie.getName(), cookie.getValue());
    183
    184             if (cookie.getPath() != null) {
    185                 add(sb, '$' + CookieHeaderNames.PATH, cookie.getPath());
    186             }
    187
    188             if (cookie.getDomain() != null) {
    189                 add(sb, '$' + CookieHeaderNames.DOMAIN, cookie.getDomain());
    190             }
    191
    192             if (cookie.getVersion() >= 1) {
    193                 if(!cookie.getPorts().isEmpty()) {
    194                     sb.append('$');
    195                     sb.append(CookieHeaderNames.PORT);
    196                     sb.append((char) HttpCodecUtil.EQUALS);
    197                     sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
    198                     for (int port: cookie.getPorts()) {
    199                         sb.append(port);
    200                         sb.append((char) HttpCodecUtil.COMMA);
    201                     }
    202                     sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE);
    203                     sb.append((char) HttpCodecUtil.SEMICOLON);
    204                 }
    205             }
    206         }
    207
    208         sb.setLength(sb.length() - 1);
    209         return sb.toString();
    210     }
    211
    212     private static void add(StringBuilder sb, String name, String val) {
    213         if (val == null) {
    214             addQuoted(sb, name, "");
    215             return;
    216         }
    217
    218         for (int i = 0; i < val.length(); i ++) {
    219             char c = val.charAt(i);
    220             switch (c) {
    221             case '\t': case ' ': case '"': case '(':  case ')': case ',':
    222             case '/':  case ':': case ';': case '<':  case '=': case '>':
    223             case '?':  case '@': case '[': case '\\': case ']':
    224             case '{':  case '}':
    225                 addQuoted(sb, name, val);
    226                 return;
    227             }
    228         }
    229
    230         addUnquoted(sb, name, val);
    231     }
    232
    233     private static void addUnquoted(StringBuilder sb, String name, String val) {
    234         sb.append(name);
    235         sb.append((char) HttpCodecUtil.EQUALS);
    236         sb.append(val);
    237         sb.append((char) HttpCodecUtil.SEMICOLON);
    238     }
    239
    240     private static void addQuoted(StringBuilder sb, String name, String val) {
    241         if (val == null) {
    242             val = "";
    243         }
    244
    245         sb.append(name);
    246         sb.append((char) HttpCodecUtil.EQUALS);
    247         sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
    248         sb.append(val.replace("\\", "\\\\").replace("\"", "\\\""));
    249         sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
    250         sb.append((char) HttpCodecUtil.SEMICOLON);
    251     }
    252
    253     private static void add(StringBuilder sb, String name, int val) {
    254         sb.append(name);
    255         sb.append((char) HttpCodecUtil.EQUALS);
    256         sb.append(val);
    257         sb.append((char) HttpCodecUtil.SEMICOLON);
    258     }
    259 }