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 }