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