View Javadoc
1   /*
2    * Copyright 2014 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 io.netty.handler.codec.http;
17  
18  import java.util.Collections;
19  import java.util.Set;
20  import java.util.TreeSet;
21  
22  import static io.netty.handler.codec.http.CookieEncoderUtil.*;
23  
24  /**
25   * A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used server side.
26   *
27   * Only name and value fields are expected, so old fields are not populated (path, domain, etc).
28   *
29   * Old <a href="http://tools.ietf.org/html/rfc2965">RFC2965</a> cookies are still supported,
30   * old fields will simply be ignored.
31   *
32   * @see ServerCookieEncoder
33   */
34  public final class ServerCookieDecoder {
35  
36      /**
37       * Decodes the specified Set-Cookie HTTP header value into a {@link Cookie}.
38       *
39       * @return the decoded {@link Cookie}
40       */
41      public static Set<Cookie> decode(String header) {
42  
43          if (header == null) {
44              throw new NullPointerException("header");
45          }
46  
47          final int headerLen = header.length();
48  
49          if (headerLen == 0) {
50              return Collections.emptySet();
51          }
52  
53          Set<Cookie> cookies = new TreeSet<Cookie>();
54  
55          int i = 0;
56  
57          boolean rfc2965Style = false;
58          if (header.regionMatches(true, 0, "$Version", 0, 8)) {
59              // RFC 2965 style cookie, move to after version value
60              i = header.indexOf(';') + 1;
61              rfc2965Style = true;
62          }
63  
64          loop: for (;;) {
65  
66              // Skip spaces and separators.
67              for (;;) {
68                  if (i == headerLen) {
69                      break loop;
70                  }
71                  char c = header.charAt(i);
72                  if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
73                          || c == '\r' || c == ' ' || c == ',' || c == ';') {
74                      i++;
75                      continue;
76                  }
77                  break;
78              }
79  
80              int newNameStart = i;
81              int newNameEnd = i;
82              String value;
83  
84              if (i == headerLen) {
85                  value = null;
86              } else {
87                  keyValLoop: for (;;) {
88  
89                      char curChar = header.charAt(i);
90                      if (curChar == ';') {
91                          // NAME; (no value till ';')
92                          newNameEnd = i;
93                          value = null;
94                          break keyValLoop;
95                      } else if (curChar == '=') {
96                          // NAME=VALUE
97                          newNameEnd = i;
98                          i++;
99                          if (i == headerLen) {
100                             // NAME= (empty value, i.e. nothing after '=')
101                             value = "";
102                             break keyValLoop;
103                         }
104 
105                         int newValueStart = i;
106                         char c = header.charAt(i);
107                         if (c == '"') {
108                             // NAME="VALUE"
109                             StringBuilder newValueBuf = stringBuilder();
110 
111                             final char q = c;
112                             boolean hadBackslash = false;
113                             i++;
114                             for (;;) {
115                                 if (i == headerLen) {
116                                     value = newValueBuf.toString();
117                                     break keyValLoop;
118                                 }
119                                 if (hadBackslash) {
120                                     hadBackslash = false;
121                                     c = header.charAt(i++);
122                                     if (c == '\\' || c == '"') {
123                                         // Escape last backslash.
124                                         newValueBuf.setCharAt(newValueBuf.length() - 1, c);
125                                     } else {
126                                         // Do not escape last backslash.
127                                         newValueBuf.append(c);
128                                     }
129                                 } else {
130                                     c = header.charAt(i++);
131                                     if (c == q) {
132                                         value = newValueBuf.toString();
133                                         break keyValLoop;
134                                     }
135                                     newValueBuf.append(c);
136                                     if (c == '\\') {
137                                         hadBackslash = true;
138                                     }
139                                 }
140                             }
141                         } else {
142                             // NAME=VALUE;
143                             int semiPos = header.indexOf(';', i);
144                             if (semiPos > 0) {
145                                 value = header.substring(newValueStart, semiPos);
146                                 i = semiPos;
147                             } else {
148                                 value = header.substring(newValueStart);
149                                 i = headerLen;
150                             }
151                         }
152                         break keyValLoop;
153                     } else {
154                         i++;
155                     }
156 
157                     if (i == headerLen) {
158                         // NAME (no value till the end of string)
159                         newNameEnd = headerLen;
160                         value = null;
161                         break;
162                     }
163                 }
164             }
165 
166             if (!rfc2965Style || (!header.regionMatches(newNameStart, "$Path", 0, "$Path".length()) &&
167                     !header.regionMatches(newNameStart, "$Domain", 0, "$Domain".length()) &&
168                     !header.regionMatches(newNameStart, "$Port", 0, "$Port".length()))) {
169 
170                 // skip obsolete RFC2965 fields
171                 String name = header.substring(newNameStart, newNameEnd);
172                 cookies.add(new DefaultCookie(name, value));
173             }
174         }
175 
176         return cookies;
177     }
178 
179     private ServerCookieDecoder() {
180         // unused
181     }
182 }