Fix #218: CookieDecoder.decode() throws StackOverflowError
- Rewrote key-value decoder not using a regular expression
This commit is contained in:
parent
ee8091f6fb
commit
7b2992a95a
@ -21,8 +21,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes an HTTP header value into {@link Cookie}s. This decoder can decode
|
* Decodes an HTTP header value into {@link Cookie}s. This decoder can decode
|
||||||
@ -41,12 +39,6 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public class CookieDecoder {
|
public class CookieDecoder {
|
||||||
|
|
||||||
private static final Pattern PATTERN = Pattern.compile(
|
|
||||||
// See: https://github.com/netty/netty/pull/96
|
|
||||||
//"(?:\\s|[;,])*\\$*([^;=]+)(?:=(?:[\"']((?:\\\\.|[^\"])*)[\"']|([^;,]*)))?(\\s*(?:[;,]+\\s*|$))"
|
|
||||||
"(?:\\s|[;,])*\\$*([^;=]+)(?:=(?:[\"']((?:\\\\.|[^\"])*)[\"']|([^;]*)))?(\\s*(?:[;,]+\\s*|$))"
|
|
||||||
);
|
|
||||||
|
|
||||||
private static final String COMMA = ",";
|
private static final String COMMA = ",";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -175,58 +167,126 @@ public class CookieDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void extractKeyValuePairs(
|
private static void extractKeyValuePairs(
|
||||||
String header, List<String> names, List<String> values) {
|
final String header, final List<String> names, final List<String> values) {
|
||||||
Matcher m = PATTERN.matcher(header);
|
|
||||||
int pos = 0;
|
|
||||||
String name = null;
|
|
||||||
String value = null;
|
|
||||||
String separator = null;
|
|
||||||
while (m.find(pos)) {
|
|
||||||
pos = m.end();
|
|
||||||
|
|
||||||
// Extract name and value pair from the match.
|
final int headerLen = header.length();
|
||||||
String newName = m.group(1);
|
loop: for (int i = 0;;) {
|
||||||
String newValue = m.group(3);
|
|
||||||
if (newValue == null) {
|
|
||||||
newValue = decodeValue(m.group(2));
|
|
||||||
}
|
|
||||||
String newSeparator = m.group(4);
|
|
||||||
|
|
||||||
if (name == null) {
|
// Skip spaces and separators.
|
||||||
name = newName;
|
for (;;) {
|
||||||
value = newValue == null? "" : newValue;
|
if (i == headerLen) {
|
||||||
separator = newSeparator;
|
break loop;
|
||||||
continue;
|
}
|
||||||
|
switch (header.charAt(i)) {
|
||||||
|
case '\t': case '\n': case 0x0b: case '\f': case '\r':
|
||||||
|
case ' ': case ',': case ';':
|
||||||
|
i ++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue == null &&
|
// Skip '$'.
|
||||||
!CookieHeaderNames.DISCARD.equalsIgnoreCase(newName) &&
|
for (;;) {
|
||||||
!CookieHeaderNames.SECURE.equalsIgnoreCase(newName) &&
|
if (i == headerLen) {
|
||||||
!CookieHeaderNames.HTTPONLY.equalsIgnoreCase(newName)) {
|
break loop;
|
||||||
value = value + separator + newName;
|
}
|
||||||
separator = newSeparator;
|
if (header.charAt(i) == '$') {
|
||||||
continue;
|
i ++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String value;
|
||||||
|
|
||||||
|
if (i == headerLen) {
|
||||||
|
name = null;
|
||||||
|
value = null;
|
||||||
|
} else {
|
||||||
|
int newNameStart = i;
|
||||||
|
keyValLoop: for (;;) {
|
||||||
|
switch (header.charAt(i)) {
|
||||||
|
case ';':
|
||||||
|
// NAME; (no value till ';')
|
||||||
|
name = header.substring(newNameStart, i);
|
||||||
|
value = null;
|
||||||
|
break keyValLoop;
|
||||||
|
case '=':
|
||||||
|
// NAME=VALUE
|
||||||
|
name = header.substring(newNameStart, i);
|
||||||
|
i ++;
|
||||||
|
if (i == headerLen) {
|
||||||
|
// NAME= (empty value, i.e. nothing after '=')
|
||||||
|
value = "";
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newValueStart = i;
|
||||||
|
char c = header.charAt(i);
|
||||||
|
if (c == '"' || c == '\'') {
|
||||||
|
// NAME="VALUE" or NAME='VALUE'
|
||||||
|
StringBuilder newValueBuf = new StringBuilder(header.length() - i);
|
||||||
|
final char q = c;
|
||||||
|
boolean hadBackslash = false;
|
||||||
|
i ++;
|
||||||
|
for (;;) {
|
||||||
|
if (i == headerLen) {
|
||||||
|
value = newValueBuf.toString();
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
if (hadBackslash) {
|
||||||
|
hadBackslash = false;
|
||||||
|
c = header.charAt(i ++);
|
||||||
|
switch (c) {
|
||||||
|
case '\\': case '"': case '\'':
|
||||||
|
// Escape last backslash.
|
||||||
|
newValueBuf.setCharAt(newValueBuf.length() - 1, c);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Do not escape last backslash.
|
||||||
|
newValueBuf.append(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = header.charAt(i ++);
|
||||||
|
if (c == q) {
|
||||||
|
value = newValueBuf.toString();
|
||||||
|
break keyValLoop;
|
||||||
|
}
|
||||||
|
newValueBuf.append(c);
|
||||||
|
if (c == '\\') {
|
||||||
|
hadBackslash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// NAME=VALUE;
|
||||||
|
int semiPos = header.indexOf(';', i);
|
||||||
|
if (semiPos > 0) {
|
||||||
|
value = header.substring(newValueStart, semiPos);
|
||||||
|
i = semiPos;
|
||||||
|
} else {
|
||||||
|
value = header.substring(newValueStart);
|
||||||
|
i = headerLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break keyValLoop;
|
||||||
|
default:
|
||||||
|
i ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == headerLen) {
|
||||||
|
// NAME (no value till the end of string)
|
||||||
|
name = header.substring(newNameStart);
|
||||||
|
value = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
names.add(name);
|
names.add(name);
|
||||||
values.add(value);
|
values.add(value);
|
||||||
|
|
||||||
name = newName;
|
|
||||||
value = newValue;
|
|
||||||
separator = newSeparator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The last entry
|
|
||||||
if (name != null) {
|
|
||||||
names.add(name);
|
|
||||||
values.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String decodeValue(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return value.replace("\\\"", "\"").replace("\\\\", "\\");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,7 +271,9 @@ public class CookieDecoderTest {
|
|||||||
"d=\"1\\\"2\\\"3\"," +
|
"d=\"1\\\"2\\\"3\"," +
|
||||||
"e=\"\\\"\\\"\"," +
|
"e=\"\\\"\\\"\"," +
|
||||||
"f=\"1\\\"\\\"2\"," +
|
"f=\"1\\\"\\\"2\"," +
|
||||||
"g=\"\\\\\"";
|
"g=\"\\\\\"," +
|
||||||
|
"h=\"';,\\x\"";
|
||||||
|
|
||||||
|
|
||||||
Set<Cookie> cookies = new CookieDecoder().decode(source);
|
Set<Cookie> cookies = new CookieDecoder().decode(source);
|
||||||
Iterator<Cookie> it = cookies.iterator();
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
@ -305,6 +307,10 @@ public class CookieDecoderTest {
|
|||||||
assertEquals("g", c.getName());
|
assertEquals("g", c.getName());
|
||||||
assertEquals("\\", c.getValue());
|
assertEquals("\\", c.getValue());
|
||||||
|
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("h", c.getName());
|
||||||
|
assertEquals("';,\\x", c.getValue());
|
||||||
|
|
||||||
assertFalse(it.hasNext());
|
assertFalse(it.hasNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -390,4 +396,59 @@ public class CookieDecoderTest {
|
|||||||
assertEquals("HTTPOnly", c.getName());
|
assertEquals("HTTPOnly", c.getName());
|
||||||
assertEquals("", c.getValue());
|
assertEquals("", c.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodingLongValue() {
|
||||||
|
String longValue =
|
||||||
|
"b!!!$Q!!$ha!!<NC=MN(F!!%#4!!<NC=MN(F!!2!d!!!!#=IvZB!!2,F!!!!'=KqtH!!2-9!!!!" +
|
||||||
|
"'=IvZM!!3f:!!!!$=HbQW!!3g'!!!!%=J^wI!!3g-!!!!%=J^wI!!3g1!!!!$=HbQW!!3g2!!!!" +
|
||||||
|
"$=HbQW!!3g5!!!!%=J^wI!!3g9!!!!$=HbQW!!3gT!!!!$=HbQW!!3gX!!!!#=J^wI!!3gY!!!!" +
|
||||||
|
"#=J^wI!!3gh!!!!$=HbQW!!3gj!!!!$=HbQW!!3gr!!!!$=HbQW!!3gx!!!!#=J^wI!!3h!!!!!" +
|
||||||
|
"$=HbQW!!3h$!!!!#=J^wI!!3h'!!!!$=HbQW!!3h,!!!!$=HbQW!!3h0!!!!%=J^wI!!3h1!!!!" +
|
||||||
|
"#=J^wI!!3h2!!!!$=HbQW!!3h4!!!!$=HbQW!!3h7!!!!$=HbQW!!3h8!!!!%=J^wI!!3h:!!!!" +
|
||||||
|
"#=J^wI!!3h@!!!!%=J^wI!!3hB!!!!$=HbQW!!3hC!!!!$=HbQW!!3hL!!!!$=HbQW!!3hQ!!!!" +
|
||||||
|
"$=HbQW!!3hS!!!!%=J^wI!!3hU!!!!$=HbQW!!3h[!!!!$=HbQW!!3h^!!!!$=HbQW!!3hd!!!!" +
|
||||||
|
"%=J^wI!!3he!!!!%=J^wI!!3hf!!!!%=J^wI!!3hg!!!!$=HbQW!!3hh!!!!%=J^wI!!3hi!!!!" +
|
||||||
|
"%=J^wI!!3hv!!!!$=HbQW!!3i/!!!!#=J^wI!!3i2!!!!#=J^wI!!3i3!!!!%=J^wI!!3i4!!!!" +
|
||||||
|
"$=HbQW!!3i7!!!!$=HbQW!!3i8!!!!$=HbQW!!3i9!!!!%=J^wI!!3i=!!!!#=J^wI!!3i>!!!!" +
|
||||||
|
"%=J^wI!!3iD!!!!$=HbQW!!3iF!!!!#=J^wI!!3iH!!!!%=J^wI!!3iM!!!!%=J^wI!!3iS!!!!" +
|
||||||
|
"#=J^wI!!3iU!!!!%=J^wI!!3iZ!!!!#=J^wI!!3i]!!!!%=J^wI!!3ig!!!!%=J^wI!!3ij!!!!" +
|
||||||
|
"%=J^wI!!3ik!!!!#=J^wI!!3il!!!!$=HbQW!!3in!!!!%=J^wI!!3ip!!!!$=HbQW!!3iq!!!!" +
|
||||||
|
"$=HbQW!!3it!!!!%=J^wI!!3ix!!!!#=J^wI!!3j!!!!!$=HbQW!!3j%!!!!$=HbQW!!3j'!!!!" +
|
||||||
|
"%=J^wI!!3j(!!!!%=J^wI!!9mJ!!!!'=KqtH!!=SE!!<NC=MN(F!!?VS!!<NC=MN(F!!Zw`!!!!" +
|
||||||
|
"%=KqtH!!j+C!!<NC=MN(F!!j+M!!<NC=MN(F!!j+a!!<NC=MN(F!!j,.!!<NC=MN(F!!n>M!!!!" +
|
||||||
|
"'=KqtH!!s1X!!!!$=MMyc!!s1_!!!!#=MN#O!!ypn!!!!'=KqtH!!ypr!!!!'=KqtH!#%h!!!!!" +
|
||||||
|
"%=KqtH!#%o!!!!!'=KqtH!#)H6!!<NC=MN(F!#*%'!!!!%=KqtH!#+k(!!!!'=KqtH!#-E!!!!!" +
|
||||||
|
"'=KqtH!#1)w!!!!'=KqtH!#1)y!!!!'=KqtH!#1*M!!!!#=KqtH!#1*p!!!!'=KqtH!#14Q!!<N" +
|
||||||
|
"C=MN(F!#14S!!<NC=MN(F!#16I!!<NC=MN(F!#16N!!<NC=MN(F!#16X!!<NC=MN(F!#16k!!<N" +
|
||||||
|
"C=MN(F!#17@!!<NC=MN(F!#17A!!<NC=MN(F!#1Cq!!!!'=KqtH!#7),!!!!#=KqtH!#7)b!!!!" +
|
||||||
|
"#=KqtH!#7Ww!!!!'=KqtH!#?cQ!!!!'=KqtH!#His!!!!'=KqtH!#Jrh!!!!'=KqtH!#O@M!!<N" +
|
||||||
|
"C=MN(F!#O@O!!<NC=MN(F!#OC6!!<NC=MN(F!#Os.!!!!#=KqtH!#YOW!!!!#=H/Li!#Zat!!!!" +
|
||||||
|
"'=KqtH!#ZbI!!!!%=KqtH!#Zbc!!!!'=KqtH!#Zbs!!!!%=KqtH!#Zby!!!!'=KqtH!#Zce!!!!" +
|
||||||
|
"'=KqtH!#Zdc!!!!%=KqtH!#Zea!!!!'=KqtH!#ZhI!!!!#=KqtH!#ZiD!!!!'=KqtH!#Zis!!!!" +
|
||||||
|
"'=KqtH!#Zj0!!!!#=KqtH!#Zj1!!!!'=KqtH!#Zj[!!!!'=KqtH!#Zj]!!!!'=KqtH!#Zj^!!!!" +
|
||||||
|
"'=KqtH!#Zjb!!!!'=KqtH!#Zk!!!!!'=KqtH!#Zk6!!!!#=KqtH!#Zk9!!!!%=KqtH!#Zk<!!!!" +
|
||||||
|
"'=KqtH!#Zl>!!!!'=KqtH!#]9R!!!!$=H/Lt!#]I6!!!!#=KqtH!#]Z#!!!!%=KqtH!#^*N!!!!" +
|
||||||
|
"#=KqtH!#^:m!!!!#=KqtH!#_*_!!!!%=J^wI!#`-7!!!!#=KqtH!#`T>!!!!'=KqtH!#`T?!!!!" +
|
||||||
|
"'=KqtH!#`TA!!!!'=KqtH!#`TB!!!!'=KqtH!#`TG!!!!'=KqtH!#`TP!!!!#=KqtH!#`U,!!!!" +
|
||||||
|
"'=KqtH!#`U/!!!!'=KqtH!#`U0!!!!#=KqtH!#`U9!!!!'=KqtH!#aEQ!!!!%=KqtH!#b<)!!!!" +
|
||||||
|
"'=KqtH!#c9-!!!!%=KqtH!#dxC!!!!%=KqtH!#dxE!!!!%=KqtH!#ev$!!!!'=KqtH!#fBi!!!!" +
|
||||||
|
"#=KqtH!#fBj!!!!'=KqtH!#fG)!!!!'=KqtH!#fG+!!!!'=KqtH!#g<d!!!!'=KqtH!#g<e!!!!" +
|
||||||
|
"'=KqtH!#g=J!!!!'=KqtH!#gat!!!!#=KqtH!#s`D!!!!#=J_#p!#sg?!!!!#=J_#p!#t<a!!!!" +
|
||||||
|
"#=KqtH!#t<c!!!!#=KqtH!#trY!!!!$=JiYj!#vA$!!!!'=KqtH!#xs_!!!!'=KqtH!$$rO!!!!" +
|
||||||
|
"#=KqtH!$$rP!!!!#=KqtH!$(!%!!!!'=KqtH!$)]o!!!!%=KqtH!$,@)!!!!'=KqtH!$,k]!!!!" +
|
||||||
|
"'=KqtH!$1]+!!!!%=KqtH!$3IO!!!!%=KqtH!$3J#!!!!'=KqtH!$3J.!!!!'=KqtH!$3J:!!!!" +
|
||||||
|
"#=KqtH!$3JH!!!!#=KqtH!$3JI!!!!#=KqtH!$3JK!!!!%=KqtH!$3JL!!!!'=KqtH!$3JS!!!!" +
|
||||||
|
"'=KqtH!$8+M!!!!#=KqtH!$99d!!!!%=KqtH!$:Lw!!!!#=LK+x!$:N@!!!!#=KqtG!$:NC!!!!" +
|
||||||
|
"#=KqtG!$:hW!!!!'=KqtH!$:i[!!!!'=KqtH!$:ih!!!!'=KqtH!$:it!!!!'=KqtH!$:kO!!!!" +
|
||||||
|
"'=KqtH!$>*B!!!!'=KqtH!$>hD!!!!+=J^x0!$?lW!!!!'=KqtH!$?ll!!!!'=KqtH!$?lm!!!!" +
|
||||||
|
"%=KqtH!$?mi!!!!'=KqtH!$?mx!!!!'=KqtH!$D7]!!!!#=J_#p!$D@T!!!!#=J_#p!$V<g!!!!" +
|
||||||
|
"'=KqtH";
|
||||||
|
|
||||||
|
Set<Cookie> cookies = new CookieDecoder().decode("bh=\"" + longValue + "\";");
|
||||||
|
assertEquals(1, cookies.size());
|
||||||
|
Cookie c = cookies.iterator().next();
|
||||||
|
assertEquals("bh", c.getName());
|
||||||
|
assertEquals(longValue, c.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user