419687 - HttpClient's query parameters must be case sensitive.

Modified class Fields to take a boolean parameter that defines
whether it is case sensitive or not, and updated HttpRequest to use
a case sensitive Fields instance for the query parameters.
This commit is contained in:
Simone Bordet 2013-10-17 12:37:01 +02:00
parent 45828ee906
commit 7618826349
3 changed files with 82 additions and 18 deletions

View File

@ -54,7 +54,7 @@ public class HttpRequest implements Request
private static final AtomicLong ids = new AtomicLong(); private static final AtomicLong ids = new AtomicLong();
private final HttpFields headers = new HttpFields(); private final HttpFields headers = new HttpFields();
private final Fields params = new Fields(); private final Fields params = new Fields(true);
private final Map<String, Object> attributes = new HashMap<>(); private final Map<String, Object> attributes = new HashMap<>();
private final List<RequestListener> requestListeners = new ArrayList<>(); private final List<RequestListener> requestListeners = new ArrayList<>();
private final List<Response.ResponseListener> responseListeners = new ArrayList<>(); private final List<Response.ResponseListener> responseListeners = new ArrayList<>();

View File

@ -312,4 +312,30 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
Assert.assertEquals(HttpStatus.OK_200, response.getStatus()); Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
} }
@Test
public void testCaseSensitiveParameterName() throws Exception
{
final String name1 = "a";
final String name2 = "A";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
Assert.assertEquals(name1, request.getParameter(name1));
Assert.assertEquals(name2, request.getParameter(name2));
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/path?" + name1 + "=" + name1)
.param(name2, name2)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
} }

View File

@ -29,20 +29,33 @@ import java.util.Set;
/** /**
* <p>A container for name/value pairs, known as fields.</p> * <p>A container for name/value pairs, known as fields.</p>
* <p>A {@link Field} is composed of a case-insensitive name string and * <p>A {@link Field} is composed of a name string that can be case-sensitive
* or case-insensitive (by specifying the option at the constructor) and
* of a case-sensitive set of value strings.</p> * of a case-sensitive set of value strings.</p>
* <p>The implementation of this class is not thread safe.</p> * <p>The implementation of this class is not thread safe.</p>
*/ */
public class Fields implements Iterable<Fields.Field> public class Fields implements Iterable<Fields.Field>
{ {
private final boolean caseSensitive;
private final Map<String, Field> fields; private final Map<String, Field> fields;
/** /**
* <p>Creates an empty modifiable {@link Fields} instance.</p> * <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
* @see #Fields(Fields, boolean) * @see #Fields(Fields, boolean)
*/ */
public Fields() public Fields()
{ {
this(false);
}
/**
* <p>Creates an empty, modifiable, case insensitive {@link Fields} instance.</p>
* @param caseSensitive whether this {@link Fields} instance must be case sensitive
* @see #Fields(Fields, boolean)
*/
public Fields(boolean caseSensitive)
{
this.caseSensitive = caseSensitive;
fields = new LinkedHashMap<>(); fields = new LinkedHashMap<>();
} }
@ -55,6 +68,7 @@ public class Fields implements Iterable<Fields.Field>
*/ */
public Fields(Fields original, boolean immutable) public Fields(Fields original, boolean immutable)
{ {
this.caseSensitive = original.caseSensitive;
Map<String, Field> copy = new LinkedHashMap<>(); Map<String, Field> copy = new LinkedHashMap<>();
copy.putAll(original.fields); copy.putAll(original.fields);
fields = immutable ? Collections.unmodifiableMap(copy) : copy; fields = immutable ? Collections.unmodifiableMap(copy) : copy;
@ -68,7 +82,18 @@ public class Fields implements Iterable<Fields.Field>
if (obj == null || getClass() != obj.getClass()) if (obj == null || getClass() != obj.getClass())
return false; return false;
Fields that = (Fields)obj; Fields that = (Fields)obj;
return fields.equals(that.fields); if (size() != that.size())
return false;
if (caseSensitive != that.caseSensitive)
return false;
for (Map.Entry<String, Field> entry : fields.entrySet())
{
String name = entry.getKey();
Field value = entry.getValue();
if (!value.equals(that.get(name), caseSensitive))
return false;
}
return true;
} }
@Override @Override
@ -88,13 +113,18 @@ public class Fields implements Iterable<Fields.Field>
return result; return result;
} }
private String normalizeName(String name)
{
return caseSensitive ? name : name.toLowerCase(Locale.ENGLISH);
}
/** /**
* @param name the field name * @param name the field name
* @return the {@link Field} with the given name, or null if no such field exists * @return the {@link Field} with the given name, or null if no such field exists
*/ */
public Field get(String name) public Field get(String name)
{ {
return fields.get(name.trim().toLowerCase(Locale.ENGLISH)); return fields.get(normalizeName(name));
} }
/** /**
@ -105,10 +135,9 @@ public class Fields implements Iterable<Fields.Field>
*/ */
public void put(String name, String value) public void put(String name, String value)
{ {
name = name.trim();
// Preserve the case for the field name // Preserve the case for the field name
Field field = new Field(name, value); Field field = new Field(name, value);
fields.put(name.toLowerCase(Locale.ENGLISH), field); fields.put(normalizeName(name), field);
} }
/** /**
@ -119,7 +148,7 @@ public class Fields implements Iterable<Fields.Field>
public void put(Field field) public void put(Field field)
{ {
if (field != null) if (field != null)
fields.put(field.name().toLowerCase(Locale.ENGLISH), field); fields.put(normalizeName(field.name()), field);
} }
/** /**
@ -131,17 +160,18 @@ public class Fields implements Iterable<Fields.Field>
*/ */
public void add(String name, String value) public void add(String name, String value)
{ {
name = name.trim(); String key = normalizeName(name);
Field field = fields.get(name.toLowerCase(Locale.ENGLISH)); Field field = fields.get(key);
if (field == null) if (field == null)
{ {
// Preserve the case for the field name
field = new Field(name, value); field = new Field(name, value);
fields.put(name.toLowerCase(Locale.ENGLISH), field); fields.put(key, field);
} }
else else
{ {
field = new Field(field.name(), field.values(), value); field = new Field(field.name(), field.values(), value);
fields.put(name.toLowerCase(Locale.ENGLISH), field); fields.put(key, field);
} }
} }
@ -153,8 +183,7 @@ public class Fields implements Iterable<Fields.Field>
*/ */
public Field remove(String name) public Field remove(String name)
{ {
name = name.trim(); return fields.remove(normalizeName(name));
return fields.remove(name.toLowerCase(Locale.ENGLISH));
} }
/** /**
@ -219,6 +248,17 @@ public class Fields implements Iterable<Fields.Field>
System.arraycopy(moreValues, 0, this.values, values.length, moreValues.length); System.arraycopy(moreValues, 0, this.values, values.length, moreValues.length);
} }
public boolean equals(Field that, boolean caseSensitive)
{
if (this == that)
return true;
if (that == null)
return false;
if (caseSensitive)
return equals(that);
return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
}
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
{ {
@ -227,15 +267,13 @@ public class Fields implements Iterable<Fields.Field>
if (obj == null || getClass() != obj.getClass()) if (obj == null || getClass() != obj.getClass())
return false; return false;
Field that = (Field)obj; Field that = (Field)obj;
// Field names must be lowercase, thus we lowercase them before transmission, but keep them as is return name.equals(that.name) && Arrays.equals(values, that.values);
// internally. That's why we've to compare them case insensitive.
return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
} }
@Override @Override
public int hashCode() public int hashCode()
{ {
int result = name.toLowerCase(Locale.ENGLISH).hashCode(); int result = name.hashCode();
result = 31 * result + Arrays.hashCode(values); result = 31 * result + Arrays.hashCode(values);
return result; return result;
} }