Issue #5093 Static UrlEncoded (#5098)

* Issue #5093 Static UrlEncoded

Updated UrlEncoded to static only class with no synchronization

* Fixed additional tests

* fixed formatting

Signed-off-by: gregw <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2020-07-30 17:57:38 +02:00 committed by GitHub
parent aa3bd243b4
commit 7adbf247ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 399 additions and 504 deletions

View File

@ -57,6 +57,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.util.UrlEncoded;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -834,14 +835,14 @@ public class DispatcherTest
assertEquals(null, request.getPathInfo()); assertEquals(null, request.getPathInfo());
assertEquals(null, request.getPathTranslated()); assertEquals(null, request.getPathTranslated());
UrlEncoded query = new UrlEncoded(); MultiMap<String> query = new MultiMap<>();
query.decode(request.getQueryString()); UrlEncoded.decodeTo(request.getQueryString(), query, UrlEncoded.ENCODING);
assertThat(query.getString("do"), is("end")); assertThat(query.getString("do"), is("end"));
// Russian for "selected=Temperature" // Russian for "selected=Temperature"
UrlEncoded q2 = new UrlEncoded(); MultiMap<String> q2 = new MultiMap<>();
q2.decode(query.getString("else")); UrlEncoded.decodeTo(query.getString("else"), q2, UrlEncoded.ENCODING);
String russian = q2.encode(); String russian = UrlEncoded.encode(q2, UrlEncoded.ENCODING, false);
assertThat(russian, is("%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BE=%D0%A2%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D1%83%D1%80%D0%B0")); assertThat(russian, is("%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BE=%D0%A2%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D1%83%D1%80%D0%B0"));
assertThat(query.containsKey("test"), is(false)); assertThat(query.containsKey("test"), is(false));
assertThat(query.containsKey("foreign"), is(false)); assertThat(query.containsKey("foreign"), is(false));

View File

@ -44,19 +44,11 @@ import static org.eclipse.jetty.util.TypeUtil.convertHexDigit;
* passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset" * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset"
* System property. * System property.
* </p> * </p>
* <p>
* The hashtable either contains String single values, vectors
* of String or arrays of Strings.
* </p>
* <p>
* This class is only partially synchronised. In particular, simple
* get operations are not protected from concurrent updates.
* </p>
* *
* @see java.net.URLEncoder * @see java.net.URLEncoder
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class UrlEncoded extends MultiMap<String> implements Cloneable public class UrlEncoded
{ {
static final Logger LOG = LoggerFactory.getLogger(UrlEncoded.class); static final Logger LOG = LoggerFactory.getLogger(UrlEncoded.class);
@ -87,62 +79,8 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
ENCODING = encoding; ENCODING = encoding;
} }
public UrlEncoded(UrlEncoded url) private UrlEncoded()
{ {
super(url);
}
public UrlEncoded()
{
}
public UrlEncoded(String query)
{
decodeTo(query, this, ENCODING);
}
public void decode(String query)
{
decodeTo(query, this, ENCODING);
}
public void decode(String query, Charset charset)
{
decodeTo(query, this, charset);
}
/**
* Encode MultiMap with % encoding for UTF8 sequences.
*
* @return the MultiMap as a string with % encoding
*/
public String encode()
{
return encode(ENCODING, false);
}
/**
* Encode MultiMap with % encoding for arbitrary Charset sequences.
*
* @param charset the charset to use for encoding
* @return the MultiMap as a string encoded with % encodings
*/
public String encode(Charset charset)
{
return encode(charset, false);
}
/**
* Encode MultiMap with % encoding.
*
* @param charset the charset to encode with
* @param equalsForNullValue if True, then an '=' is always used, even
* for parameters without a value. e.g. <code>"blah?a=&amp;b=&amp;c="</code>.
* @return the MultiMap as a string encoded with % encodings
*/
public synchronized String encode(Charset charset, boolean equalsForNullValue)
{
return encode(this, charset, equalsForNullValue);
} }
/** /**
@ -190,11 +128,10 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
if (val != null) if (val != null)
{ {
String str = val; if (val.length() > 0)
if (str.length() > 0)
{ {
result.append('='); result.append('=');
result.append(encodeString(str, charset)); result.append(encodeString(val, charset));
} }
else if (equalsForNullValue) else if (equalsForNullValue)
result.append('='); result.append('=');
@ -228,6 +165,18 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
* @param charset the charset to use for decoding * @param charset the charset to use for decoding
*/ */
public static void decodeTo(String content, MultiMap<String> map, Charset charset) public static void decodeTo(String content, MultiMap<String> map, Charset charset)
{
decodeTo(content, map, charset, -1);
}
/**
* Decoded parameters to Map.
*
* @param content the string containing the encoded parameters
* @param map the MultiMap to put parsed query parameters into
* @param charset the charset to use for decoding
*/
public static void decodeTo(String content, MultiMap<String> map, Charset charset, int maxKeys)
{ {
if (charset == null) if (charset == null)
charset = ENCODING; charset = ENCODING;
@ -238,64 +187,62 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
return; return;
} }
synchronized (map) String key = null;
String value;
int mark = -1;
boolean encoded = false;
for (int i = 0; i < content.length(); i++)
{ {
String key = null; char c = content.charAt(i);
String value; switch (c)
int mark = -1;
boolean encoded = false;
for (int i = 0; i < content.length(); i++)
{ {
char c = content.charAt(i); case '&':
switch (c) int l = i - mark - 1;
{ value = l == 0 ? "" : (encoded ? decodeString(content, mark + 1, l, charset) : content.substring(mark + 1, i));
case '&': mark = i;
int l = i - mark - 1; encoded = false;
value = l == 0 ? "" : (encoded ? decodeString(content, mark + 1, l, charset) : content.substring(mark + 1, i)); if (key != null)
mark = i; {
encoded = false; map.add(key, value);
if (key != null) }
{ else if (value != null && value.length() > 0)
map.add(key, value); {
} map.add(value, "");
else if (value != null && value.length() > 0) }
{ checkMaxKeys(map, maxKeys);
map.add(value, ""); key = null;
} value = null;
key = null; break;
value = null; case '=':
if (key != null)
break; break;
case '=': key = encoded ? decodeString(content, mark + 1, i - mark - 1, charset) : content.substring(mark + 1, i);
if (key != null) mark = i;
break; encoded = false;
key = encoded ? decodeString(content, mark + 1, i - mark - 1, charset) : content.substring(mark + 1, i); break;
mark = i; case '+':
encoded = false; case '%':
break; encoded = true;
case '+': break;
encoded = true;
break;
case '%':
encoded = true;
break;
}
} }
}
if (key != null) if (key != null)
{
int l = content.length() - mark - 1;
value = l == 0 ? "" : (encoded ? decodeString(content, mark + 1, l, charset) : content.substring(mark + 1));
map.add(key, value);
checkMaxKeys(map, maxKeys);
}
else if (mark < content.length())
{
key = encoded
? decodeString(content, mark + 1, content.length() - mark - 1, charset)
: content.substring(mark + 1);
if (key != null && key.length() > 0)
{ {
int l = content.length() - mark - 1; map.add(key, "");
value = l == 0 ? "" : (encoded ? decodeString(content, mark + 1, l, charset) : content.substring(mark + 1)); checkMaxKeys(map, maxKeys);
map.add(key, value);
}
else if (mark < content.length())
{
key = encoded
? decodeString(content, mark + 1, content.length() - mark - 1, charset)
: content.substring(mark + 1);
if (key != null && key.length() > 0)
{
map.add(key, "");
}
} }
} }
} }
@ -316,76 +263,73 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
public static void decodeUtf8To(String query, int offset, int length, MultiMap<String> map) public static void decodeUtf8To(String query, int offset, int length, MultiMap<String> map)
{ {
Utf8StringBuilder buffer = new Utf8StringBuilder(); Utf8StringBuilder buffer = new Utf8StringBuilder();
synchronized (map) String key = null;
String value;
int end = offset + length;
for (int i = offset; i < end; i++)
{ {
String key = null; char c = query.charAt(i);
String value = null; switch (c)
int end = offset + length;
for (int i = offset; i < end; i++)
{ {
char c = query.charAt(i); case '&':
switch (c) value = buffer.toReplacedString();
{ buffer.reset();
case '&': if (key != null)
value = buffer.toReplacedString(); {
buffer.reset(); map.add(key, value);
if (key != null) }
{ else if (value != null && value.length() > 0)
map.add(key, value); {
} map.add(value, "");
else if (value != null && value.length() > 0) }
{ key = null;
map.add(value, ""); break;
}
key = null;
value = null;
break;
case '=': case '=':
if (key != null) if (key != null)
{ {
buffer.append(c);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
if (i + 2 < end)
{
char hi = query.charAt(++i);
char lo = query.charAt(++i);
buffer.append(decodeHexByte(hi, lo));
}
else
{
throw new Utf8Appendable.NotUtf8Exception("Incomplete % encoding");
}
break;
default:
buffer.append(c); buffer.append(c);
break; break;
} }
} key = buffer.toReplacedString();
buffer.reset();
break;
if (key != null) case '+':
{ buffer.append((byte)' ');
value = buffer.toReplacedString(); break;
buffer.reset();
map.add(key, value); case '%':
} if (i + 2 < end)
else if (buffer.length() > 0) {
{ char hi = query.charAt(++i);
map.add(buffer.toReplacedString(), ""); char lo = query.charAt(++i);
buffer.append(decodeHexByte(hi, lo));
}
else
{
throw new Utf8Appendable.NotUtf8Exception("Incomplete % encoding");
}
break;
default:
buffer.append(c);
break;
} }
} }
if (key != null)
{
value = buffer.toReplacedString();
buffer.reset();
map.add(key, value);
}
else if (buffer.length() > 0)
{
map.add(buffer.toReplacedString(), "");
}
} }
/** /**
@ -400,74 +344,70 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) public static void decode88591To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
throws IOException throws IOException
{ {
synchronized (map) StringBuilder buffer = new StringBuilder();
String key = null;
String value;
int b;
int totalLength = 0;
while ((b = in.read()) >= 0)
{ {
StringBuilder buffer = new StringBuilder(); switch ((char)b)
String key = null;
String value = null;
int b;
int totalLength = 0;
while ((b = in.read()) >= 0)
{ {
switch ((char)b) case '&':
{ value = buffer.length() == 0 ? "" : buffer.toString();
case '&': buffer.setLength(0);
value = buffer.length() == 0 ? "" : buffer.toString(); if (key != null)
buffer.setLength(0); {
if (key != null) map.add(key, value);
{ }
map.add(key, value); else if (value.length() > 0)
} {
else if (value.length() > 0) map.add(value, "");
{ }
map.add(value, ""); key = null;
} checkMaxKeys(map, maxKeys);
key = null; break;
value = null;
checkMaxKeys(map, maxKeys);
break;
case '=': case '=':
if (key != null) if (key != null)
{ {
buffer.append((char)b);
break;
}
key = buffer.toString();
buffer.setLength(0);
break;
case '+':
buffer.append(' ');
break;
case '%':
int code0 = in.read();
int code1 = in.read();
buffer.append(decodeHexChar(code0, code1));
break;
default:
buffer.append((char)b); buffer.append((char)b);
break; break;
} }
checkMaxLength(++totalLength, maxLength); key = buffer.toString();
} buffer.setLength(0);
break;
if (key != null) case '+':
{ buffer.append(' ');
value = buffer.length() == 0 ? "" : buffer.toString(); break;
buffer.setLength(0);
map.add(key, value); case '%':
int code0 = in.read();
int code1 = in.read();
buffer.append(decodeHexChar(code0, code1));
break;
default:
buffer.append((char)b);
break;
} }
else if (buffer.length() > 0) checkMaxLength(++totalLength, maxLength);
{
map.add(buffer.toString(), "");
}
checkMaxKeys(map, maxKeys);
} }
if (key != null)
{
value = buffer.length() == 0 ? "" : buffer.toString();
buffer.setLength(0);
map.add(key, value);
}
else if (buffer.length() > 0)
{
map.add(buffer.toString(), "");
}
checkMaxKeys(map, maxKeys);
} }
/** /**
@ -482,74 +422,70 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) public static void decodeUtf8To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys)
throws IOException throws IOException
{ {
synchronized (map) Utf8StringBuilder buffer = new Utf8StringBuilder();
String key = null;
String value;
int b;
int totalLength = 0;
while ((b = in.read()) >= 0)
{ {
Utf8StringBuilder buffer = new Utf8StringBuilder(); switch ((char)b)
String key = null;
String value = null;
int b;
int totalLength = 0;
while ((b = in.read()) >= 0)
{ {
switch ((char)b) case '&':
{ value = buffer.toReplacedString();
case '&': buffer.reset();
value = buffer.toReplacedString(); if (key != null)
buffer.reset(); {
if (key != null) map.add(key, value);
{ }
map.add(key, value); else if (value != null && value.length() > 0)
} {
else if (value != null && value.length() > 0) map.add(value, "");
{ }
map.add(value, ""); key = null;
} checkMaxKeys(map, maxKeys);
key = null; break;
value = null;
checkMaxKeys(map, maxKeys);
break;
case '=': case '=':
if (key != null) if (key != null)
{ {
buffer.append((byte)b);
break;
}
key = buffer.toReplacedString();
buffer.reset();
break;
case '+':
buffer.append((byte)' ');
break;
case '%':
char code0 = (char)in.read();
char code1 = (char)in.read();
buffer.append(decodeHexByte(code0, code1));
break;
default:
buffer.append((byte)b); buffer.append((byte)b);
break; break;
} }
checkMaxLength(++totalLength, maxLength); key = buffer.toReplacedString();
} buffer.reset();
break;
if (key != null) case '+':
{ buffer.append((byte)' ');
value = buffer.toReplacedString(); break;
buffer.reset();
map.add(key, value); case '%':
char code0 = (char)in.read();
char code1 = (char)in.read();
buffer.append(decodeHexByte(code0, code1));
break;
default:
buffer.append((byte)b);
break;
} }
else if (buffer.length() > 0) checkMaxLength(++totalLength, maxLength);
{
map.add(buffer.toReplacedString(), "");
}
checkMaxKeys(map, maxKeys);
} }
if (key != null)
{
value = buffer.toReplacedString();
buffer.reset();
map.add(key, value);
}
else if (buffer.length() > 0)
{
map.add(buffer.toReplacedString(), "");
}
checkMaxKeys(map, maxKeys);
} }
public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException public static void decodeUtf16To(InputStream in, MultiMap<String> map, int maxLength, int maxKeys) throws IOException
@ -558,8 +494,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
StringWriter buf = new StringWriter(8192); StringWriter buf = new StringWriter(8192);
IO.copy(input, buf, maxLength); IO.copy(input, buf, maxLength);
// TODO implement maxKeys decodeTo(buf.getBuffer().toString(), map, StandardCharsets.UTF_16, maxKeys);
decodeTo(buf.getBuffer().toString(), map, StandardCharsets.UTF_16);
} }
/** /**
@ -627,77 +562,73 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
return; return;
} }
synchronized (map) String key = null;
String value;
int c;
int totalLength = 0;
try (ByteArrayOutputStream2 output = new ByteArrayOutputStream2())
{ {
String key = null; int size;
String value = null;
int c; while ((c = in.read()) > 0)
int totalLength = 0;
try (ByteArrayOutputStream2 output = new ByteArrayOutputStream2())
{ {
int size = 0; switch ((char)c)
while ((c = in.read()) > 0)
{ {
switch ((char)c) case '&':
{ size = output.size();
case '&': value = size == 0 ? "" : output.toString(charset);
size = output.size(); output.setCount(0);
value = size == 0 ? "" : output.toString(charset); if (key != null)
output.setCount(0); {
if (key != null) map.add(key, value);
{ }
map.add(key, value); else if (value != null && value.length() > 0)
} {
else if (value != null && value.length() > 0) map.add(value, "");
{ }
map.add(value, ""); key = null;
} checkMaxKeys(map, maxKeys);
key = null; break;
value = null; case '=':
checkMaxKeys(map, maxKeys); if (key != null)
break; {
case '=':
if (key != null)
{
output.write(c);
break;
}
size = output.size();
key = size == 0 ? "" : output.toString(charset);
output.setCount(0);
break;
case '+':
output.write(' ');
break;
case '%':
int code0 = in.read();
int code1 = in.read();
output.write(decodeHexChar(code0, code1));
break;
default:
output.write(c); output.write(c);
break; break;
} }
checkMaxLength(++totalLength, maxLength); size = output.size();
key = size == 0 ? "" : output.toString(charset);
output.setCount(0);
break;
case '+':
output.write(' ');
break;
case '%':
int code0 = in.read();
int code1 = in.read();
output.write(decodeHexChar(code0, code1));
break;
default:
output.write(c);
break;
} }
checkMaxLength(++totalLength, maxLength);
size = output.size();
if (key != null)
{
value = size == 0 ? "" : output.toString(charset);
output.setCount(0);
map.add(key, value);
}
else if (size > 0)
{
map.add(output.toString(charset), "");
}
checkMaxKeys(map, maxKeys);
} }
size = output.size();
if (key != null)
{
value = size == 0 ? "" : output.toString(charset);
output.setCount(0);
map.add(key, value);
}
else if (size > 0)
{
map.add(output.toString(charset), "");
}
checkMaxKeys(map, maxKeys);
} }
} }
@ -747,7 +678,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
char c = encoded.charAt(offset + i); char c = encoded.charAt(offset + i);
if (c < 0 || c > 0xff) if (c > 0xff)
{ {
if (buffer == null) if (buffer == null)
{ {
@ -808,7 +739,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
char c = encoded.charAt(offset + i); char c = encoded.charAt(offset + i);
if (c < 0 || c > 0xff) if (c > 0xff)
{ {
if (buffer == null) if (buffer == null)
{ {
@ -838,7 +769,7 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
byte[] ba = new byte[length]; byte[] ba = new byte[length];
int n = 0; int n = 0;
while (c >= 0 && c <= 0xff) while (c <= 0xff)
{ {
if (c == '%') if (c == '%')
{ {
@ -935,18 +866,15 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
{ {
if (charset == null) if (charset == null)
charset = ENCODING; charset = ENCODING;
byte[] bytes = null; byte[] bytes;
bytes = string.getBytes(charset); bytes = string.getBytes(charset);
int len = bytes.length;
byte[] encoded = new byte[bytes.length * 3]; byte[] encoded = new byte[bytes.length * 3];
int n = 0; int n = 0;
boolean noEncode = true; boolean noEncode = true;
for (int i = 0; i < len; i++) for (byte b : bytes)
{ {
byte b = bytes[i];
if (b == ' ') if (b == ' ')
{ {
noEncode = false; noEncode = false;
@ -981,10 +909,4 @@ public class UrlEncoded extends MultiMap<String> implements Cloneable
return new String(encoded, 0, n, charset); return new String(encoded, 0, n, charset);
} }
@Override
public Object clone()
{
return new UrlEncoded(this);
}
} }

View File

@ -24,15 +24,21 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.DynamicTest.dynamicTest;
@ -49,97 +55,97 @@ public class URLEncodedTest
tests.add(dynamicTest("Initially not empty", () -> tests.add(dynamicTest("Initially not empty", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
assertEquals(0, urlEncoded.size()); assertEquals(0, urlEncoded.size());
})); }));
tests.add(dynamicTest("Not empty after decode(\"\")", () -> tests.add(dynamicTest("Not empty after decode(\"\")", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode(""); UrlEncoded.decodeTo("", urlEncoded, UrlEncoded.ENCODING);
assertEquals(0, urlEncoded.size()); assertEquals(0, urlEncoded.size());
})); }));
tests.add(dynamicTest("Simple encode", () -> tests.add(dynamicTest("Simple encode", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name1=Value1"); UrlEncoded.decodeTo("Name1=Value1", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "simple param size"); assertEquals(1, urlEncoded.size(), "simple param size");
assertEquals("Name1=Value1", urlEncoded.encode(), "simple encode"); assertEquals("Name1=Value1", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "simple encode");
assertEquals("Value1", urlEncoded.getString("Name1"), "simple get"); assertEquals("Value1", urlEncoded.getString("Name1"), "simple get");
})); }));
tests.add(dynamicTest("Dangling param", () -> tests.add(dynamicTest("Dangling param", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name2="); UrlEncoded.decodeTo("Name2=", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "dangling param size"); assertEquals(1, urlEncoded.size(), "dangling param size");
assertEquals("Name2", urlEncoded.encode(), "dangling encode"); assertEquals("Name2", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "dangling encode");
assertEquals("", urlEncoded.getString("Name2"), "dangling get"); assertEquals("", urlEncoded.getString("Name2"), "dangling get");
})); }));
tests.add(dynamicTest("noValue param", () -> tests.add(dynamicTest("noValue param", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name3"); UrlEncoded.decodeTo("Name3", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "noValue param size"); assertEquals(1, urlEncoded.size(), "noValue param size");
assertEquals("Name3", urlEncoded.encode(), "noValue encode"); assertEquals("Name3", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "noValue encode");
assertEquals("", urlEncoded.getString("Name3"), "noValue get"); assertEquals("", urlEncoded.getString("Name3"), "noValue get");
})); }));
tests.add(dynamicTest("badly encoded param", () -> tests.add(dynamicTest("badly encoded param", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name4=V\u0629lue+4%21"); UrlEncoded.decodeTo("Name4=V\u0629lue+4%21", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "encoded param size"); assertEquals(1, urlEncoded.size(), "encoded param size");
assertEquals("Name4=V%D8%A9lue+4%21", urlEncoded.encode(), "encoded encode"); assertEquals("Name4=V%D8%A9lue+4%21", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "encoded encode");
assertEquals("V\u0629lue 4!", urlEncoded.getString("Name4"), "encoded get"); assertEquals("V\u0629lue 4!", urlEncoded.getString("Name4"), "encoded get");
})); }));
tests.add(dynamicTest("encoded param 1", () -> tests.add(dynamicTest("encoded param 1", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name4=Value%2B4%21"); UrlEncoded.decodeTo("Name4=Value%2B4%21", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "encoded param size"); assertEquals(1, urlEncoded.size(), "encoded param size");
assertEquals("Name4=Value%2B4%21", urlEncoded.encode(), "encoded encode"); assertEquals("Name4=Value%2B4%21", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "encoded encode");
assertEquals("Value+4!", urlEncoded.getString("Name4"), "encoded get"); assertEquals("Value+4!", urlEncoded.getString("Name4"), "encoded get");
})); }));
tests.add(dynamicTest("encoded param 2", () -> tests.add(dynamicTest("encoded param 2", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name4=Value+4%21%20%214"); UrlEncoded.decodeTo("Name4=Value+4%21%20%214", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "encoded param size"); assertEquals(1, urlEncoded.size(), "encoded param size");
assertEquals("Name4=Value+4%21+%214", urlEncoded.encode(), "encoded encode"); assertEquals("Name4=Value+4%21+%214", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "encoded encode");
assertEquals("Value 4! !4", urlEncoded.getString("Name4"), "encoded get"); assertEquals("Value 4! !4", urlEncoded.getString("Name4"), "encoded get");
})); }));
tests.add(dynamicTest("multi param", () -> tests.add(dynamicTest("multi param", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name5=aaa&Name6=bbb"); UrlEncoded.decodeTo("Name5=aaa&Name6=bbb", urlEncoded, UrlEncoded.ENCODING);
assertEquals(2, urlEncoded.size(), "multi param size"); assertEquals(2, urlEncoded.size(), "multi param size");
assertTrue(urlEncoded.encode().equals("Name5=aaa&Name6=bbb") || assertTrue(UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false).equals("Name5=aaa&Name6=bbb") ||
urlEncoded.encode().equals("Name6=bbb&Name5=aaa"), UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false).equals("Name6=bbb&Name5=aaa"),
"multi encode " + urlEncoded.encode()); "multi encode " + UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false));
assertEquals("aaa", urlEncoded.getString("Name5"), "multi get"); assertEquals("aaa", urlEncoded.getString("Name5"), "multi get");
assertEquals("bbb", urlEncoded.getString("Name6"), "multi get"); assertEquals("bbb", urlEncoded.getString("Name6"), "multi get");
})); }));
tests.add(dynamicTest("multiple value encoded", () -> tests.add(dynamicTest("multiple value encoded", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name7=aaa&Name7=b%2Cb&Name7=ccc"); UrlEncoded.decodeTo("Name7=aaa&Name7=b%2Cb&Name7=ccc", urlEncoded, UrlEncoded.ENCODING);
assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", urlEncoded.encode(), "multi encode"); assertEquals("Name7=aaa&Name7=b%2Cb&Name7=ccc", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "multi encode");
assertEquals("aaa,b,b,ccc", urlEncoded.getString("Name7"), "list get all"); assertEquals("aaa,b,b,ccc", urlEncoded.getString("Name7"), "list get all");
assertEquals("aaa", urlEncoded.getValues("Name7").get(0), "list get"); assertEquals("aaa", urlEncoded.getValues("Name7").get(0), "list get");
assertEquals("b,b", urlEncoded.getValues("Name7").get(1), "list get"); assertEquals("b,b", urlEncoded.getValues("Name7").get(1), "list get");
@ -148,11 +154,11 @@ public class URLEncodedTest
tests.add(dynamicTest("encoded param", () -> tests.add(dynamicTest("encoded param", () ->
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("Name8=xx%2C++yy++%2Czz"); UrlEncoded.decodeTo("Name8=xx%2C++yy++%2Czz", urlEncoded, UrlEncoded.ENCODING);
assertEquals(1, urlEncoded.size(), "encoded param size"); assertEquals(1, urlEncoded.size(), "encoded param size");
assertEquals("Name8=xx%2C++yy++%2Czz", urlEncoded.encode(), "encoded encode"); assertEquals("Name8=xx%2C++yy++%2Czz", UrlEncoded.encode(urlEncoded,UrlEncoded.ENCODING, false), "encoded encode");
assertEquals("xx, yy ,zz", urlEncoded.getString("Name8"), "encoded get"); assertEquals("xx, yy ,zz", urlEncoded.getString("Name8"), "encoded get");
})); }));
@ -219,7 +225,7 @@ public class URLEncodedTest
{ {
try (ByteArrayInputStream in3 = new ByteArrayInputStream("name=libell%E9".getBytes(StringUtil.__ISO_8859_1))) try (ByteArrayInputStream in3 = new ByteArrayInputStream("name=libell%E9".getBytes(StringUtil.__ISO_8859_1)))
{ {
MultiMap m3 = new MultiMap(); MultiMap<String> m3 = new MultiMap<>();
Charset nullCharset = null; // use the one from the system property Charset nullCharset = null; // use the one from the system property
UrlEncoded.decodeTo(in3, m3, nullCharset, -1, -1); UrlEncoded.decodeTo(in3, m3, nullCharset, -1, -1);
assertEquals("libell\u00E9", m3.getString("name"), "stream name"); assertEquals("libell\u00E9", m3.getString("name"), "stream name");
@ -230,11 +236,11 @@ public class URLEncodedTest
public void testUtf8() public void testUtf8()
throws Exception throws Exception
{ {
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
assertEquals(0, urlEncoded.size(), "Empty"); assertEquals(0, urlEncoded.size(), "Empty");
urlEncoded.clear(); urlEncoded.clear();
urlEncoded.decode("text=%E0%B8%9F%E0%B8%AB%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%81%E0%B8%9F%E0%B8%A7%E0%B8%AB%E0%B8%AA%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%AB%E0%B8%9F%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%AA%E0%B8%B2%E0%B8%9F%E0%B8%81%E0%B8%AB%E0%B8%A3%E0%B8%94%E0%B9%89%E0%B8%9F%E0%B8%AB%E0%B8%99%E0%B8%81%E0%B8%A3%E0%B8%94%E0%B8%B5&Action=Submit"); UrlEncoded.decodeTo("text=%E0%B8%9F%E0%B8%AB%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%81%E0%B8%9F%E0%B8%A7%E0%B8%AB%E0%B8%AA%E0%B8%94%E0%B8%B2%E0%B9%88%E0%B8%AB%E0%B8%9F%E0%B8%81%E0%B8%A7%E0%B8%94%E0%B8%AA%E0%B8%B2%E0%B8%9F%E0%B8%81%E0%B8%AB%E0%B8%A3%E0%B8%94%E0%B9%89%E0%B8%9F%E0%B8%AB%E0%B8%99%E0%B8%81%E0%B8%A3%E0%B8%94%E0%B8%B5&Action=Submit", urlEncoded, UrlEncoded.ENCODING);
String hex = "E0B89FE0B8ABE0B881E0B8A7E0B894E0B8B2E0B988E0B881E0B89FE0B8A7E0B8ABE0B8AAE0B894E0B8B2E0B988E0B8ABE0B89FE0B881E0B8A7E0B894E0B8AAE0B8B2E0B89FE0B881E0B8ABE0B8A3E0B894E0B989E0B89FE0B8ABE0B899E0B881E0B8A3E0B894E0B8B5"; String hex = "E0B89FE0B8ABE0B881E0B8A7E0B894E0B8B2E0B988E0B881E0B89FE0B8A7E0B8ABE0B8AAE0B894E0B8B2E0B988E0B8ABE0B89FE0B881E0B8A7E0B894E0B8AAE0B8B2E0B89FE0B881E0B8ABE0B8A3E0B894E0B989E0B89FE0B8ABE0B899E0B881E0B8A3E0B894E0B8B5";
String expected = new String(TypeUtil.fromHexString(hex), "utf-8"); String expected = new String(TypeUtil.fromHexString(hex), "utf-8");
@ -245,8 +251,8 @@ public class URLEncodedTest
public void testUtf8MultiByteCodePoint() public void testUtf8MultiByteCodePoint()
{ {
String input = "text=test%C3%A4"; String input = "text=test%C3%A4";
UrlEncoded urlEncoded = new UrlEncoded(); MultiMap<String> urlEncoded = new MultiMap<>();
urlEncoded.decode(input); UrlEncoded.decodeTo(input, urlEncoded, UrlEncoded.ENCODING);
// http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex // http://www.ltg.ed.ac.uk/~richard/utf-8.cgi?input=00e4&mode=hex
// Should be "testä" // Should be "testä"
@ -255,4 +261,50 @@ public class URLEncodedTest
String expected = "test\u00e4"; String expected = "test\u00e4";
assertThat(urlEncoded.getString("text"), is(expected)); assertThat(urlEncoded.getString("text"), is(expected));
} }
public static Stream<Arguments> invalidTestData()
{
ArrayList<Arguments> data = new ArrayList<>();
data.add(Arguments.of("Name=xx%zzyy", UTF_8, IllegalArgumentException.class));
data.add(Arguments.of("Name=%FF%FF%FF", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=%EF%EF%EF", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=%E%F%F", UTF_8, IllegalArgumentException.class));
data.add(Arguments.of("Name=x%", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=x%2", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=xxx%", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("name=X%c0%afZ", UTF_8, Utf8Appendable.NotUtf8Exception.class));
return data.stream();
}
@ParameterizedTest
@MethodSource("invalidTestData")
public void testInvalidDecode(String inputString, Charset charset, Class<? extends Throwable> expectedThrowable)
{
assertThrows(expectedThrowable, () ->
{
UrlEncoded.decodeTo(inputString, new MultiMap<>(), charset);
});
}
@ParameterizedTest
@MethodSource("invalidTestData")
public void testInvalidDecodeUtf8ToMap(String inputString, Charset charset, Class<? extends Throwable> expectedThrowable)
{
assertThrows(expectedThrowable, () ->
{
MultiMap<String> map = new MultiMap<>();
UrlEncoded.decodeUtf8To(inputString, map);
});
}
@ParameterizedTest
@MethodSource("invalidTestData")
public void testInvalidDecodeTo(String inputString, Charset charset, Class<? extends Throwable> expectedThrowable)
{
assertThrows(expectedThrowable, () ->
{
MultiMap<String> map = new MultiMap<>();
UrlEncoded.decodeTo(inputString, map, charset);
});
}
} }

View File

@ -1,80 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class UrlEncodedInvalidEncodingTest
{
public static Stream<Arguments> data()
{
ArrayList<Arguments> data = new ArrayList<>();
data.add(Arguments.of("Name=xx%zzyy", UTF_8, IllegalArgumentException.class));
data.add(Arguments.of("Name=%FF%FF%FF", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=%EF%EF%EF", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=%E%F%F", UTF_8, IllegalArgumentException.class));
data.add(Arguments.of("Name=x%", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=x%2", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("Name=xxx%", UTF_8, Utf8Appendable.NotUtf8Exception.class));
data.add(Arguments.of("name=X%c0%afZ", UTF_8, Utf8Appendable.NotUtf8Exception.class));
return data.stream();
}
@ParameterizedTest
@MethodSource("data")
public void testDecode(String inputString, Charset charset, Class<? extends Throwable> expectedThrowable)
{
assertThrows(expectedThrowable, () ->
{
UrlEncoded urlEncoded = new UrlEncoded();
urlEncoded.decode(inputString, charset);
});
}
@ParameterizedTest
@MethodSource("data")
public void testDecodeUtf8ToMap(String inputString, Charset charset, Class<? extends Throwable> expectedThrowable)
{
assertThrows(expectedThrowable, () ->
{
MultiMap<String> map = new MultiMap<>();
UrlEncoded.decodeUtf8To(inputString, map);
});
}
@ParameterizedTest
@MethodSource("data")
public void testDecodeTo(String inputString, Charset charset, Class<? extends Throwable> expectedThrowable)
{
assertThrows(expectedThrowable, () ->
{
MultiMap<String> map = new MultiMap<>();
UrlEncoded.decodeTo(inputString, map, charset);
});
}
}

View File

@ -79,7 +79,7 @@ public class SessionDump extends HttpServlet
session = request.getSession(true); session = request.getSession(true);
session.setAttribute("test", "value"); session.setAttribute("test", "value");
session.setAttribute("obj", new ObjectAttributeValue(System.currentTimeMillis())); session.setAttribute("obj", new ObjectAttributeValue(System.currentTimeMillis()));
session.setAttribute("WEBCL", new MultiMap()); session.setAttribute("WEBCL", new MultiMap<>());
} }
else if (session != null) else if (session != null)
{ {
@ -137,7 +137,7 @@ public class SessionDump extends HttpServlet
else else
{ {
if (session.getAttribute("WEBCL") == null) if (session.getAttribute("WEBCL") == null)
session.setAttribute("WEBCL", new MultiMap()); session.setAttribute("WEBCL", new MultiMap<>());
try try
{ {
out.println("<b>ID:</b> " + session.getId() + "<br/>"); out.println("<b>ID:</b> " + session.getId() + "<br/>");