V4.1 Fix "=" character in HttpPostRequestDecoder

Motivation
Issue #3004 shows that "=" character was not supported as it should in
the HttpPostRequestDecoder in form-data boundary.

Modifications:
Add 2 methods in StringUtil
- split with maxPart argument: String split with max parts only (to prevent multiple '='
to be source of extra split while not needed)
- substringAfter: String part after delimiter (since first part is not
needed)
Use those methods in HttpPostRequestDecoder.
Change and the HttpPostRequestDecoderTest to check using a boundary
beginning with "=".

Results:
The fix implies more stability and fix the issue.
This commit is contained in:
Frederic Bregier 2014-10-15 23:47:27 +02:00 committed by Trustin Lee
parent a1af35313c
commit eb415fded6
4 changed files with 82 additions and 14 deletions

View File

@ -672,7 +672,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
if (checkSecondArg) { if (checkSecondArg) {
// read next values and store them in the map as Attribute // read next values and store them in the map as Attribute
for (int i = 2; i < contents.length; i++) { for (int i = 2; i < contents.length; i++) {
String[] values = StringUtil.split(contents[i], '='); String[] values = StringUtil.split(contents[i], '=', 2);
Attribute attribute; Attribute attribute;
try { try {
String name = cleanString(values[0]); String name = cleanString(values[0]);
@ -721,8 +721,8 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
// Take care of possible "multipart/mixed" // Take care of possible "multipart/mixed"
if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) { if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) {
if (currentStatus == MultiPartStatus.DISPOSITION) { if (currentStatus == MultiPartStatus.DISPOSITION) {
String[] values = StringUtil.split(contents[2], '='); String values = StringUtil.substringAfter(contents[2], '=');
multipartMixedBoundary = "--" + values[1]; multipartMixedBoundary = "--" + values;
currentStatus = MultiPartStatus.MIXEDDELIMITER; currentStatus = MultiPartStatus.MIXEDDELIMITER;
return decodeMultipart(MultiPartStatus.MIXEDDELIMITER); return decodeMultipart(MultiPartStatus.MIXEDDELIMITER);
} else { } else {
@ -731,11 +731,11 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest
} else { } else {
for (int i = 1; i < contents.length; i++) { for (int i = 1; i < contents.length; i++) {
if (contents[i].toLowerCase().startsWith(HttpHeaders.Values.CHARSET)) { if (contents[i].toLowerCase().startsWith(HttpHeaders.Values.CHARSET)) {
String[] values = StringUtil.split(contents[i], '='); String values = StringUtil.substringAfter(contents[i], '=');
Attribute attribute; Attribute attribute;
try { try {
attribute = factory.createAttribute(request, HttpHeaders.Values.CHARSET, attribute = factory.createAttribute(request, HttpHeaders.Values.CHARSET,
cleanString(values[1])); cleanString(values));
} catch (NullPointerException e) { } catch (NullPointerException e) {
throw new ErrorDataDecoderException(e); throw new ErrorDataDecoderException(e);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {

View File

@ -169,25 +169,25 @@ public class HttpPostRequestDecoder implements InterfaceHttpPostRequestDecoder {
} else { } else {
return null; return null;
} }
String[] boundary = StringUtil.split(headerContentType[mrank], '='); String boundary = StringUtil.substringAfter(headerContentType[mrank], '=');
if (boundary.length != 2) { if (boundary == null) {
throw new ErrorDataDecoderException("Needs a boundary value"); throw new ErrorDataDecoderException("Needs a boundary value");
} }
if (boundary[1].charAt(0) == '"') { if (boundary.charAt(0) == '"') {
String bound = boundary[1].trim(); String bound = boundary.trim();
int index = bound.length() - 1; int index = bound.length() - 1;
if (bound.charAt(index) == '"') { if (bound.charAt(index) == '"') {
boundary[1] = bound.substring(1, index); boundary = bound.substring(1, index);
} }
} }
if (headerContentType[crank].toLowerCase().startsWith( if (headerContentType[crank].toLowerCase().startsWith(
HttpHeaders.Values.CHARSET)) { HttpHeaders.Values.CHARSET)) {
String[] charset = StringUtil.split(headerContentType[crank], '='); String charset = StringUtil.substringAfter(headerContentType[crank], '=');
if (charset.length > 1) { if (charset != null) {
return new String[] {"--" + boundary[1], charset[1]}; return new String[] {"--" + boundary, charset};
} }
} }
return new String[] {"--" + boundary[1]}; return new String[] {"--" + boundary};
} }
return null; return null;
} }

View File

@ -111,6 +111,63 @@ public final class StringUtil {
return res.toArray(new String[res.size()]); return res.toArray(new String[res.size()]);
} }
/**
* Splits the specified {@link String} with the specified delimiter in maxParts maximum parts.
* This operation is a simplified and optimized
* version of {@link String#split(String, int)}.
*/
public static String[] split(String value, char delim, int maxParts) {
final int end = value.length();
final List<String> res = new ArrayList<String>();
int start = 0;
int cpt = 1;
for (int i = 0; i < end && cpt < maxParts; i ++) {
if (value.charAt(i) == delim) {
if (start == i) {
res.add(EMPTY_STRING);
} else {
res.add(value.substring(start, i));
}
start = i + 1;
cpt++;
}
}
if (start == 0) { // If no delimiter was found in the value
res.add(value);
} else {
if (start != end) {
// Add the last element if it's not empty.
res.add(value.substring(start, end));
} else {
// Truncate trailing empty elements.
for (int i = res.size() - 1; i >= 0; i --) {
if (res.get(i).isEmpty()) {
res.remove(i);
} else {
break;
}
}
}
}
return res.toArray(new String[res.size()]);
}
/**
* Get the item after one char delim if the delim is found (else null).
* This operation is a simplified and optimized
* version of {@link String#split(String, int)}.
*/
public static String substringAfter(String value, char delim) {
int pos = value.indexOf(delim);
if (pos >= 0) {
return value.substring(pos + 1);
}
return null;
}
/** /**
* Converts the specified byte value into a 2-digit hexadecimal integer. * Converts the specified byte value into a 2-digit hexadecimal integer.
*/ */

View File

@ -70,4 +70,15 @@ public class StringUtilTest {
public void splitWithDelimiterAtBeginning() { public void splitWithDelimiterAtBeginning() {
assertArrayEquals(new String[] { "", "foo", "bar" }, split("#foo#bar", '#')); assertArrayEquals(new String[] { "", "foo", "bar" }, split("#foo#bar", '#'));
} }
@Test
public void splitMaxPart() {
assertArrayEquals(new String[] { "foo", "bar:bar2" }, split("foo:bar:bar2", ':', 2));
assertArrayEquals(new String[] { "foo", "bar", "bar2" }, split("foo:bar:bar2", ':', 3));
}
@Test
public void substringAfterTest() {
assertEquals("bar:bar2", substringAfter("foo:bar:bar2", ':'));
}
} }