Skip to content

Commit

Permalink
[UNDERTOW-2269] Encode query string on forward/include and improve me…
Browse files Browse the repository at this point in the history
…rge of parameters
  • Loading branch information
baranowb committed Apr 5, 2024
1 parent ddb4aee commit fc9083d
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 50 deletions.
3 changes: 3 additions & 0 deletions core/src/main/java/io/undertow/UndertowMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -647,4 +647,7 @@ public interface UndertowMessages {
@Message(id = 208, value = "Failed to allocate resource")
IOException failedToAllocateResource();

@Message(id = 209, value = "Failed to encode query string '%s' with '%s' encoding.")
IllegalArgumentException failedToEncodeQueryString(final String q, final String e);

}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public final class HttpServerExchange extends AbstractAttachable {
private String resolvedPath = "";

/**
* the query string
* the query string - percent encoded
*/
private String queryString = "";

Expand Down
62 changes: 44 additions & 18 deletions core/src/main/java/io/undertow/util/QueryParameterUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.Map;
import org.xnio.OptionMap;

import io.undertow.UndertowMessages;
import io.undertow.UndertowOptions;
import io.undertow.server.HttpServerExchange;

Expand All @@ -42,33 +44,42 @@ private QueryParameterUtils() {
}

public static String buildQueryString(final Map<String, Deque<String>> params) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Map.Entry<String, Deque<String>> entry : params.entrySet()) {
if (entry.getValue().isEmpty()) {
if (first) {
first = false;
} else {
sb.append('&');
}
sb.append(entry.getKey());
sb.append('=');
} else {
for (String val : entry.getValue()) {
return QueryParameterUtils.buildQueryString(params, null);
}

public static String buildQueryString(final Map<String, Deque<String>> params, final String encoding) {
try {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Map.Entry<String, Deque<String>> entry : params.entrySet()) {
final String key = encoding != null ? URLEncoder.encode(entry.getKey(), encoding) : entry.getKey();
if (entry.getValue().isEmpty()) {
if (first) {
first = false;
} else {
sb.append('&');
}
sb.append(entry.getKey());
sb.append(key);
sb.append('=');
sb.append(val);
} else {
for (String val : entry.getValue()) {
if (first) {
first = false;
} else {
sb.append('&');
}
final String _val = encoding != null ? URLEncoder.encode(val, encoding) : val;
sb.append(key);
sb.append('=');
sb.append(_val);
}
}
}
return sb.toString();
} catch (UnsupportedEncodingException e) {
throw UndertowMessages.MESSAGES.failedToEncodeQueryString(buildQueryString(params), encoding);
}
return sb.toString();
}

/**
* Parses a query string into a map
* @param newQueryString The query string
Expand Down Expand Up @@ -146,8 +157,9 @@ public static Map<String, Deque<String>> mergeQueryParametersWithNewQueryString(
return mergeQueryParametersWithNewQueryString(queryParameters, newQueryString, StandardCharsets.UTF_8.name());
}

@Deprecated
public static Map<String, Deque<String>> mergeQueryParametersWithNewQueryString(final Map<String, Deque<String>> queryParameters, final String newQueryString, final String encoding) {

//DEPRACETED this will create duplicates
Map<String, Deque<String>> newQueryParameters = parseQueryString(newQueryString, encoding);
//according to the spec the new query parameters have to 'take precedence'
for (Map.Entry<String, Deque<String>> entry : queryParameters.entrySet()) {
Expand All @@ -160,6 +172,20 @@ public static Map<String, Deque<String>> mergeQueryParametersWithNewQueryString(
return newQueryParameters;
}

public static Map<String, Deque<String>> mergeQueryParameters(final Map<String, Deque<String>> newParams, final Map<String, Deque<String>> oldParams) {
//according to the spec the new query parameters have to 'take precedence'
for (Map.Entry<String, Deque<String>> entry : oldParams.entrySet()) {
if (!newParams.containsKey(entry.getKey())) {
newParams.put(entry.getKey(), new ArrayDeque<>(entry.getValue()));
} else {
final Deque<String> newValues = newParams.get(entry.getKey());
final Deque<String> oldValues = entry.getValue();
oldValues.stream().filter(v -> !newValues.contains(v)).forEach(v-> newValues.add(v));
}
}
return newParams;
}

public static String getQueryParamEncoding(HttpServerExchange exchange) {
String encoding = null;
OptionMap undertowOptions = exchange.getConnection().getUndertowOptions();
Expand Down
50 changes: 19 additions & 31 deletions servlet/src/main/java/io/undertow/servlet/util/DispatchUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,6 @@
*/
package io.undertow.servlet.util;

import io.undertow.server.Connectors;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.handlers.ServletPathMatch;
import io.undertow.servlet.handlers.ServletRequestContext;
import io.undertow.servlet.spec.HttpServletRequestImpl;
import io.undertow.servlet.spec.HttpServletResponseImpl;
import io.undertow.servlet.spec.ServletContextImpl;
import io.undertow.util.ParameterLimitException;
import jakarta.servlet.ServletException;
import java.util.Deque;
import java.util.Map;

import static jakarta.servlet.AsyncContext.ASYNC_CONTEXT_PATH;
import static jakarta.servlet.AsyncContext.ASYNC_MAPPING;
import static jakarta.servlet.AsyncContext.ASYNC_PATH_INFO;
Expand All @@ -54,6 +42,20 @@
import static jakarta.servlet.RequestDispatcher.INCLUDE_REQUEST_URI;
import static jakarta.servlet.RequestDispatcher.INCLUDE_SERVLET_PATH;

import java.util.Deque;
import java.util.Map;

import io.undertow.server.Connectors;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.handlers.ServletPathMatch;
import io.undertow.servlet.handlers.ServletRequestContext;
import io.undertow.servlet.spec.HttpServletRequestImpl;
import io.undertow.servlet.spec.HttpServletResponseImpl;
import io.undertow.servlet.spec.ServletContextImpl;
import io.undertow.util.ParameterLimitException;
import io.undertow.util.QueryParameterUtils;
import jakarta.servlet.ServletException;

/**
* <p>Utility class to manage the dispatching parsing of the path. The methods
* fill the exchange, request and response with the needed data for the
Expand Down Expand Up @@ -210,24 +212,6 @@ public static ServletPathMatch dispatchAsync(final String path,
return pathMatch;
}

private static Map<String, Deque<String>> mergeQueryParameters(final Map<String, Deque<String>> newParams, final Map<String, Deque<String>> oldParams) {
for (Map.Entry<String, Deque<String>> entry : oldParams.entrySet()) {
Deque<String> values = newParams.get(entry.getKey());
if (values == null) {
// add all the values as new params do not contain this key
newParams.put(entry.getKey(), entry.getValue());
} else {
// merge values new params first
for (String v : entry.getValue()) {
if (!values.contains(v)) {
values.add(v);
}
}
}
}
return newParams;
}

private static String assignRequestPath(final String path, final HttpServletRequestImpl requestImpl,
final ServletContextImpl servletContext, final boolean include) throws ParameterLimitException {
final StringBuilder sb = new StringBuilder();
Expand All @@ -253,7 +237,11 @@ private static String assignRequestPath(final String path, final HttpServletRequ
}
// both forward and include merge parameters by spec
if (!fake.getQueryString().isEmpty()) {
requestImpl.setQueryParameters(mergeQueryParameters(fake.getQueryParameters(), requestImpl.getQueryParameters()));
final Map<String, Deque<String>> merged = QueryParameterUtils.mergeQueryParameters(fake.getQueryParameters(), exchange.getQueryParameters());
requestImpl.setQueryParameters(null);
exchange.getQueryParameters().clear();
exchange.getQueryParameters().putAll(merged);
requestImpl.getQueryParameters();
}
return newRequestPath;
}
Expand Down

0 comments on commit fc9083d

Please sign in to comment.