fix parameter/multimap handling with duplicates, add test

This commit is contained in:
Jörg Prante 2023-12-21 15:48:37 +01:00
parent 3c7e102765
commit 0a31fca8fe
7 changed files with 135 additions and 60 deletions

View file

@ -1,3 +1,3 @@
group = org.xbib group = org.xbib
name = net-http name = net-http
version = 4.0.8 version = 4.0.9

View file

@ -3,6 +3,7 @@ dependencies {
api libs.netty.codec.http2 api libs.netty.codec.http2
testImplementation project(':net-http-client-netty') testImplementation project(':net-http-client-netty')
testImplementation project(':net-http-template-groovy') testImplementation project(':net-http-template-groovy')
testImplementation libs.datastructures.json.tiny
} }
test { test {

View file

@ -35,7 +35,7 @@ public class HttpRequest extends BaseHttpRequest {
@Override @Override
public InputStream getInputStream() { public InputStream getInputStream() {
return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null; return builder.getBody() != null ? new ByteBufferInputStream(builder.getBody()) : null;
} }
@Override @Override
@ -49,7 +49,7 @@ public class HttpRequest extends BaseHttpRequest {
return "HttpRequest[method=" + builder.getMethod() + return "HttpRequest[method=" + builder.getMethod() +
",version=" + builder.getVersion() + ",version=" + builder.getVersion() +
",parameter=" + builder.getParameter() + ",parameter=" + builder.getParameter() +
",body=" + (builder.byteBuffer != null) + ",body=" + (builder.getBody() != null) +
"]"; "]";
} }
} }

View file

@ -27,10 +27,6 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
private FullHttpRequest httpRequest; private FullHttpRequest httpRequest;
protected ByteBuffer byteBuffer;
protected CharBuffer charBuffer;
protected HttpRequestBuilder() { protected HttpRequestBuilder() {
} }
@ -59,25 +55,12 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
if (fullHttpRequest.content() != null) { if (fullHttpRequest.content() != null) {
ByteBuf byteBuf = fullHttpRequest.content(); ByteBuf byteBuf = fullHttpRequest.content();
byte[] bytes = ByteBufUtil.getBytes(byteBuf); byte[] bytes = ByteBufUtil.getBytes(byteBuf);
byteBuffer = ByteBuffer.wrap(bytes); this.byteBuffer = ByteBuffer.wrap(bytes);
} }
} }
return this; return this;
} }
@Override
public ByteBuffer getBody() {
return byteBuffer;
}
@Override
public CharBuffer getBodyAsChars(Charset charset) {
if (charBuffer == null) {
charBuffer = byteBuffer != null ? charset.decode(byteBuffer) : null;
}
return charBuffer;
}
@Override @Override
public HttpRequestBuilder setAddress(HttpAddress httpAddress) { public HttpRequestBuilder setAddress(HttpAddress httpAddress) {
super.setAddress(httpAddress); super.setAddress(httpAddress);

View file

@ -0,0 +1,70 @@
package org.xbib.net.http.netty.test;
import org.junit.jupiter.api.Test;
import org.xbib.datastructures.api.Builder;
import org.xbib.datastructures.json.tiny.JsonBuilder;
import org.xbib.net.Parameter;
import org.xbib.net.http.HttpHeaders;
import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.server.HttpRequest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HttpRequestTest {
@Test
public void testFormAsMultiMap() {
Parameter parameter = Parameter.builder().domain(Parameter.Domain.FORM)
.enableDuplicates()
.add("key", "key1")
.add("key", "key2")
.add("key", "key3")
.add("value", "value1")
.add("value", "")
.add("value", "")
.add("op", "and")
.add("op", "")
.add("op", "")
.build();
HttpHeaders httpHeaders = new HttpHeaders();
HttpRequest httpRequest = org.xbib.net.http.server.netty.HttpRequest.builder()
.setMethod(HttpMethod.POST)
.setParameter(parameter)
.setHeaders(httpHeaders)
.build();
assertEquals("{key=[key1, key2, key3], value=[value1, , ], op=[and, , ]}",
httpRequest.asMultiMap().toString());
}
@Test
public void testJsonBodyAsMultiMap() throws IOException {
// JSON keys overwrite each other
Builder builder = JsonBuilder.builder()
.beginMap()
.field("key", "key1")
.field("key", "key2")
.field("key", "key3")
.field("value", "value1")
.field("value", "")
.field("value", "")
.field("op", "and")
.field("op", "")
.field("op", "")
.endMap();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("content-type", "application/json");
HttpRequest httpRequest = org.xbib.net.http.server.netty.HttpRequest.builder()
.setMethod(HttpMethod.POST)
.setHeaders(httpHeaders)
.setBody(ByteBuffer.wrap(builder.build().getBytes(StandardCharsets.UTF_8)))
.build();
Objects.requireNonNull(httpRequest.getBody());
assertEquals("{key=[key3], value=[], op=[]}",
httpRequest.asMultiMap().toString());
}
}

View file

@ -2,6 +2,7 @@ package org.xbib.net.http.server;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.CharBuffer;
import java.nio.charset.MalformedInputException; import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.charset.UnmappableCharacterException; import java.nio.charset.UnmappableCharacterException;
@ -184,34 +185,45 @@ public abstract class BaseHttpRequest implements HttpRequest {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public MultiMap<String, Object> asMultiMap() { public MultiMap<String, Object> asMultiMap() {
PercentDecoder percentDecoder = new PercentDecoder();
MultiMap<String, Object> multiMap = new ParameterMap(); MultiMap<String, Object> multiMap = new ParameterMap();
String contentType = getHeaders().get(HttpHeaderNames.CONTENT_TYPE); Parameter parameter = getParameter();
if (getMethod() == HttpMethod.POST && try {
contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) { PercentDecoder percentDecoder = new PercentDecoder();
String bodyAsChars = getBodyAsChars(StandardCharsets.UTF_8).toString(); String contentType = getHeaders().get(HttpHeaderNames.CONTENT_TYPE);
Map<String, Object> map = Json.toMap(bodyAsChars); if (getMethod() == HttpMethod.POST &&
for (Map.Entry<String, Object> entry : map.entrySet()) { contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) {
if (entry.getValue() instanceof Iterable) { CharBuffer charBuffer = builder.getBodyAsChars(StandardCharsets.UTF_8);
multiMap.putAll(entry.getKey(), (Iterable<Object>) entry.getValue()); if (charBuffer != null) {
String bodyAsChars = charBuffer.toString();
Map<String, Object> map = Json.toMap(bodyAsChars);
for (Map.Entry<String, Object> entry : map.entrySet()) {
if (entry.getValue() instanceof Iterable) {
multiMap.putAll(entry.getKey(), (Iterable<Object>) entry.getValue());
} else {
multiMap.put(entry.getKey(), entry.getValue());
}
}
} else { } else {
multiMap.put(entry.getKey(), entry.getValue()); logger.log(Level.WARNING, "body is null in POST request where json is declared");
}
} else {
if (parameter != null) {
toMultiMapEntry(parameter.get(Parameter.Domain.FORM),
percentDecoder,
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
multiMap);
} }
} }
} if (parameter != null) {
try { toMultiMapEntry(parameter.get(Parameter.Domain.PATH),
toMultiMapEntry(getParameter().get(Parameter.Domain.PATH), percentDecoder,
percentDecoder, false,
false, multiMap);
multiMap); toMultiMapEntry(parameter.get(Parameter.Domain.QUERY),
toMultiMapEntry(getParameter().get(Parameter.Domain.FORM), percentDecoder,
percentDecoder, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType), multiMap);
multiMap); }
toMultiMapEntry(getParameter().get(Parameter.Domain.QUERY),
percentDecoder,
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType),
multiMap);
} catch (ParameterException e) { } catch (ParameterException e) {
logger.log(Level.WARNING, e.getMessage(), ExceptionFormatter.format(e)); logger.log(Level.WARNING, e.getMessage(), ExceptionFormatter.format(e));
} }
@ -223,23 +235,29 @@ public abstract class BaseHttpRequest implements HttpRequest {
PercentDecoder percentDecoder, PercentDecoder percentDecoder,
boolean isFormEncoded, boolean isFormEncoded,
MultiMap<String, Object> multiMap) { MultiMap<String, Object> multiMap) {
if (parameter == null) {
return;
}
for (Pair<String, Object> entry : parameter) { for (Pair<String, Object> entry : parameter) {
try { try {
List<Object> list; List<Object> list;
Object value = entry.getValue(); Object object = entry.getValue();
if (value instanceof List) { if (object instanceof List) {
list = (List<Object>) value; list = (List<Object>) object;
} else if (value != null) { } else if (object != null) {
list = List.of(value); list = List.of(object);
} else { } else {
list = List.of(); list = List.of();
} }
for (Object object : list) { for (Object o : list) {
String string = object.toString(); String string = o.toString();
if (isFormEncoded) { if (isFormEncoded) {
string = string.replace('+', ' '); string = string.replace('+', ' ');
} }
multiMap.put(entry.getKey(), percentDecoder.decode(string)); String name = entry.getKey();
String value = percentDecoder.decode(string);
logger.log(Level.INFO, "name = " + name + " value = " + value);
multiMap.put(name, value);
} }
} catch (MalformedInputException | UnmappableCharacterException e) { } catch (MalformedInputException | UnmappableCharacterException e) {
logger.log(Level.WARNING, "unable to percent decode parameter: " + logger.log(Level.WARNING, "unable to percent decode parameter: " +
@ -255,7 +273,7 @@ public abstract class BaseHttpRequest implements HttpRequest {
@Override @Override
protected Collection<Object> newValues() { protected Collection<Object> newValues() {
// keep values with multiple occurences // keep values with multiple occurrences
return TinyList.builder(); return TinyList.builder();
} }
} }

View file

@ -1,6 +1,5 @@
package org.xbib.net.http.server; package org.xbib.net.http.server;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
@ -57,6 +56,8 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
protected ByteBuffer byteBuffer; protected ByteBuffer byteBuffer;
protected CharBuffer charBuffer;
protected boolean done; protected boolean done;
protected List<Message> messages; protected List<Message> messages;
@ -194,6 +195,13 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
return byteBuffer; return byteBuffer;
} }
public CharBuffer getBodyAsChars(Charset charset) {
if (charBuffer == null) {
charBuffer = byteBuffer != null ? charset.decode(byteBuffer) : null;
}
return charBuffer;
}
@Override @Override
public BaseHttpRequestBuilder setBaseURL(URL baseURL) { public BaseHttpRequestBuilder setBaseURL(URL baseURL) {
if (done) { if (done) {
@ -259,11 +267,6 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
return requestPath; return requestPath;
} }
@Override
public CharBuffer getBodyAsChars(Charset charset) {
return byteBuffer != null ? charset.decode(byteBuffer) : null;
}
@Override @Override
public BaseHttpRequestBuilder setParameter(Parameter parameter) { public BaseHttpRequestBuilder setParameter(Parameter parameter) {
if (done) { if (done) {