From ba332dd10bfd51de682f6f5b1099747f4f40b183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Sat, 4 Nov 2023 19:45:51 +0100 Subject: [PATCH] add multi map convenience method to obtain all parameters from a http request --- gradle.properties | 2 +- net-http-server/build.gradle | 1 + .../src/main/java/module-info.java | 1 + .../xbib/net/http/server/BaseHttpRequest.java | 97 ++++++++++++++++++- .../org/xbib/net/http/server/HttpRequest.java | 3 + settings.gradle | 1 + 6 files changed, 102 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6df7df8..4ba1a76 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = org.xbib name = net-http -version = 4.0.2 +version = 4.0.3 org.gradle.warning.mode = ALL diff --git a/net-http-server/build.gradle b/net-http-server/build.gradle index 0589dca..3c91d86 100644 --- a/net-http-server/build.gradle +++ b/net-http-server/build.gradle @@ -3,5 +3,6 @@ dependencies { api libs.config implementation libs.settings.datastructures.json implementation libs.settings.datastructures.yaml + implementation libs.datastructures.tiny implementation libs.datastructures.json.tiny } diff --git a/net-http-server/src/main/java/module-info.java b/net-http-server/src/main/java/module-info.java index f3f4427..d844c53 100644 --- a/net-http-server/src/main/java/module-info.java +++ b/net-http-server/src/main/java/module-info.java @@ -26,6 +26,7 @@ module org.xbib.net.http.server { requires org.xbib.net.mime; requires org.xbib.net.http; requires org.xbib.datastructures.common; + requires org.xbib.datastructures.tiny; requires org.xbib.datastructures.json.tiny; requires org.xbib.config; requires java.logging; diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java index 0f20e1b..7269ca1 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java @@ -2,27 +2,41 @@ package org.xbib.net.http.server; import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; +import java.nio.charset.UnmappableCharacterException; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; +import org.xbib.datastructures.common.MultiMap; import org.xbib.datastructures.common.Pair; +import org.xbib.datastructures.json.tiny.Json; import org.xbib.datastructures.json.tiny.JsonBuilder; +import org.xbib.datastructures.tiny.TinyList; +import org.xbib.datastructures.tiny.TinyMultiMap; import org.xbib.net.Attributes; import org.xbib.net.Parameter; import org.xbib.net.ParameterException; +import org.xbib.net.PercentDecoder; import org.xbib.net.URL; +import org.xbib.net.http.HttpHeaderNames; +import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpVersion; import org.xbib.net.http.server.auth.BaseAttributes; import org.xbib.net.http.server.route.HttpRouterContext; +import org.xbib.net.util.ExceptionFormatter; public abstract class BaseHttpRequest implements HttpRequest { + private static final Logger logger = Logger.getLogger(BaseHttpRequest.class.getName()); + protected final BaseHttpRequestBuilder builder; private final Attributes attributes; @@ -157,7 +171,7 @@ public abstract class BaseHttpRequest implements HttpRequest { jsonBuilder.buildKey("sequenceid").buildValue(builder.sequenceId); jsonBuilder.buildKey("streamid").buildValue(builder.streamId); jsonBuilder.buildKey("requestid").buildValue(builder.requestId); - // body may be large + // body may be large, skip it //jsonBuilder.buildKey("encoding").buildValue("ISO-8859-1"); //jsonBuilder.buildKey("body").buildValue(StandardCharsets.ISO_8859_1.decode(builder.getBody()).toString()); jsonBuilder.endMap(); @@ -166,4 +180,83 @@ public abstract class BaseHttpRequest implements HttpRequest { } return jsonBuilder.build(); } + + @SuppressWarnings("unchecked") + @Override + public MultiMap asMultiMap() { + PercentDecoder percentDecoder = new PercentDecoder(); + MultiMap multiMap = new ParameterMap(); + String contentType = getHeaders().get(HttpHeaderNames.CONTENT_TYPE); + if (getMethod() == HttpMethod.POST && + contentType != null && contentType.contains(HttpHeaderValues.APPLICATION_JSON)) { + String bodyAsChars = getBodyAsChars(StandardCharsets.UTF_8).toString(); + Map map = Json.toMap(bodyAsChars); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Iterable) { + multiMap.putAll(entry.getKey(), (Iterable) entry.getValue()); + } else { + multiMap.put(entry.getKey(), entry.getValue()); + } + } + } + try { + toMultiMapEntry(getParameter().get(Parameter.Domain.PATH), + percentDecoder, + false, + multiMap); + toMultiMapEntry(getParameter().get(Parameter.Domain.FORM), + percentDecoder, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType), + multiMap); + toMultiMapEntry(getParameter().get(Parameter.Domain.QUERY), + percentDecoder, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.equals(contentType), + multiMap); + } catch (ParameterException e) { + logger.log(Level.WARNING, e.getMessage(), ExceptionFormatter.format(e)); + } + return multiMap; + } + + @SuppressWarnings("unchecked") + private static void toMultiMapEntry(Parameter parameter, + PercentDecoder percentDecoder, + boolean isFormEncoded, + MultiMap multiMap) { + for (Pair entry : parameter) { + try { + List list; + Object value = entry.getValue(); + if (value instanceof List) { + list = (List) value; + } else if (value != null) { + list = List.of(value); + } else { + list = List.of(); + } + for (Object object : list) { + String string = object.toString(); + if (isFormEncoded) { + string = string.replace('+', ' '); + } + multiMap.put(entry.getKey(), percentDecoder.decode(string)); + } + } catch (MalformedInputException | UnmappableCharacterException e) { + logger.log(Level.WARNING, "unable to percent decode parameter: " + + entry.getKey() + "=" + entry.getValue()); + } + } + } + + private static class ParameterMap extends TinyMultiMap { + + public ParameterMap() { + } + + @Override + protected Collection newValues() { + // keep values with multiple occurences + return TinyList.builder(); + } + } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java index 916389b..ca1e55d 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java @@ -3,6 +3,7 @@ package org.xbib.net.http.server; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.List; +import org.xbib.datastructures.common.MultiMap; import org.xbib.net.Attributes; import org.xbib.net.Parameter; import org.xbib.net.Request; @@ -45,4 +46,6 @@ public interface HttpRequest extends Request { Attributes getAttributes(); String asJson(); + + MultiMap asMultiMap(); } diff --git a/settings.gradle b/settings.gradle index 4b4ef20..ddfd5ca 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,7 @@ dependencyResolutionManagement { library('net-security', 'org.xbib', 'net-security').versionRef('net') library('net-bouncycastle', 'org.xbib', 'net-bouncycastle').versionRef('net') library('datastructures-common', 'org.xbib', 'datastructures-common').versionRef('datastructures') + library('datastructures-tiny', 'org.xbib', 'datastructures-tiny').versionRef('datastructures') library('datastructures-json-tiny', 'org.xbib', 'datastructures-json-tiny').versionRef('datastructures') library('datastructures-yaml-tiny', 'org.xbib', 'datastructures-yaml-tiny').versionRef('datastructures') library('config', 'org.xbib', 'config').versionRef('datastructures')