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.http2;
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: 1108 $, $Date: 2012-04-21 12:51:25 +0200 (sam., 21 avr. 2012) $
49   * @see CookieDecoder
50   *
51   * @apiviz.stereotype utility
52   * @apiviz.has        org.jboss.netty.handler.codec.http2.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  * <strong>Be aware that calling this method will clear the content of the {@link CookieEncoder}</strong>
91  */
92      public String encode() {
93          String answer;
94          if (server) {
95              answer = encodeServerSide();
96          } else {
97              answer = encodeClientSide();
98          }
99          cookies.clear();
100         return answer;
101     }
102 
103     private String encodeServerSide() {
104         StringBuilder sb = new StringBuilder();
105 
106         for (Cookie cookie: cookies) {
107             add(sb, cookie.getName(), cookie.getValue());
108 
109             if (cookie.getMaxAge() >= 0) {
110                 if (cookie.getVersion() == 0) {
111                     addUnquoted(sb, CookieHeaderNames.EXPIRES,
112                             new HttpHeaderDateFormat().format(
113                                     new Date(System.currentTimeMillis() +
114                                              cookie.getMaxAge() * 1000L)));
115                 } else {
116                     add(sb, CookieHeaderNames.MAX_AGE, cookie.getMaxAge());
117                 }
118             }
119 
120             if (cookie.getPath() != null) {
121                 if (cookie.getVersion() > 0) {
122                     add(sb, CookieHeaderNames.PATH, cookie.getPath());
123                 } else {
124                     addUnquoted(sb, CookieHeaderNames.PATH, cookie.getPath());
125                 }
126             }
127 
128             if (cookie.getDomain() != null) {
129                 if (cookie.getVersion() > 0) {
130                     add(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
131                 } else {
132                     addUnquoted(sb, CookieHeaderNames.DOMAIN, cookie.getDomain());
133                 }
134             }
135             if (cookie.isSecure()) {
136                 sb.append(CookieHeaderNames.SECURE);
137                 sb.append((char) HttpCodecUtil.SEMICOLON);
138             }
139             if (cookie.isHttpOnly()) {
140                 sb.append(CookieHeaderNames.HTTPONLY);
141                 sb.append((char) HttpCodecUtil.SEMICOLON);
142             }
143             if (cookie.getVersion() >= 1) {
144                 if (cookie.getComment() != null) {
145                     add(sb, CookieHeaderNames.COMMENT, cookie.getComment());
146                 }
147 
148                 add(sb, CookieHeaderNames.VERSION, 1);
149 
150                 if (cookie.getCommentUrl() != null) {
151                     addQuoted(sb, CookieHeaderNames.COMMENTURL, cookie.getCommentUrl());
152                 }
153 
154                 if (!cookie.getPorts().isEmpty()) {
155                     sb.append(CookieHeaderNames.PORT);
156                     sb.append((char) HttpCodecUtil.EQUALS);
157                     sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
158                     for (int port: cookie.getPorts()) {
159                         sb.append(port);
160                         sb.append((char) HttpCodecUtil.COMMA);
161                     }
162                     sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE);
163                     sb.append((char) HttpCodecUtil.SEMICOLON);
164                 }
165                 if (cookie.isDiscard()) {
166                     sb.append(CookieHeaderNames.DISCARD);
167                     sb.append((char) HttpCodecUtil.SEMICOLON);
168                 }
169             }
170         }
171 
172         if (sb.length() > 0) {
173             sb.setLength(sb.length() - 1);
174         }
175 
176         return sb.toString();
177     }
178 
179     private String encodeClientSide() {
180         StringBuilder sb = new StringBuilder();
181 
182         for (Cookie cookie: cookies) {
183             if (cookie.getVersion() >= 1) {
184                 add(sb, '$' + CookieHeaderNames.VERSION, 1);
185             }
186 
187             add(sb, cookie.getName(), cookie.getValue());
188 
189             if (cookie.getPath() != null) {
190                 add(sb, '$' + CookieHeaderNames.PATH, cookie.getPath());
191             }
192 
193             if (cookie.getDomain() != null) {
194                 add(sb, '$' + CookieHeaderNames.DOMAIN, cookie.getDomain());
195             }
196 
197             if (cookie.getVersion() >= 1) {
198                 if (!cookie.getPorts().isEmpty()) {
199                     sb.append('$');
200                     sb.append(CookieHeaderNames.PORT);
201                     sb.append((char) HttpCodecUtil.EQUALS);
202                     sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
203                     for (int port: cookie.getPorts()) {
204                         sb.append(port);
205                         sb.append((char) HttpCodecUtil.COMMA);
206                     }
207                     sb.setCharAt(sb.length() - 1, (char) HttpCodecUtil.DOUBLE_QUOTE);
208                     sb.append((char) HttpCodecUtil.SEMICOLON);
209                 }
210             }
211         }
212 
213         if (sb.length() > 0) {
214             sb.setLength(sb.length() - 1);
215         }
216 
217         return sb.toString();
218     }
219 
220     private static void add(StringBuilder sb, String name, String val) {
221         if (val == null) {
222             addQuoted(sb, name, "");
223             return;
224         }
225 
226         for (int i = 0; i < val.length(); i ++) {
227             char c = val.charAt(i);
228             switch (c) {
229             case '\t': case ' ': case '"': case '(': case ')': case ',':
230             case '/': case ':': case ';': case '<': case '=': case '>':
231             case '?': case '@': case '[': case '\\': case ']':
232             case '{': case '}':
233                 addQuoted(sb, name, val);
234                 return;
235             }
236         }
237 
238         addUnquoted(sb, name, val);
239     }
240 
241     private static void addUnquoted(StringBuilder sb, String name, String val) {
242         sb.append(name);
243         sb.append((char) HttpCodecUtil.EQUALS);
244         sb.append(val);
245         sb.append((char) HttpCodecUtil.SEMICOLON);
246     }
247 
248     private static void addQuoted(StringBuilder sb, String name, String val) {
249         if (val == null) {
250             val = "";
251         }
252 
253         sb.append(name);
254         sb.append((char) HttpCodecUtil.EQUALS);
255         sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
256         sb.append(val.replace("\\", "\\\\").replace("\"", "\\\""));
257         sb.append((char) HttpCodecUtil.DOUBLE_QUOTE);
258         sb.append((char) HttpCodecUtil.SEMICOLON);
259     }
260 
261     private static void add(StringBuilder sb, String name, int val) {
262         sb.append(name);
263         sb.append((char) HttpCodecUtil.EQUALS);
264         sb.append(val);
265         sb.append((char) HttpCodecUtil.SEMICOLON);
266     }
267 }