1
18
19 package io.undertow.util;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.UndertowMessages;
23 import io.undertow.server.handlers.Cookie;
24 import io.undertow.server.handlers.CookieImpl;
25
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.TreeMap;
30
31
37 public class Cookies {
38
39 public static final String DOMAIN = "$Domain";
40 public static final String VERSION = "$Version";
41 public static final String PATH = "$Path";
42
43
44
71 public static Cookie parseSetCookieHeader(final String headerValue) {
72
73 String key = null;
74 CookieImpl cookie = null;
75 int state = 0;
76 int current = 0;
77 for (int i = 0; i < headerValue.length(); ++i) {
78 char c = headerValue.charAt(i);
79 switch (state) {
80 case 0: {
81
82 if (c == '=') {
83 key = headerValue.substring(current, i);
84 current = i + 1;
85 state = 1;
86 } else if ((c == ';' || c == ' ') && current == i) {
87 current++;
88 } else if (c == ';') {
89 if (cookie == null) {
90 throw UndertowMessages.MESSAGES.couldNotParseCookie(headerValue);
91 } else {
92 handleValue(cookie, headerValue.substring(current, i), null);
93 }
94 current = i + 1;
95 }
96 break;
97 }
98 case 1: {
99 if (c == ';') {
100 if (cookie == null) {
101 cookie = new CookieImpl(key, headerValue.substring(current, i));
102 } else {
103 handleValue(cookie, key, headerValue.substring(current, i));
104 }
105 state = 0;
106 current = i + 1;
107 key = null;
108 } else if (c == '"' && current == i) {
109 current++;
110 state = 2;
111 }
112 break;
113 }
114 case 2: {
115 if (c == '"') {
116 if (cookie == null) {
117 cookie = new CookieImpl(key, headerValue.substring(current, i));
118 } else {
119 handleValue(cookie, key, headerValue.substring(current, i));
120 }
121 state = 0;
122 current = i + 1;
123 key = null;
124 }
125 break;
126 }
127 }
128 }
129 if (key == null) {
130 if (current != headerValue.length()) {
131 handleValue(cookie, headerValue.substring(current, headerValue.length()), null);
132 }
133 } else {
134 if (current != headerValue.length()) {
135 if(cookie == null) {
136 cookie = new CookieImpl(key, headerValue.substring(current, headerValue.length()));
137 } else {
138 handleValue(cookie, key, headerValue.substring(current, headerValue.length()));
139 }
140 } else {
141 handleValue(cookie, key, null);
142 }
143 }
144
145 return cookie;
146 }
147
148 private static void handleValue(CookieImpl cookie, String key, String value) {
149 if (key.equalsIgnoreCase("path")) {
150 cookie.setPath(value);
151 } else if (key.equalsIgnoreCase("domain")) {
152 cookie.setDomain(value);
153 } else if (key.equalsIgnoreCase("max-age")) {
154 cookie.setMaxAge(Integer.parseInt(value));
155 } else if (key.equalsIgnoreCase("expires")) {
156 cookie.setExpires(DateUtils.parseDate(value));
157 } else if (key.equalsIgnoreCase("discard")) {
158 cookie.setDiscard(true);
159 } else if (key.equalsIgnoreCase("secure")) {
160 cookie.setSecure(true);
161 } else if (key.equalsIgnoreCase("httpOnly")) {
162 cookie.setHttpOnly(true);
163 } else if (key.equalsIgnoreCase("version")) {
164 cookie.setVersion(Integer.parseInt(value));
165 } else if (key.equalsIgnoreCase("comment")) {
166 cookie.setComment(value);
167 } else if (key.equalsIgnoreCase("samesite")) {
168 cookie.setSameSite(true);
169 cookie.setSameSiteMode(value);
170 }
171
172 }
173
174
200 public static Map<String, Cookie> parseRequestCookies(int maxCookies, boolean allowEqualInValue, List<String> cookies) {
201 return parseRequestCookies(maxCookies, allowEqualInValue, cookies, LegacyCookieSupport.COMMA_IS_SEPARATOR);
202 }
203
204 static Map<String, Cookie> parseRequestCookies(int maxCookies, boolean allowEqualInValue, List<String> cookies, boolean commaIsSeperator) {
205 return parseRequestCookies(maxCookies, allowEqualInValue, cookies, commaIsSeperator, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
206 }
207
208 static Map<String, Cookie> parseRequestCookies(int maxCookies, boolean allowEqualInValue, List<String> cookies, boolean commaIsSeperator, boolean allowHttpSepartorsV0) {
209 if (cookies == null) {
210 return new TreeMap<>();
211 }
212 final Map<String, Cookie> parsedCookies = new TreeMap<>();
213
214 for (String cookie : cookies) {
215 parseCookie(cookie, parsedCookies, maxCookies, allowEqualInValue, commaIsSeperator, allowHttpSepartorsV0);
216 }
217 return parsedCookies;
218 }
219
220 private static void parseCookie(final String cookie, final Map<String, Cookie> parsedCookies, int maxCookies, boolean allowEqualInValue, boolean commaIsSeperator, boolean allowHttpSepartorsV0) {
221 int state = 0;
222 String name = null;
223 int start = 0;
224 boolean containsEscapedQuotes = false;
225 int cookieCount = parsedCookies.size();
226 final Map<String, String> cookies = new HashMap<>();
227 final Map<String, String> additional = new HashMap<>();
228 for (int i = 0; i < cookie.length(); ++i) {
229 char c = cookie.charAt(i);
230 switch (state) {
231 case 0: {
232
233 if (c == ' ' || c == '\t' || c == ';') {
234 start = i + 1;
235 break;
236 }
237 state = 1;
238
239 }
240 case 1: {
241
242 if (c == '=') {
243 name = cookie.substring(start, i);
244 start = i + 1;
245 state = 2;
246 } else if (c == ';' || (commaIsSeperator && c == ',')) {
247 if(name != null) {
248 cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional);
249 } else if(UndertowLogger.REQUEST_LOGGER.isTraceEnabled()) {
250 UndertowLogger.REQUEST_LOGGER.trace("Ignoring invalid cookies in header " + cookie);
251 }
252 state = 0;
253 start = i + 1;
254 }
255 break;
256 }
257 case 2: {
258
259 if (c == ';' || (commaIsSeperator && c == ',')) {
260 cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional);
261 state = 0;
262 start = i + 1;
263 } else if (c == '"' && start == i) { if it is the first character
264 containsEscapedQuotes = false;
265 state = 3;
266 start = i + 1;
267 } else if (c == '=') {
268 if (!allowEqualInValue && !allowHttpSepartorsV0) {
269 cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional);
270 state = 4;
271 start = i + 1;
272 }
273 } else if (c != ':' && !allowHttpSepartorsV0 && LegacyCookieSupport.isHttpSeparator(c)) {
274
275
276
277
278 cookieCount = createCookie(name, cookie.substring(start, i), maxCookies, cookieCount, cookies, additional);
279 state = 4;
280 start = i + 1;
281 }
282 break;
283 }
284 case 3: {
285
286 if (c == '"') {
287 cookieCount = createCookie(name, containsEscapedQuotes ? unescapeDoubleQuotes(cookie.substring(start, i)) : cookie.substring(start, i), maxCookies, cookieCount, cookies, additional);
288 state = 0;
289 start = i + 1;
290 }
291
292 if (c == '\\' && (i + 1 < cookie.length()) && cookie.charAt(i + 1) == '"') {
293
294 if (i + 2 == cookie.length()) {
295 break;
296 }
297 if (i + 2 < cookie.length() && (cookie.charAt(i + 2) == ';'
298 || (commaIsSeperator && cookie.charAt(i + 2) == ','))) {
299 break;
300 }
301
302 i++;
303 containsEscapedQuotes = true;
304 }
305 break;
306 }
307 case 4: {
308
309 if (c == ';' || (commaIsSeperator && c == ',')) {
310 state = 0;
311 }
312 start = i + 1;
313 break;
314 }
315 }
316 }
317 if (state == 2) {
318 createCookie(name, cookie.substring(start), maxCookies, cookieCount, cookies, additional);
319 }
320
321 for (final Map.Entry<String, String> entry : cookies.entrySet()) {
322 Cookie c = new CookieImpl(entry.getKey(), entry.getValue());
323 String domain = additional.get(DOMAIN);
324 if (domain != null) {
325 c.setDomain(domain);
326 }
327 String version = additional.get(VERSION);
328 if (version != null) {
329 c.setVersion(Integer.parseInt(version));
330 }
331 String path = additional.get(PATH);
332 if (path != null) {
333 c.setPath(path);
334 }
335 parsedCookies.put(c.getName(), c);
336 }
337 }
338
339 private static int createCookie(final String name, final String value, int maxCookies, int cookieCount,
340 final Map<String, String> cookies, final Map<String, String> additional) {
341 if (!name.isEmpty() && name.charAt(0) == '$') {
342 if(additional.containsKey(name)) {
343 return cookieCount;
344 }
345 additional.put(name, value);
346 return cookieCount;
347 } else {
348 if (cookieCount == maxCookies) {
349 throw UndertowMessages.MESSAGES.tooManyCookies(maxCookies);
350 }
351 if(cookies.containsKey(name)) {
352 return cookieCount;
353 }
354 cookies.put(name, value);
355 return ++cookieCount;
356 }
357 }
358
359 private static String unescapeDoubleQuotes(final String value) {
360 if (value == null || value.isEmpty()) {
361 return value;
362 }
363
364
365 char[] tmp = new char[value.length()];
366 int dest = 0;
367 for(int i = 0; i < value.length(); i++) {
368 if (value.charAt(i) == '\\' && (i + 1 < value.length()) && value.charAt(i + 1) == '"') {
369 i++;
370 }
371 tmp[dest] = value.charAt(i);
372 dest++;
373 }
374 return new String(tmp, 0, dest);
375 }
376
377 private Cookies() {
378
379 }
380 }
381