Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dcbf425
[UNDERTOW-2605] ensure ReadTimeoutStreamSourceConduit is cleaned up a…
aogburn Sep 11, 2025
0a3714e
[UNDERTOW-2605] fix ExactLengthReadTimeoutTestCase for proxy profile
aogburn Sep 12, 2025
fcd94cd
[UNDERTOW-2582] Add weak map for ServerWebSocketContainer
fl4via Dec 3, 2024
15a7e1b
[UNDERTOW-2534] At FrameHandler, set the tccl before invoking the han…
aogburn Nov 16, 2024
f151ce7
[UNDERTOW-2534] At UndertowContainerProvider, prevent the tccl from l…
fl4via Dec 3, 2024
fb2d1d9
[UNDERTOW-2609] Make HttpServerExchange.getQueryString() return the n…
fl4via Sep 13, 2025
c460b57
[UNDERTOW-2609] At HttpServerExchange, start a refactoring for making…
fl4via Sep 15, 2025
cc7d60c
[UNDERTOW-2609] Fix the meaning of query string methods and fields in…
fl4via Sep 18, 2025
34a730f
[UNDERTOW-2609] If possible, it is always better to test the whole qu…
fl4via Jan 4, 2026
ca61570
[UNDERTOW-2377] CVE-2024-3884 CVE-2024-4027 Define 2MB default upload…
baranowb Apr 24, 2024
8b57ed4
[UNDERTOW-2377] Set SERVLET_ENTITY_SIZE to -1 at ServletInputStreamEa…
fl4via Sep 3, 2025
09f0639
[UNDERTOW-2377] SimpleBlockingServerTestCase does not need to set MUL…
fl4via Sep 3, 2025
4886a2e
[UNDERTOW-2377] Set MULTIPART_MAX_ENTITY_SIZE to -1 at MultiPartTestC…
fl4via Sep 3, 2025
bcc10ed
[UNDERTOW-2377] Set MAX_ENTITY_SIZE to -1 at ConnectionTerminationTes…
fl4via Sep 4, 2025
bdc86d1
[UNDERTOW-2656] CVE-2025-12543 Add handler to scrutinize Host header …
baranowb Nov 4, 2025
5db5baa
[UNDERTOW-2656] Add checks for empty Host header
baranowb Dec 4, 2025
1396c5c
[UNDERTOW-2674] Fix the usage of the error codes, we should be using …
fl4via Jan 9, 2026
2947b7c
[UNDERTOW-2668] ServletRelativePathAttribute switch to %U from %R and…
baranowb Dec 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions build.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Created by buildmetadata-maven-plugin 1.7.0 ( SHA: 6f444cae )
build.artifactId=undertow-websockets-jsr
build.groupId=io.undertow
build.java.compiler=HotSpot 64-Bit Tiered Compilers
build.java.runtime.name=OpenJDK Runtime Environment
build.java.runtime.version=1.8.0_422-b05
build.java.vendor=Red Hat, Inc.
build.java.vm=OpenJDK 64-Bit Server VM
build.maven.execution.cmdline=-Djavax.net.ssl.trustStore\=/home/aogburn/bin/builder/maven.truststore -Djavax.net.ssl.trustStorePassword\=rhmaven -s /home/aogburn/bin/builder/eap-build-settings.xml clean install -DskipTests
build.maven.version=3.9.6
build.scmRevision.date=17.09.2024
build.scmRevision.id=aaa36f6ad214aecd7d0d611cad582aa058f3a3e8
build.scmRevision.url=scm\:git\://github.com/undertow-io/undertow.git/undertow-websockets-jsr
build.version=2.2.33.SP2-redhat-00001
build.version.full=2.2.33.SP2-redhat-00001raaa36f6ad214aecd7d0d611cad582aa058f3a3e8
12 changes: 12 additions & 0 deletions core/src/main/java/io/undertow/Handlers.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.undertow.server.handlers.DisableCacheHandler;
import io.undertow.server.handlers.ExceptionHandler;
import io.undertow.server.handlers.GracefulShutdownHandler;
import io.undertow.server.handlers.HostHeaderHandler;
import io.undertow.server.handlers.HttpContinueAcceptingHandler;
import io.undertow.server.handlers.HttpContinueReadHandler;
import io.undertow.server.handlers.HttpTraceHandler;
Expand Down Expand Up @@ -600,6 +601,17 @@ public static LearningPushHandler learningPushHandler(int maxEntries, HttpHandle
return new LearningPushHandler(maxEntries, -1, next);
}

/**
* Creates a handler that automatically vets Host header content/absence/presence according to
* https://datatracker.ietf.org/doc/html/rfc7230#section-5.4 and related
*
* @param next The next handler
* @return A host header handler
*/
public static HostHeaderHandler hostHeaderHandler(HttpHandler next) {
return new HostHeaderHandler(next);
}

private Handlers() {

}
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/io/undertow/UndertowLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -478,4 +478,8 @@ void nodeConfigCreated(URI connectionURI, String balancer, String domain, String
@LogMessage(level = WARN)
@Message(id = 5107, value = "Failed to set web socket timeout.")
void failedToSetWSTimeout(@Cause Exception e);

@LogMessage(level = WARN)
@Message(id = 5108, value = "Configuration option is no longer supported: %s.")
void configurationNotSupported(String string);
}
9 changes: 7 additions & 2 deletions core/src/main/java/io/undertow/UndertowOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,14 @@ public class UndertowOptions {
public static final Option<Long> MULTIPART_MAX_ENTITY_SIZE = Option.simple(UndertowOptions.class, "MULTIPART_MAX_ENTITY_SIZE", Long.class);

/**
* We do not have a default upload limit
* Default maximum upload size 2MB
*/
public static final long DEFAULT_MAX_ENTITY_SIZE = -1;
public static final long DEFAULT_MAX_ENTITY_SIZE = 2097152;

/**
* Default maximum multipart upload size 2MB
*/
public static final long DEFAULT_MULTIPART_MAX_ENTITY_SIZE = 2097152;

/**
* If we should buffer pipelined requests. Defaults to false.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private QueryStringAttribute(boolean includeQuestionMark) {

@Override
public String readAttribute(final HttpServerExchange exchange) {
String qs = exchange.getQueryString();
String qs = exchange.getDecodedQueryString();
if(qs.isEmpty() || !includeQuestionMark) {
return qs;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ public String readAttribute(final HttpServerExchange exchange) {
.append(exchange.getRequestMethod().toString())
.append(' ')
.append(exchange.getRequestURI());
if (!exchange.getQueryString().isEmpty()) {
if (!exchange.getDecodedQueryString().isEmpty()) {
sb.append('?');
sb.append(exchange.getQueryString());
sb.append(exchange.getDecodedQueryString());
}
sb.append(' ')
.append(exchange.getProtocol().toString()).toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,11 @@ private void exitRead(long consumed, Throwable readError) throws IOException {
}
long newVal = oldVal - consumed;
state = newVal;
if (allAreClear(state, MASK_COUNT)) {
if (allAreClear(state, FLAG_FINISHED)) {
next.suspendReads();
}
}
}

private void invokeFinishListener() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected URI getRedirectURI(final HttpServerExchange exchange, final int port)
}
}
uriBuilder.append(uri);
final String queryString = exchange.getQueryString();
final String queryString = exchange.getDecodedQueryString();
if (queryString != null && !queryString.isEmpty()) {
uriBuilder.append("?").append(queryString);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,17 +235,17 @@ private AuthenticationMechanismOutcome handleDigestHeader(HttpServerExchange exc
if(parsedHeader.containsKey(DigestAuthorizationToken.DIGEST_URI)) {
String uri = parsedHeader.get(DigestAuthorizationToken.DIGEST_URI);
String requestURI = exchange.getRequestURI();
if(!exchange.getQueryString().isEmpty()) {
requestURI = requestURI + "?" + exchange.getQueryString();
if(!exchange.getDecodedQueryString().isEmpty()) {
requestURI = requestURI + "?" + exchange.getDecodedQueryString();
}
if(!uri.equals(requestURI)) {
//it is possible we were given an absolute URI
//we reconstruct the URI from the host header to make sure they match up
//I am not sure if this is overly strict, however I think it is better
//to be safe than sorry
requestURI = exchange.getRequestURL();
if(!exchange.getQueryString().isEmpty()) {
requestURI = requestURI + "?" + exchange.getQueryString();
if(!exchange.getDecodedQueryString().isEmpty()) {
requestURI = requestURI + "?" + exchange.getDecodedQueryString();
}
if(!uri.equals(requestURI)) {
//just end the auth process
Expand Down
5 changes: 2 additions & 3 deletions core/src/main/java/io/undertow/server/Connectors.java
Original file line number Diff line number Diff line change
Expand Up @@ -546,10 +546,9 @@ public static void setExchangeRequestPath(final HttpServerExchange exchange, fin
}
if(requiresDecode && allowUnescapedCharactersInUrl) {
final String decodedQS = URLUtils.decode(qs, charset, decodeSlashFlag,false, decodeBuffer);
exchange.setQueryString(decodedQS);
} else {
exchange.setQueryString(qs);
exchange.setDecodedQueryString(decodedQS);
}
exchange.setQueryString(qs);

URLUtils.parseQueryString(qs, exchange, charset, decodeQueryString, maxParameters);
return;
Expand Down
74 changes: 56 additions & 18 deletions core/src/main/java/io/undertow/server/HttpServerExchange.java
Original file line number Diff line number Diff line change
Expand Up @@ -207,13 +207,14 @@ public final class HttpServerExchange extends AbstractAttachable {
private String resolvedPath = "";

/**
* the query string - percent encoded
* the unencoded query string (i.e. percent encoded), in its original form as it appears in the received request.
*/
private String queryString = "";

/**
* the non-decoded query string. Set only when query string goes through decoding
* the decoded query string, if there was any decoding done
*/
private String nonDecodedQueryString = null;
private String decodedQueryString = null;

private int requestWrapperCount = 0;
private ConduitWrapper<StreamSourceConduit>[] requestWrappers; //we don't allocate these by default, as for get requests they are not used
Expand Down Expand Up @@ -573,24 +574,25 @@ public HttpServerExchange setResolvedPath(final String resolvedPath) {
}

/**
* Returns the query string for this request.
*
* @return The query string, without the leading ?
* @return The query string as originally appeared in the request, without the leading ?
*/
public String getQueryString() {
return queryString;
return this.queryString;
}

/**
* Set query string. Leading {@code '?'} char will be removed automatically.
* Sets the query string, unencoded and in its original form as it appears in the received request.
* Leading {@code '?'} char will be removed automatically.<p>
*
* @param queryString the query string as originally contained in the request, without any decoding
* @return this http server exchange
*/
public HttpServerExchange setQueryString(final String queryString) {
// Clean leading ?
if( queryString.length() > 0 && queryString.charAt(0) == '?' ) {
this.queryString = queryString.substring(1);
} else {
this.queryString = queryString;
this.queryString = cleanQueryString(queryString);
if (this.queryString == null) {
this.queryString = "";
}
return this;
}
Expand All @@ -600,27 +602,63 @@ public HttpServerExchange setQueryString(final String queryString) {
* The returned string does not contain the leading {@code '?'} char.
*
* @return The request query string, without the leading {@code '?'}, non-decoded.
*
* @deprecated use {@link #getQueryString()} instead
*/
@Deprecated
public String getNonDecodedQueryString() {
return this.nonDecodedQueryString == null? this.queryString: this.nonDecodedQueryString;
return getQueryString();
}

/**
* Sets the non-decoded query string. Leading {@code '?'} char will be removed automatically.<p>
* Must be invoked only if the {@link #getQueryString() query string} has gone through decoding. In such case, we expect
* that both forms of the query string will be set in the exchange: {@link #setQueryString decoded} and non-decoded.
*
* @param nonDecodedQueryString the query string as originally contained in the request, without any decoding
* @param unencodedQueryString the query string as originally contained in the request, without any decoding
* @return this http server exchange
*
* @deprecated Use #setQueryString instead
*/
@Deprecated
public HttpServerExchange setNonDecodedQueryString(String unencodedQueryString) {
return setQueryString(unencodedQueryString);
}

/**
* Returns the query string in its decoded form if available, which will depend on configs such as
* {@link UndertowOptions#ALLOW_UNESCAPED_CHARACTERS_IN_URL}.
* If unavailable, the decoded query string is just the same as {@link #getQueryString}
*
* @return The request query string, without the leading {@code '?'}, post parsing, decoded.
*/
public HttpServerExchange setNonDecodedQueryString(String nonDecodedQueryString) {
public String getDecodedQueryString() {
return this.decodedQueryString != null && this.decodedQueryString.length() > 0 ? this.decodedQueryString : this.queryString;
}

/**
* Sets the decoded query string.
* Leading {@code '?'} char will be removed automatically.<p>
* Must be invoked only if the {@link #getQueryString() query string} has gone through decoding. In such case, we expect
* that both forms of the query string will be set in the exchange: decoded and {@link #setQueryString non-decoded}
*
* @param decodedQueryString the request query string, without the leading {@code '?'}, post parsing, decoded.
* @return this http server exchange
*/
public HttpServerExchange setDecodedQueryString(String decodedQueryString) {
this.decodedQueryString = cleanQueryString(decodedQueryString);
return this;
}

private String cleanQueryString(String queryString) {
// Clean leading ?
if( nonDecodedQueryString.length() > 0 && nonDecodedQueryString.charAt(0) == '?' ) {
this.nonDecodedQueryString = nonDecodedQueryString.substring(1);
if (queryString == null) {
return queryString;
} else if( queryString.length() > 0 && queryString.charAt(0) == '?' ) {
return queryString.substring(1);
} else {
this.nonDecodedQueryString = nonDecodedQueryString;
return queryString;
}
return this;
}

/**
Expand Down
Loading
Loading