* Honor parameters order when parsing query and form parameters When parsing the query or form parameters in Request, the values are stored in a MultiMap. This class extends HashMap which does not preserve the order of insertion so a request with parameters "first=1&second=2" might end up in a map where "second" will come first when iterating on the entry set. The order is necessary in some case where the request is signed off the body and/or the query parameters. When the order is not preserved, it is impossible to reconstruct the original request sent, unless using the Request::getInputStream which consumes the stream and makes subsequent calls to Request::getParameters to don't return the form parameters which can be misleading. The same behavior applied to query parameters, by using Request::getQueryString, you get the correct order but Request::getParameters will not. Moreoever, if the application is behind a reverse proxy using Jetty that is proxying using Request::getParameters which consume the request InputStream, it will be completely impossible to reconstruct the original request. * Added a test with parameter merging Co-authored-by: Jacques-Etienne Beaudet <jebeaudet@gmail.com>
This commit is contained in:
parent
292d6cda9f
commit
f2e9680431
|
@ -34,6 +34,7 @@ import java.util.EnumSet;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
@ -253,6 +254,64 @@ public class RequestTest
|
||||||
assertTrue(responses.startsWith("HTTP/1.1 200"));
|
assertTrue(responses.startsWith("HTTP/1.1 200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParameterExtractionKeepOrderingIntact() throws Exception
|
||||||
|
{
|
||||||
|
AtomicReference<Map<String, String[]>> reference = new AtomicReference<>();
|
||||||
|
_handler._checker = new RequestTester()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean check(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
reference.set(request.getParameterMap());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
String request = "POST /?first=1&second=2&third=3&fourth=4 HTTP/1.1\r\n" +
|
||||||
|
"Host: whatever\r\n" +
|
||||||
|
"Content-Type: application/x-www-form-urlencoded\n" +
|
||||||
|
"Connection: close\n" +
|
||||||
|
"Content-Length: 34\n" +
|
||||||
|
"\n" +
|
||||||
|
"fifth=5&sixth=6&seventh=7&eighth=8";
|
||||||
|
|
||||||
|
String responses = _connector.getResponse(request);
|
||||||
|
assertTrue(responses.startsWith("HTTP/1.1 200"));
|
||||||
|
assertThat(new ArrayList<>(reference.get().keySet()), is(Arrays.asList("first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParameterExtractionOrderingWithMerge() throws Exception
|
||||||
|
{
|
||||||
|
AtomicReference<Map<String, String[]>> reference = new AtomicReference<>();
|
||||||
|
_handler._checker = new RequestTester()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean check(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
reference.set(request.getParameterMap());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
String request = "POST /?a=1&b=2&c=3&a=4 HTTP/1.1\r\n" +
|
||||||
|
"Host: whatever\r\n" +
|
||||||
|
"Content-Type: application/x-www-form-urlencoded\n" +
|
||||||
|
"Connection: close\n" +
|
||||||
|
"Content-Length: 11\n" +
|
||||||
|
"\n" +
|
||||||
|
"c=5&b=6&a=7";
|
||||||
|
|
||||||
|
String responses = _connector.getResponse(request);
|
||||||
|
Map<String, String[]> returnedMap = reference.get();
|
||||||
|
assertTrue(responses.startsWith("HTTP/1.1 200"));
|
||||||
|
assertThat(new ArrayList<>(returnedMap.keySet()), is(Arrays.asList("a", "b", "c")));
|
||||||
|
assertTrue(Arrays.equals(returnedMap.get("a"), new String[]{"1", "4", "7"}));
|
||||||
|
assertTrue(Arrays.equals(returnedMap.get("b"), new String[]{"2", "6"}));
|
||||||
|
assertTrue(Arrays.equals(returnedMap.get("c"), new String[]{"3", "5"}));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParamExtractionBadSequence() throws Exception
|
public void testParamExtractionBadSequence() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,8 +15,8 @@ package org.eclipse.jetty.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import java.util.Map;
|
||||||
* @param <V> the entry type for multimap values
|
* @param <V> the entry type for multimap values
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class MultiMap<V> extends HashMap<String, List<V>>
|
public class MultiMap<V> extends LinkedHashMap<String, List<V>>
|
||||||
{
|
{
|
||||||
public MultiMap()
|
public MultiMap()
|
||||||
{
|
{
|
||||||
|
@ -316,13 +316,13 @@ public class MultiMap<V> extends HashMap<String, List<V>>
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
Iterator<Entry<String, List<V>>> iter = entrySet().iterator();
|
Iterator<Map.Entry<String, List<V>>> iter = entrySet().iterator();
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append('{');
|
sb.append('{');
|
||||||
boolean delim = false;
|
boolean delim = false;
|
||||||
while (iter.hasNext())
|
while (iter.hasNext())
|
||||||
{
|
{
|
||||||
Entry<String, List<V>> e = iter.next();
|
Map.Entry<String, List<V>> e = iter.next();
|
||||||
if (delim)
|
if (delim)
|
||||||
{
|
{
|
||||||
sb.append(", ");
|
sb.append(", ");
|
||||||
|
@ -350,7 +350,7 @@ public class MultiMap<V> extends HashMap<String, List<V>>
|
||||||
*/
|
*/
|
||||||
public Map<String, String[]> toStringArrayMap()
|
public Map<String, String[]> toStringArrayMap()
|
||||||
{
|
{
|
||||||
HashMap<String, String[]> map = new HashMap<String, String[]>(size() * 3 / 2)
|
Map<String, String[]> map = new LinkedHashMap<String, String[]>(size() * 3 / 2)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
|
|
Loading…
Reference in New Issue