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