View Javadoc
1   /*
2    * Copyright 2015 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    *   https://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.netty5.handler.codec.http.cookie;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.Set;
23  import java.util.TreeSet;
24  
25  import static java.util.Objects.requireNonNull;
26  
27  /**
28   * A <a href="https://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used server side.
29   *
30   * Only name and value fields are expected, so old fields are not populated (path, domain, etc).
31   *
32   * Old <a href="https://tools.ietf.org/html/rfc2965">RFC2965</a> cookies are still supported,
33   * old fields will simply be ignored.
34   *
35   * @see ServerCookieEncoder
36   */
37  public final class ServerCookieDecoder extends CookieDecoder {
38  
39      private static final String RFC2965_VERSION = "$Version";
40  
41      private static final String RFC2965_PATH = "$" + CookieHeaderNames.PATH;
42  
43      private static final String RFC2965_DOMAIN = "$" + CookieHeaderNames.DOMAIN;
44  
45      private static final String RFC2965_PORT = "$Port";
46  
47      /**
48       * Strict encoder that validates that name and value chars are in the valid scope
49       * defined in RFC6265
50       */
51      public static final ServerCookieDecoder STRICT = new ServerCookieDecoder(true);
52  
53      /**
54       * Lax instance that doesn't validate name and value
55       */
56      public static final ServerCookieDecoder LAX = new ServerCookieDecoder(false);
57  
58      private ServerCookieDecoder(boolean strict) {
59          super(strict);
60      }
61  
62      /**
63       * Decodes the specified {@code Cookie} HTTP header value into a {@link Cookie}. Unlike {@link #decode(String)},
64       * this includes all cookie values present, even if they have the same name.
65       *
66       * @return the decoded {@link Cookie}
67       */
68      public List<Cookie> decodeAll(String header) {
69          List<Cookie> cookies = new ArrayList<>();
70          decode(cookies, header);
71          return Collections.unmodifiableList(cookies);
72      }
73  
74      /**
75       * Decodes the specified {@code Cookie} HTTP header value into a {@link Cookie}.
76       *
77       * @return the decoded {@link Cookie}
78       */
79      public Set<Cookie> decode(String header) {
80          Set<Cookie> cookies = new TreeSet<>();
81          decode(cookies, header);
82          return cookies;
83      }
84  
85      /**
86       * Decodes the specified {@code Cookie} HTTP header value into a {@link Cookie}.
87       */
88      private void decode(Collection<? super Cookie> cookies, String header) {
89          final int headerLen = requireNonNull(header, "header").length();
90  
91          if (headerLen == 0) {
92              return;
93          }
94  
95          int i = 0;
96  
97          boolean rfc2965Style = false;
98          if (header.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) {
99              // RFC 2965 style cookie, move to after version value
100             i = header.indexOf(';') + 1;
101             rfc2965Style = true;
102         }
103 
104         loop: for (;;) {
105 
106             // Skip spaces and separators.
107             for (;;) {
108                 if (i == headerLen) {
109                     break loop;
110                 }
111                 char c = header.charAt(i);
112                 if (c == '\t' || c == '\n' || c == 0x0b || c == '\f'
113                         || c == '\r' || c == ' ' || c == ',' || c == ';') {
114                     i++;
115                     continue;
116                 }
117                 break;
118             }
119 
120             int nameBegin = i;
121             int nameEnd;
122             int valueBegin;
123             int valueEnd;
124 
125             for (;;) {
126 
127                 char curChar = header.charAt(i);
128                 if (curChar == ';') {
129                     // NAME; (no value till ';')
130                     nameEnd = i;
131                     valueBegin = valueEnd = -1;
132                     break;
133 
134                 } else if (curChar == '=') {
135                     // NAME=VALUE
136                     nameEnd = i;
137                     i++;
138                     if (i == headerLen) {
139                         // NAME= (empty value, i.e. nothing after '=')
140                         valueBegin = valueEnd = 0;
141                         break;
142                     }
143 
144                     valueBegin = i;
145                     // NAME=VALUE;
146                     int semiPos = header.indexOf(';', i);
147                     valueEnd = i = semiPos > 0 ? semiPos : headerLen;
148                     break;
149                 } else {
150                     i++;
151                 }
152 
153                 if (i == headerLen) {
154                     // NAME (no value till the end of string)
155                     nameEnd = headerLen;
156                     valueBegin = valueEnd = -1;
157                     break;
158                 }
159             }
160 
161             if (rfc2965Style && (header.regionMatches(nameBegin, RFC2965_PATH, 0, RFC2965_PATH.length()) ||
162                     header.regionMatches(nameBegin, RFC2965_DOMAIN, 0, RFC2965_DOMAIN.length()) ||
163                     header.regionMatches(nameBegin, RFC2965_PORT, 0, RFC2965_PORT.length()))) {
164 
165                 // skip obsolete RFC2965 fields
166                 continue;
167             }
168 
169             DefaultCookie cookie = initCookie(header, nameBegin, nameEnd, valueBegin, valueEnd);
170             if (cookie != null) {
171                 cookies.add(cookie);
172             }
173         }
174     }
175 }