1
17 package org.apache.tomcat.util.http.parser;
18
19 import java.nio.charset.StandardCharsets;
20
21 import org.apache.juli.logging.Log;
22 import org.apache.juli.logging.LogFactory;
23 import org.apache.tomcat.util.http.ServerCookie;
24 import org.apache.tomcat.util.http.ServerCookies;
25 import org.apache.tomcat.util.log.UserDataHelper;
26 import org.apache.tomcat.util.res.StringManager;
27
28
29
52 public class Cookie {
53
54 private static final Log log = LogFactory.getLog(Cookie.class);
55 private static final UserDataHelper invalidCookieVersionLog = new UserDataHelper(log);
56 private static final UserDataHelper invalidCookieLog = new UserDataHelper(log);
57 private static final StringManager sm =
58 StringManager.getManager("org.apache.tomcat.util.http.parser");
59
60 private static final boolean isCookieOctet[] = new boolean[256];
61 private static final boolean isText[] = new boolean[256];
62 private static final byte[] VERSION_BYTES = "$Version".getBytes(StandardCharsets.ISO_8859_1);
63 private static final byte[] PATH_BYTES = "$Path".getBytes(StandardCharsets.ISO_8859_1);
64 private static final byte[] DOMAIN_BYTES = "$Domain".getBytes(StandardCharsets.ISO_8859_1);
65 private static final byte[] EMPTY_BYTES = new byte[0];
66 private static final byte TAB_BYTE = (byte) 0x09;
67 private static final byte SPACE_BYTE = (byte) 0x20;
68 private static final byte QUOTE_BYTE = (byte) 0x22;
69 private static final byte COMMA_BYTE = (byte) 0x2C;
70 private static final byte FORWARDSLASH_BYTE = (byte) 0x2F;
71 private static final byte SEMICOLON_BYTE = (byte) 0x3B;
72 private static final byte EQUALS_BYTE = (byte) 0x3D;
73 private static final byte SLASH_BYTE = (byte) 0x5C;
74 private static final byte DEL_BYTE = (byte) 0x7F;
75
76
77 static {
78
79
80 for (int i = 0; i < 256; i++) {
81 if (i < 0x21 || i == QUOTE_BYTE || i == COMMA_BYTE ||
82 i == SEMICOLON_BYTE || i == SLASH_BYTE || i == DEL_BYTE) {
83 isCookieOctet[i] = false;
84 } else {
85 isCookieOctet[i] = true;
86 }
87 }
88 for (int i = 0; i < 256; i++) {
89 if (i < TAB_BYTE || (i > TAB_BYTE && i < SPACE_BYTE) || i == DEL_BYTE) {
90 isText[i] = false;
91 } else {
92 isText[i] = true;
93 }
94 }
95 }
96
97
98 private Cookie() {
99
100 }
101
102
103 public static void parseCookie(byte[] bytes, int offset, int len,
104 ServerCookies serverCookies) {
105
106
107
108 ByteBuffer bb = new ByteBuffer(bytes, offset, len);
109
110
111
112
113
114 skipLWS(bb);
115
116
117 int mark = bb.position();
118
119 SkipResult skipResult = skipBytes(bb, VERSION_BYTES);
120 if (skipResult != SkipResult.FOUND) {
121
122 parseCookieRfc6265(bb, serverCookies);
123 return;
124 }
125
126 skipLWS(bb);
127
128 skipResult = skipByte(bb, EQUALS_BYTE);
129 if (skipResult != SkipResult.FOUND) {
130
131
132 bb.position(mark);
133 parseCookieRfc6265(bb, serverCookies);
134 return;
135 }
136
137 skipLWS(bb);
138
139 ByteBuffer value = readCookieValue(bb);
140 if (value != null && value.remaining() == 1) {
141 int version = value.get() - '0';
142 if (version == 1 || version == 0) {
143
144
145 skipLWS(bb);
146 byte b = bb.get();
147 if (b == SEMICOLON_BYTE || b == COMMA_BYTE) {
148 parseCookieRfc2109(bb, serverCookies, version);
149 }
150 } else {
151
152
153 value.rewind();
154 logInvalidVersion(value);
155 }
156 } else {
157
158
159 logInvalidVersion(value);
160 }
161 }
162
163
164 public static String unescapeCookieValueRfc2109(String input) {
165 if (input == null || input.length() < 2) {
166 return input;
167 }
168 if (input.charAt(0) != '"' && input.charAt(input.length() - 1) != '"') {
169 return input;
170 }
171
172 StringBuilder sb = new StringBuilder(input.length());
173 char[] chars = input.toCharArray();
174 boolean escaped = false;
175
176 for (int i = 1; i < input.length() - 1; i++) {
177 if (chars[i] == '\\') {
178 escaped = true;
179 } else if (escaped) {
180 escaped = false;
181 if (chars[i] < 128) {
182 sb.append(chars[i]);
183 } else {
184 sb.append('\\');
185 sb.append(chars[i]);
186 }
187 } else {
188 sb.append(chars[i]);
189 }
190 }
191 return sb.toString();
192 }
193
194
195 private static void parseCookieRfc6265(ByteBuffer bb, ServerCookies serverCookies) {
196
197 boolean moreToProcess = true;
198
199 while (moreToProcess) {
200 skipLWS(bb);
201
202 ByteBuffer name = readToken(bb);
203 ByteBuffer value = null;
204
205 skipLWS(bb);
206
207 SkipResult skipResult = skipByte(bb, EQUALS_BYTE);
208 if (skipResult == SkipResult.FOUND) {
209 skipLWS(bb);
210 value = readCookieValueRfc6265(bb);
211 if (value == null) {
212 logInvalidHeader(bb);
213
214 skipUntilSemiColon(bb);
215 continue;
216 }
217 skipLWS(bb);
218 }
219
220 skipResult = skipByte(bb, SEMICOLON_BYTE);
221 if (skipResult == SkipResult.FOUND) {
222
223 } else if (skipResult == SkipResult.NOT_FOUND) {
224 logInvalidHeader(bb);
225
226 skipUntilSemiColon(bb);
227 continue;
228 } else {
229
230 moreToProcess = false;
231 }
232
233 if (name.hasRemaining()) {
234 ServerCookie sc = serverCookies.addCookie();
235 sc.getName().setBytes(name.array(), name.position(), name.remaining());
236 if (value == null) {
237 sc.getValue().setBytes(EMPTY_BYTES, 0, EMPTY_BYTES.length);
238 } else {
239 sc.getValue().setBytes(value.array(), value.position(), value.remaining());
240 }
241 }
242 }
243 }
244
245
246 private static void parseCookieRfc2109(ByteBuffer bb, ServerCookies serverCookies,
247 int version) {
248
249 boolean moreToProcess = true;
250
251 while (moreToProcess) {
252 skipLWS(bb);
253
254 boolean parseAttributes = true;
255
256 ByteBuffer name = readToken(bb);
257 ByteBuffer value = null;
258 ByteBuffer path = null;
259 ByteBuffer domain = null;
260
261 skipLWS(bb);
262
263 SkipResult skipResult = skipByte(bb, EQUALS_BYTE);
264 if (skipResult == SkipResult.FOUND) {
265 skipLWS(bb);
266 value = readCookieValueRfc2109(bb, false);
267 if (value == null) {
268 skipInvalidCookie(bb);
269 continue;
270 }
271 skipLWS(bb);
272 }
273
274 skipResult = skipByte(bb, COMMA_BYTE);
275 if (skipResult == SkipResult.FOUND) {
276 parseAttributes = false;
277 }
278 skipResult = skipByte(bb, SEMICOLON_BYTE);
279 if (skipResult == SkipResult.EOF) {
280 parseAttributes = false;
281 moreToProcess = false;
282 } else if (skipResult == SkipResult.NOT_FOUND) {
283 skipInvalidCookie(bb);
284 continue;
285 }
286
287 if (parseAttributes) {
288 skipResult = skipBytes(bb, PATH_BYTES);
289 if (skipResult == SkipResult.FOUND) {
290 skipLWS(bb);
291 skipResult = skipByte(bb, EQUALS_BYTE);
292 if (skipResult != SkipResult.FOUND) {
293 skipInvalidCookie(bb);
294 continue;
295 }
296 path = readCookieValueRfc2109(bb, true);
297 if (path == null) {
298 skipInvalidCookie(bb);
299 continue;
300 }
301 skipLWS(bb);
302
303 skipResult = skipByte(bb, COMMA_BYTE);
304 if (skipResult == SkipResult.FOUND) {
305 parseAttributes = false;
306 }
307 skipResult = skipByte(bb, SEMICOLON_BYTE);
308 if (skipResult == SkipResult.EOF) {
309 parseAttributes = false;
310 moreToProcess = false;
311 } else if (skipResult == SkipResult.NOT_FOUND) {
312 skipInvalidCookie(bb);
313 continue;
314 }
315 }
316 }
317
318 if (parseAttributes) {
319 skipResult = skipBytes(bb, DOMAIN_BYTES);
320 if (skipResult == SkipResult.FOUND) {
321 skipLWS(bb);
322 skipResult = skipByte(bb, EQUALS_BYTE);
323 if (skipResult != SkipResult.FOUND) {
324 skipInvalidCookie(bb);
325 continue;
326 }
327 domain = readCookieValueRfc2109(bb, false);
328 if (domain == null) {
329 skipInvalidCookie(bb);
330 continue;
331 }
332
333 skipResult = skipByte(bb, COMMA_BYTE);
334 if (skipResult == SkipResult.FOUND) {
335 parseAttributes = false;
336 }
337 skipResult = skipByte(bb, SEMICOLON_BYTE);
338 if (skipResult == SkipResult.EOF) {
339 parseAttributes = false;
340 moreToProcess = false;
341 } else if (skipResult == SkipResult.NOT_FOUND) {
342 skipInvalidCookie(bb);
343 continue;
344 }
345 }
346 }
347
348 if (name.hasRemaining() && value != null && value.hasRemaining()) {
349 ServerCookie sc = serverCookies.addCookie();
350 sc.setVersion(version);
351 sc.getName().setBytes(name.array(), name.position(), name.remaining());
352 sc.getValue().setBytes(value.array(), value.position(), value.remaining());
353 if (domain != null) {
354 sc.getDomain().setBytes(domain.array(), domain.position(), domain.remaining());
355 }
356 if (path != null) {
357 sc.getPath().setBytes(path.array(), path.position(), path.remaining());
358 }
359 }
360 }
361 }
362
363
364 private static void skipInvalidCookie(ByteBuffer bb) {
365 logInvalidHeader(bb);
366
367 skipUntilSemiColonOrComma(bb);
368 }
369
370
371 private static void skipLWS(ByteBuffer bb) {
372 while(bb.hasRemaining()) {
373 byte b = bb.get();
374 if (b != TAB_BYTE && b != SPACE_BYTE) {
375 bb.rewind();
376 break;
377 }
378 }
379 }
380
381
382 private static void skipUntilSemiColon(ByteBuffer bb) {
383 while(bb.hasRemaining()) {
384 if (bb.get() == SEMICOLON_BYTE) {
385 break;
386 }
387 }
388 }
389
390
391 private static void skipUntilSemiColonOrComma(ByteBuffer bb) {
392 while(bb.hasRemaining()) {
393 byte b = bb.get();
394 if (b == SEMICOLON_BYTE || b == COMMA_BYTE) {
395 break;
396 }
397 }
398 }
399
400
401 private static SkipResult skipByte(ByteBuffer bb, byte target) {
402
403 if (!bb.hasRemaining()) {
404 return SkipResult.EOF;
405 }
406 if (bb.get() == target) {
407 return SkipResult.FOUND;
408 }
409
410 bb.rewind();
411 return SkipResult.NOT_FOUND;
412 }
413
414
415 private static SkipResult skipBytes(ByteBuffer bb, byte[] target) {
416 int mark = bb.position();
417
418 for (int i = 0; i < target.length; i++) {
419 if (!bb.hasRemaining()) {
420 bb.position(mark);
421 return SkipResult.EOF;
422 }
423 if (bb.get() != target[i]) {
424 bb.position(mark);
425 return SkipResult.NOT_FOUND;
426 }
427 }
428 return SkipResult.FOUND;
429 }
430
431
432
436 private static ByteBuffer readCookieValue(ByteBuffer bb) {
437 boolean quoted = false;
438 if (bb.hasRemaining()) {
439 if (bb.get() == QUOTE_BYTE) {
440 quoted = true;
441 } else {
442 bb.rewind();
443 }
444 }
445 int start = bb.position();
446 int end = bb.limit();
447 while (bb.hasRemaining()) {
448 byte b = bb.get();
449 if (isCookieOctet[(b & 0xFF)]) {
450
451 } else if (b == SEMICOLON_BYTE || b == COMMA_BYTE || b == SPACE_BYTE || b == TAB_BYTE) {
452 end = bb.position() - 1;
453 bb.position(end);
454 break;
455 } else if (quoted && b == QUOTE_BYTE) {
456 end = bb.position() - 1;
457 break;
458 } else {
459
460 return null;
461 }
462 }
463
464 return new ByteBuffer(bb.bytes, start, end - start);
465 }
466
467
468
472 private static ByteBuffer readCookieValueRfc6265(ByteBuffer bb) {
473 boolean quoted = false;
474 if (bb.hasRemaining()) {
475 if (bb.get() == QUOTE_BYTE) {
476 quoted = true;
477 } else {
478 bb.rewind();
479 }
480 }
481 int start = bb.position();
482 int end = bb.limit();
483 while (bb.hasRemaining()) {
484 byte b = bb.get();
485 if (isCookieOctet[(b & 0xFF)]) {
486
487 } else if (b == SEMICOLON_BYTE || b == SPACE_BYTE || b == TAB_BYTE) {
488 end = bb.position() - 1;
489 bb.position(end);
490 break;
491 } else if (quoted && b == QUOTE_BYTE) {
492 end = bb.position() - 1;
493 break;
494 } else {
495
496 return null;
497 }
498 }
499
500 return new ByteBuffer(bb.bytes, start, end - start);
501 }
502
503
504 private static ByteBuffer readCookieValueRfc2109(ByteBuffer bb, boolean allowForwardSlash) {
505 if (!bb.hasRemaining()) {
506 return null;
507 }
508
509 if (bb.peek() == QUOTE_BYTE) {
510 return readQuotedString(bb);
511 } else {
512 if (allowForwardSlash) {
513 return readTokenAllowForwardSlash(bb);
514 } else {
515 return readToken(bb);
516 }
517 }
518 }
519
520
521 private static ByteBuffer readToken(ByteBuffer bb) {
522 final int start = bb.position();
523 int end = bb.limit();
524 while (bb.hasRemaining()) {
525 if (!HttpParser.isToken(bb.get())) {
526 end = bb.position() - 1;
527 bb.position(end);
528 break;
529 }
530 }
531
532 return new ByteBuffer(bb.bytes, start, end - start);
533 }
534
535
536 private static ByteBuffer readTokenAllowForwardSlash(ByteBuffer bb) {
537 final int start = bb.position();
538 int end = bb.limit();
539 while (bb.hasRemaining()) {
540 byte b = bb.get();
541 if (b != FORWARDSLASH_BYTE && !HttpParser.isToken(b)) {
542 end = bb.position() - 1;
543 bb.position(end);
544 break;
545 }
546 }
547
548 return new ByteBuffer(bb.bytes, start, end - start);
549 }
550
551
552 private static ByteBuffer readQuotedString(ByteBuffer bb) {
553 int start = bb.position();
554
555
556 bb.get();
557 boolean escaped = false;
558 while (bb.hasRemaining()) {
559 byte b = bb.get();
560 if (b == SLASH_BYTE) {
561
562 escaped = true;
563 } else if (escaped && b > (byte) -1) {
564 escaped = false;
565 } else if (b == QUOTE_BYTE) {
566 return new ByteBuffer(bb.bytes, start, bb.position() - start);
567 } else if (isText[b & 0xFF]) {
568 escaped = false;
569 } else {
570 return null;
571 }
572 }
573
574 return null;
575 }
576
577
578 private static void logInvalidHeader(ByteBuffer bb) {
579 UserDataHelper.Mode logMode = invalidCookieLog.getNextMode();
580 if (logMode != null) {
581 String headerValue = new String(bb.array(), bb.position(), bb.limit() - bb.position(),
582 StandardCharsets.UTF_8);
583 String message = sm.getString("cookie.invalidCookieValue", headerValue);
584 switch (logMode) {
585 case INFO_THEN_DEBUG:
586 message += sm.getString("cookie.fallToDebug");
587
588 case INFO:
589 log.info(message);
590 break;
591 case DEBUG:
592 log.debug(message);
593 }
594 }
595 }
596
597
598 private static void logInvalidVersion(ByteBuffer value) {
599 UserDataHelper.Mode logMode = invalidCookieVersionLog.getNextMode();
600 if (logMode != null) {
601 String version;
602 if (value == null) {
603 version = sm.getString("cookie.valueNotPresent");
604 } else {
605 version = new String(value.bytes, value.position(),
606 value.limit() - value.position(), StandardCharsets.UTF_8);
607 }
608 String message = sm.getString("cookie.invalidCookieVersion", version);
609 switch (logMode) {
610 case INFO_THEN_DEBUG:
611 message += sm.getString("cookie.fallToDebug");
612
613 case INFO:
614 log.info(message);
615 break;
616 case DEBUG:
617 log.debug(message);
618 }
619 }
620 }
621
622
623
627 private static class ByteBuffer {
628
629 private final byte[] bytes;
630 private int limit;
631 private int position = 0;
632
633 public ByteBuffer(byte[] bytes, int offset, int len) {
634 this.bytes = bytes;
635 this.position = offset;
636 this.limit = offset + len;
637 }
638
639 public int position() {
640 return position;
641 }
642
643 public void position(int position) {
644 this.position = position;
645 }
646
647 public int limit() {
648 return limit;
649 }
650
651 public int remaining() {
652 return limit - position;
653 }
654
655 public boolean hasRemaining() {
656 return position < limit;
657 }
658
659 public byte get() {
660 return bytes[position++];
661 }
662
663 public byte peek() {
664 return bytes[position];
665 }
666
667 public void rewind() {
668 position--;
669 }
670
671 public byte[] array() {
672 return bytes;
673 }
674
675
676 @Override
677 public String toString() {
678 return "position [" + position + "], limit [" + limit + "]";
679 }
680 }
681 }
682