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         if (sb.length() > 0) {
171             sb.setLength(sb.length() - 1);
172         }
173 
174         return sb.toString();
175     }
176 
177     private String encodeClientSide() {
178         StringBuilder sb = new StringBuilder();
179 
180         for (Cookie cookie: cookies) {
181             if (cookie.getVersion() >= 1) {
182                 add(sb, '$' + CookieHeaderNames.VERSION, 1);
183             }
184 
185             add(sb, cookie.getName(), cookie.getValue());
186 
187             if (cookie.getPath() != null) {
188                 add(sb, '$' + CookieHeaderNames.PATH, cookie.getPath());
189             }
190 
191             if (cookie.getDomain() != null) {
192                 add(sb, '$' + CookieHeaderNames.DOMAIN, cookie.getDomain());
193             }
194 
195             if (cookie.getVersion() >= 1) {
196                 if(!cookie.getPorts().isEmpty()) {
197                     sb.append('$');
198                     sb.append(CookieHeaderNames.PORT);
199                     sb.append((char) HttpCodecUtil.EQUALS);
200                     sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
201                     for (int port: cookie.getPorts()) {
202                         sb.append(port);
203                         sb.append((char) HttpCodecUtil.COMMA);
204                     }
205                     sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE);
206                     sb.append((char) HttpCodecUtil.SEMICOLON);
207                 }
208             }
209         }
210 
211         if(sb.length() > 0)
212         	sb.setLength(sb.length() - 1);
213         return sb.toString();
214     }
215 
216     private static void add(StringBuilder sb, String name, String val) {
217         if (val == null) {
218             addQuoted(sb, name, "");
219             return;
220         }
221 
222         for (int i = 0; i < val.length(); i ++) {
223             char c = val.charAt(i);
224             switch (c) {
225             case '\t': case ' ': case '"': case '(':  case ')': case ',':
226             case '/':  case ':': case ';': case '<':  case '=': case '>':
227             case '?':  case '@': case '[': case '\\': case ']':
228             case '{':  case '}':
229                 addQuoted(sb, name, val);
230                 return;
231             }
232         }
233 
234         addUnquoted(sb, name, val);
235     }
236 
237     private static void addUnquoted(StringBuilder sb, String name, String val) {
238         sb.append(name);
239         sb.append((char) HttpCodecUtil.EQUALS);
240         sb.append(val);
241         sb.append((char) HttpCodecUtil.SEMICOLON);
242     }
243 
244     private static void addQuoted(StringBuilder sb, String name, String val) {
245         if (val == null) {
246             val = "";
247         }
248 
249         sb.append(name);
250         sb.append((char) HttpCodecUtil.EQUALS);
251         sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
252         sb.append(val.replace("\\", "\\\\").replace("\"", "\\\""));
253         sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
254         sb.append((char) HttpCodecUtil.SEMICOLON);
255     }
256 
257     private static void add(StringBuilder sb, String name, int val) {
258         sb.append(name);
259         sb.append((char) HttpCodecUtil.EQUALS);
260         sb.append(val);
261         sb.append((char) HttpCodecUtil.SEMICOLON);
262     }
263 }