VYPR
High severity7.5GHSA Advisory· Published Dec 3, 2025· Updated Apr 15, 2026

CVE-2024-3884

CVE-2024-3884

Description

A flaw was found in Undertow that can cause remote denial of service attacks. When the server uses the FormEncodedDataDefinition.doParse(StreamSourceChannel) method to parse large form data encoding with application/x-www-form-urlencoded, the method will cause an OutOfMemory issue. This flaw allows unauthorized users to cause a remote denial of service (DoS) attack.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
io.undertow:undertow-coreMaven
< 2.2.39.Final2.2.39.Final
io.undertow:undertow-coreMaven
>= 2.4.0.Alpha1, < 2.4.0.Beta12.4.0.Beta1
io.undertow:undertow-coreMaven
>= 2.3.0.Alpha1, < 2.3.21.Final2.3.21.Final

Affected products

1

Patches

1
cb854c779b9e

Merge pull request #1882 from fl4via/backport-fixes_2.2.x

https://github.com/undertow-io/undertowFlavia RainoneJan 29, 2026via ghsa
46 files changed · +1232 151
  • build.metadata+15 0 added
    @@ -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
    
  • core/src/main/java/io/undertow/attribute/QueryStringAttribute.java+1 1 modified
    @@ -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;
             }
    
  • core/src/main/java/io/undertow/attribute/RequestLineAttribute.java+2 2 modified
    @@ -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();
    
  • core/src/main/java/io/undertow/conduits/FixedLengthStreamSourceConduit.java+5 0 modified
    @@ -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() {
    
  • core/src/main/java/io/undertow/Handlers.java+12 0 modified
    @@ -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;
    @@ -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() {
     
         }
    
  • core/src/main/java/io/undertow/security/handlers/SinglePortConfidentialityHandler.java+1 1 modified
    @@ -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);
             }
    
  • core/src/main/java/io/undertow/security/impl/DigestAuthenticationMechanism.java+4 4 modified
    @@ -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
    
  • core/src/main/java/io/undertow/server/Connectors.java+2 3 modified
    @@ -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;
    
  • core/src/main/java/io/undertow/server/handlers/accesslog/ExtendedAccessLogParser.java+2 2 modified
    @@ -328,15 +328,15 @@ protected ExchangeAttribute getClientToServerElement(
                         return new ExchangeAttribute() {
                             @Override
                             public String readAttribute(HttpServerExchange exchange) {
    -                            String query = exchange.getQueryString();
    +                            String query = exchange.getDecodedQueryString();
     
                                 if (query.isEmpty()) {
                                     return exchange.getRequestURI();
                                 } else {
                                     StringBuilder buf = new StringBuilder();
                                     buf.append(exchange.getRequestURI());
                                     buf.append('?');
    -                                buf.append(exchange.getQueryString());
    +                                buf.append(exchange.getDecodedQueryString());
                                     return buf.toString();
                                 }
                             }
    
  • core/src/main/java/io/undertow/server/handlers/form/MultiPartParserDefinition.java+1 1 modified
    @@ -109,7 +109,7 @@ public void exchangeEvent(final HttpServerExchange exchange, final NextListener
                         nextListener.proceed();
                     }
                 });
    -            Long sizeLimit = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MULTIPART_MAX_ENTITY_SIZE);
    +            Long sizeLimit = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MULTIPART_MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MULTIPART_MAX_ENTITY_SIZE );
                 if(sizeLimit != null && sizeLimit > 0) { // do not overwrite the entity size with sizeLimit that is <= 0
                     exchange.setMaxEntitySize(sizeLimit);
                 }
    
  • core/src/main/java/io/undertow/server/handlers/HostHeaderHandler.java+295 0 added
    @@ -0,0 +1,295 @@
    +/*
    + * JBoss, Home of Professional Open Source.
    + * Copyright 2025 Red Hat, Inc., and individual contributors
    + * as indicated by the @author tags.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + *  Unless required by applicable law or agreed to in writing, software
    + *  distributed under the License is distributed on an "AS IS" BASIS,
    + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + *  See the License for the specific language governing permissions and
    + *  limitations under the License.
    + */
    +package io.undertow.server.handlers;
    +
    +import java.util.regex.Pattern;
    +
    +import io.undertow.server.HandlerWrapper;
    +import io.undertow.server.HttpHandler;
    +import io.undertow.server.HttpServerExchange;
    +import io.undertow.util.HeaderMap;
    +import io.undertow.util.HeaderValues;
    +import io.undertow.util.Headers;
    +import io.undertow.util.HttpString;
    +import io.undertow.util.NetworkUtils;
    +import io.undertow.util.Protocols;
    +import io.undertow.util.StatusCodes;
    +
    +/**
    + * Handler which check if Host header is properly formed and present.
    + *
    + * @author baranowb
    + */
    +public class HostHeaderHandler implements HttpHandler {
    +
    +    public static final HandlerWrapper WRAPPER = new Wrapper();
    +
    +    public static final String STATUS_NO_HOST_HEADER = "No Host Header";
    +    public static final String STATUS_TOO_MANY_HOST_HEADERS = "Only One Host Header Allowed";
    +    public static final String STATUS_MALFORMED_PORT = "Host Header Malformed Port";
    +    public static final String STATUS_MALFORMED_IP_LITERAL = "Host Header Malformed IP-Literal";
    +    public static final String STATUS_MALFORMED_IP_LITERAL_BAD_CHARS = "Host Header Bad Characters";
    +    public static final String STATUS_HOST_NO_MATCH = "URI Host Header NO MATCH";
    +    private static final Pattern IP4_EXACT = Pattern.compile(NetworkUtils.IP4_EXACT);
    +    private static final Pattern IP6_EXACT = Pattern.compile(NetworkUtils.IP6_EXACT);
    +    private static final boolean[] ALLOWED_REGNAME_CHARACTERS = new boolean[256];
    +    private static final boolean[] HEX_CHARACTERS = new boolean[256];
    +    private static final boolean[] ALLOWED_IPv_FUTURE_CHARACTERS = new boolean[256]; // this is almost the same as
    +                                                                                     // ALLOWED_REGNAME_CHARACTERS with bonus
    +                                                                                     // ":" but having it as array rather than
    +                                                                                     // extra check is just faster
    +    static {
    +        // reg-name = *( unreserved / pct-encoded / sub-delims )
    +        // ALPHA / DIGIT / "-" / "." / "_" / "~" , %%, and "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
    +        for (int i = 0; i < ALLOWED_REGNAME_CHARACTERS.length; ++i) {
    +            if ((i >= '0' && i <= '9') || (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z')) {
    +                ALLOWED_REGNAME_CHARACTERS[i] = true;
    +                ALLOWED_IPv_FUTURE_CHARACTERS[i] = true;
    +            } else {
    +                switch (i) {
    +                    case '-':
    +                    case '.':
    +                    case '_':
    +                    case '~':
    +                    case '!':
    +                    case '$':
    +                    case '&':
    +                    case '\'':
    +                    case '(':
    +                    case ')':
    +                    case '*':
    +                    case '+':
    +                    case ',':
    +                    case ';':
    +                    case '=': {
    +                        ALLOWED_REGNAME_CHARACTERS[i] = true;
    +                        ALLOWED_IPv_FUTURE_CHARACTERS[i] = true;
    +                        break;
    +                    }
    +                    default:
    +                        ALLOWED_REGNAME_CHARACTERS[i] = false;
    +                        ALLOWED_IPv_FUTURE_CHARACTERS[i] = false;
    +                }
    +            }
    +
    +        }
    +
    +        ALLOWED_IPv_FUTURE_CHARACTERS[':'] = true;
    +
    +        for (int i = 0; i < HEX_CHARACTERS.length; ++i) {
    +            if ((i >= '0' && i <= '9') || (i >= 'a' && i <= 'f') || (i >= 'A' && i <= 'F')) {
    +                HEX_CHARACTERS[i] = true;
    +            } else
    +                HEX_CHARACTERS[i] = false;
    +        }
    +
    +    }
    +    private final HttpHandler next;
    +
    +    public HostHeaderHandler(HttpHandler next) {
    +        this.next = next;
    +    }
    +
    +    @Override
    +    public void handleRequest(HttpServerExchange exchange) throws Exception {
    +        // TODO: add debug/warn log?
    +        // 400 if in case of no Host header or more than one: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
    +        // 400 if value violate rules. Host = uri-host [ ":" port ]
    +        // uri-host https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
    +        // port https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
    +        // NOTE: that 3.2.2 is NOT as restrictive as pure DNS it allows subdelims, percent etc by design as to not be
    +        // restricted to pure DNS, ALTHOUGH DNS compliant content is preferred.
    +        final HeaderMap headerMap = exchange.getRequestHeaders();
    +        final HeaderValues headerValues = headerMap.get(Headers.HOST);
    +        final HttpString protocol = exchange.getProtocol();
    +
    +        if((protocol.equals(Protocols.HTTP_0_9) || protocol.equals(Protocols.HTTP_1_0))){
    +            if (headerValues == null) {
    +                //TODO: should we fake Host till we make it?
    +                next.handleRequest(exchange);
    +                return;
    +            }
    +            //else {
    +                //clients want to be good citizens and send it anyway.
    +                //fall through to below, first check will be false, but rest is the same as for HTTP1.1+
    +            //}
    +        }
    +
    +        if (headerValues == null || headerValues.size() == 0) {
    +            // isEmpty - we assume http/https ? so authority is defined for this type, it cant be empty?
    +            terminate(exchange, STATUS_NO_HOST_HEADER);
    +            return;
    +        } else if (headerValues.size() > 1) {
    +            terminate(exchange, STATUS_TOO_MANY_HOST_HEADERS);
    +            return;
    +        }
    +
    +
    +        // parsing time.
    +        final String headerValue = headerValues.element();
    +        // uri-host [ ":" port ]
    +        // This is tricky, IP-Literal contain :, which is port delimiter in pair
    +        // Lets just try to take take care of port first
    +        final int rightBracketIndex = headerValue.lastIndexOf(']');
    +        final int lastColonIndex = headerValue.lastIndexOf(':');
    +
    +        final String hostHeaderURI;
    +        // in case of IPv4, rightBracketIndex will be -1, in case of IPv6, it MUST be less than last :
    +        if (rightBracketIndex < lastColonIndex) {
    +            // we have port or potentially malformed IP Literal:
    +            // IP-literal = "[" ( IPv6address / IPvFuture ) "]" - without right bracket
    +            if (rightBracketIndex == -1 && headerValue.startsWith("[")) {
    +                // bad [ = 0, ] = -1, : = n+
    +                // good [ = 0, ] = n , : = n+x
    +                terminate(exchange, STATUS_MALFORMED_IP_LITERAL);
    +                return;
    +            }
    +            // we have valid host-uri with port
    +            final String portString = headerValue.substring(lastColonIndex + 1);
    +            try {
    +                int port = Integer.parseInt(portString);
    +                if (port <= 0 || port > 65535) {
    +                    // sanity check
    +                    // NOTE: 3.2.3 does not have provision like for IPv4 - decimal between 0-255.
    +                    // so this might be too restrictive
    +                    terminate(exchange, STATUS_MALFORMED_PORT);
    +                    return;
    +                }
    +                // fall through to uri-host checks
    +            } catch (NumberFormatException nfe) {
    +                terminate(exchange, STATUS_MALFORMED_PORT);
    +                return;
    +            }
    +
    +            hostHeaderURI = headerValue.substring(0, lastColonIndex);
    +        } else {
    +            hostHeaderURI = headerValue;
    +        }
    +
    +        // at this point we either have IP-Literal, IPv4 address or custom name
    +        if (rightBracketIndex > 0 && hostHeaderURI.indexOf('[') != 0) {
    +            // 1:2:4]*
    +            terminate(exchange, STATUS_MALFORMED_IP_LITERAL);
    +            return;
    +        } else if (rightBracketIndex > 0 && hostHeaderURI.indexOf('[') == 0) {
    +            // IPv6 or IPFuture
    +            // IP-literal = "[" ( IPv6address / IPvFuture ) "]"
    +            // IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
    +            final String debracked = hostHeaderURI.substring(1, hostHeaderURI.length() - 1);
    +            if (debracked.startsWith("v")) {
    +                final int dotIndex = debracked.indexOf(".");
    +                if (dotIndex < 2) {
    +                    // we need at least one HEX
    +                    terminate(exchange, STATUS_MALFORMED_IP_LITERAL);
    +                    return;
    +                }
    +
    +                final String hex = debracked.substring(1, dotIndex);
    +                for (int i = 0; i < hex.length(); i++) {
    +                    final char c1 = hex.charAt(i);
    +                    if (!HEX_CHARACTERS[c1]) {
    +                        terminate(exchange, STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +                        return;
    +                    }
    +                }
    +                if (dotIndex + 1 >= debracked.length()) {
    +                    // we need some character behind dot.
    +                    terminate(exchange, STATUS_MALFORMED_IP_LITERAL);
    +                    return;
    +                }
    +
    +                for (int i = dotIndex + 1; i < debracked.length(); i++) {
    +                    final char c = debracked.charAt(i);
    +                    if (!ALLOWED_IPv_FUTURE_CHARACTERS[c]) {
    +                        terminate(exchange, STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +                        return;
    +                    }
    +                }
    +            } else {
    +                // This will match IPv6 and IPv6 with embedded IPv4
    +                if (!IP6_EXACT.matcher(debracked).matches()) {
    +                    terminate(exchange, STATUS_MALFORMED_IP_LITERAL);
    +                    return;
    +                }
    +                // TODO: as in case of IPv6 do we need to vet some weir daddresses?
    +            }
    +
    +        } else if (IP4_EXACT.matcher(hostHeaderURI).matches()) {
    +            // IPv4
    +            // NOTE: above will match only valid range 0-255, rest will fall through to reg-name, which is ok, since its
    +            // essentially
    +            // superset of IP, given DIGIT + "." ( unreserved )
    +        } else {
    +            // registered name can contain . and digits, so it technically overlap IPv4.
    +            // above wont cover -192.168.1.1/355.0.0.0, which technically is correct 'reg-name'
    +            // at this point we can really only check if its valid char or percent encoding.
    +            for (int index = 0; index < hostHeaderURI.length(); index++) {
    +                final char c = hostHeaderURI.charAt(index);
    +                if (c == '%') {
    +                    // we need at least two more that are HEX
    +                    if (index + 2 < hostHeaderURI.length()) {
    +                        char c1 = hostHeaderURI.charAt(++index);
    +                        char c2 = hostHeaderURI.charAt(++index);
    +                        if (!(HEX_CHARACTERS[c1] && HEX_CHARACTERS[c2])) {
    +                            terminate(exchange, STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +                            return;
    +                        }
    +                    } else {
    +                        // we dont have at least two chars(hex), so its not proper percent escape
    +                        terminate(exchange, STATUS_MALFORMED_IP_LITERAL);
    +                        return;
    +                    }
    +                } else if (!ALLOWED_REGNAME_CHARACTERS[c]) {
    +                    terminate(exchange, STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +                    return;
    +                }
    +                // valid
    +            }
    +        }
    +        // NOTE: at this point if userinfo("@") was present in host, it would have failed
    +        // we need only to check if Host header value is contained within URI if its absolute or authority
    +        if (exchange.isHostIncludedInRequestURI()) {
    +            if (!exchange.getRequestURI().contains(hostHeaderURI)) {
    +                terminate(exchange, STATUS_HOST_NO_MATCH);
    +                return;
    +            } else if (hostHeaderURI.isEmpty()) {
    +                terminate(exchange, STATUS_HOST_NO_MATCH);
    +                return;
    +            }
    +        }
    +
    +        // in the end...
    +        next.handleRequest(exchange);
    +    }
    +
    +    private void terminate(final HttpServerExchange exchange, final String message) {
    +        exchange.setStatusCode(StatusCodes.BAD_REQUEST);
    +        exchange.setResponseContentLength(0);
    +        exchange.getResponseHeaders().add(Headers.CONNECTION, Headers.CLOSE.toString());
    +        exchange.setReasonPhrase(message);
    +        exchange.endExchange();
    +    }
    +
    +    private static class Wrapper implements HandlerWrapper {
    +
    +        @Override
    +        public HttpHandler wrap(HttpHandler handler) {
    +            return new HostHeaderHandler(handler);
    +        }
    +    }
    +}
    
  • core/src/main/java/io/undertow/server/handlers/HttpTraceHandler.java+2 2 modified
    @@ -49,9 +49,9 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
                 exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "message/http");
                 StringBuilder body = new StringBuilder("TRACE ");
                 body.append(exchange.getRequestURI());
    -            if(!exchange.getQueryString().isEmpty()) {
    +            if(!exchange.getDecodedQueryString().isEmpty()) {
                     body.append('?');
    -                body.append(exchange.getQueryString());
    +                body.append(exchange.getDecodedQueryString());
                 }
                 body.append(' ');
                 body.append(exchange.getProtocol().toString());
    
  • core/src/main/java/io/undertow/server/handlers/JDBCLogHandler.java+1 1 modified
    @@ -135,7 +135,7 @@ public void logMessage(String pattern, HttpServerExchange exchange) {
             } else {
                 jdbcLogAttribute.user = sc.getAuthenticatedAccount().getPrincipal().getName();
             }
    -        jdbcLogAttribute.query = exchange.getQueryString();
    +        jdbcLogAttribute.query = exchange.getDecodedQueryString();
     
             jdbcLogAttribute.bytes = exchange.getResponseContentLength();
             if (jdbcLogAttribute.bytes < 0) {
    
  • core/src/main/java/io/undertow/server/handlers/LearningPushHandler.java+3 3 modified
    @@ -77,12 +77,12 @@ public LearningPushHandler(int maxPathEtries, int maxPathAge, int maxPushEtries,
         public void handleRequest(HttpServerExchange exchange) throws Exception {
             String fullPath;
             String requestPath;
    -        if(exchange.getQueryString().isEmpty()) {
    +        if(exchange.getDecodedQueryString().isEmpty()) {
                 fullPath = exchange.getRequestURL();
                 requestPath = exchange.getRequestPath();
             } else{
    -            fullPath = exchange.getRequestURL() + "?" + exchange.getQueryString();
    -            requestPath = exchange.getRequestPath() + "?" + exchange.getQueryString();
    +            fullPath = exchange.getRequestURL() + "?" + exchange.getDecodedQueryString();
    +            requestPath = exchange.getRequestPath() + "?" + exchange.getDecodedQueryString();
             }
     
             doPush(exchange, fullPath);
    
  • core/src/main/java/io/undertow/server/handlers/proxy/ProxyHandler.java+1 1 modified
    @@ -446,7 +446,7 @@ public void run() {
                 }
                 requestURI.append(targetURI);
     
    -            String qs = exchange.getNonDecodedQueryString();
    +            String qs = exchange.getQueryString();
                 if (qs != null && !qs.isEmpty()) {
                     requestURI.append('?');
                     requestURI.append(qs);
    
  • core/src/main/java/io/undertow/server/handlers/RequestDumpingHandler.java+1 1 modified
    @@ -98,7 +98,7 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
                 sb.append("\n");
             }
             sb.append("          protocol=" + exchange.getProtocol() + "\n");
    -        sb.append("       queryString=" + exchange.getQueryString() + "\n");
    +        sb.append("       queryString=" + exchange.getDecodedQueryString() + "\n");
             sb.append("        remoteAddr=" + exchange.getSourceAddress() + "\n");
             sb.append("        remoteHost=" + exchange.getSourceAddress().getHostName() + "\n");
             sb.append("            scheme=" + exchange.getRequestScheme() + "\n");
    
  • core/src/main/java/io/undertow/server/handlers/resource/DirectoryUtils.java+2 2 modified
    @@ -60,12 +60,12 @@ public static boolean sendRequestedBlobs(HttpServerExchange exchange) {
             String type = null;
             String etag = null;
             String quotedEtag = null;
    -        if ("css".equals(exchange.getQueryString())) {
    +        if ("css".equals(exchange.getDecodedQueryString())) {
                 buffer = Blobs.FILE_CSS_BUFFER.duplicate();
                 type = "text/css";
                 etag = Blobs.FILE_CSS_ETAG;
                 quotedEtag = Blobs.FILE_CSS_ETAG_QUOTED;
    -        } else if ("js".equals(exchange.getQueryString())) {
    +        } else if ("js".equals(exchange.getDecodedQueryString())) {
                 buffer = Blobs.FILE_JS_BUFFER.duplicate();
                 type = "application/javascript";
                 etag = Blobs.FILE_JS_ETAG;
    
  • core/src/main/java/io/undertow/server/handlers/StuckThreadDetectionHandler.java+1 1 modified
    @@ -154,7 +154,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
             // GC'ing, as the reference is removed from the Map in the finally clause
     
             Long key = Thread.currentThread().getId();
    -        MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(), exchange.getRequestURI() + exchange.getQueryString());
    +        MonitoredThread monitoredThread = new MonitoredThread(Thread.currentThread(), exchange.getRequestURI() + exchange.getDecodedQueryString());
             activeThreads.put(key, monitoredThread);
             if(timerKey == null) {
                 synchronized (this) {
    
  • core/src/main/java/io/undertow/server/handlers/URLDecodingHandler.java+1 1 modified
    @@ -83,7 +83,7 @@ private static void decodePath(HttpServerExchange exchange, String charset, Stri
         }
     
         private static void decodeQueryString(HttpServerExchange exchange, String charset, StringBuilder sb) {
    -        if (!exchange.getQueryString().isEmpty()) {
    +        if (!exchange.getDecodedQueryString().isEmpty()) {
                 final TreeMap<String, Deque<String>> newParams = new TreeMap<>();
                 for (Map.Entry<String, Deque<String>> param : exchange.getQueryParameters().entrySet()) {
                     final Deque<String> newValues = new ArrayDeque<>(param.getValue().size());
    
  • core/src/main/java/io/undertow/server/HttpServerExchange.java+56 18 modified
    @@ -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
    @@ -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;
         }
    @@ -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;
         }
     
         /**
    
  • core/src/main/java/io/undertow/server/protocol/ajp/AjpRequestParser.java+20 17 modified
    @@ -432,7 +432,7 @@ public void parse(final ByteBuffer buf, final AjpRequestParseState state, final
                             state.currentAttribute = result.value;
                             state.currentIntegerPart = -1;
                         }
    -                    String result;
    +                    String result, decodedResult = null;
                         boolean decodingAlreadyDone = false;
                         boolean decodeUnescapedCharacters = false;
                         final StringHolder resultHolder;
    @@ -451,68 +451,71 @@ public void parse(final ByteBuffer buf, final AjpRequestParseState state, final
                                 return;
                             }
                             decodeUnescapedCharacters = resultHolder.containsUrlCharacters && allowUnescapedCharactersInUrl;
    +                        result = resultHolder.value;
                             if(resultHolder.containsUnencodedCharacters || decodeUnescapedCharacters) {
                                 try {
    -                                result = decode(resultHolder.value, true);
    +                                decodedResult = decode(resultHolder.value, true);
                                 } catch (UrlDecodeException | UnsupportedEncodingException e) {
                                     UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(e);
                                     state.badRequest = true;
                                     result = resultHolder.value;
                                 }
                                 decodingAlreadyDone = true;
    -                        } else {
    -                            result = resultHolder.value;
                             }
                         }
    +                    final String finalResult = decodedResult != null ? decodedResult : result;
                         //query string.
                         if (state.currentAttribute.equals(QUERY_STRING)) {
                             String resultAsQueryString = result == null ? "" : result;
                             exchange.setQueryString(resultAsQueryString);
    +                        exchange.setDecodedQueryString(decodedResult);
                             try {
                                 if (decodeUnescapedCharacters) {
                                     // decoding needs to be done again here, to preserve the parameters and form decoding, even if it has been done at resultAsQueryString
                                     // for more info see UNDERTOW-2312 and UNDERTOW-2555
                                     URLUtils.parseQueryString(resultHolder == null || resultHolder.value == null ? "" : resultHolder.value, exchange, encoding, doDecode, maxParameters);
    +                            } else if (decodingAlreadyDone) {
    +                                URLUtils.parseQueryString(decodedResult, exchange, encoding,
    +                                        false, maxParameters);
                                 } else {
    -                                URLUtils.parseQueryString(resultAsQueryString, exchange, encoding,
    -                                        doDecode && !decodingAlreadyDone, maxParameters);
    +                                URLUtils.parseQueryString(resultAsQueryString, exchange, encoding, doDecode, maxParameters);
                                 }
                             } catch (ParameterLimitException | IllegalArgumentException e) {
                                 UndertowLogger.REQUEST_IO_LOGGER.failedToParseRequest(e);
                                 state.badRequest = true;
                             }
                         } else if (state.currentAttribute.equals(REMOTE_USER)) {
    -                        exchange.putAttachment(ExternalAuthenticationMechanism.EXTERNAL_PRINCIPAL, result);
    -                        exchange.putAttachment(HttpServerExchange.REMOTE_USER, result);
    +                        exchange.putAttachment(ExternalAuthenticationMechanism.EXTERNAL_PRINCIPAL, finalResult);
    +                        exchange.putAttachment(HttpServerExchange.REMOTE_USER, finalResult);
                         } else if (state.currentAttribute.equals(AUTH_TYPE)) {
    -                        exchange.putAttachment(ExternalAuthenticationMechanism.EXTERNAL_AUTHENTICATION_TYPE, result);
    +                        exchange.putAttachment(ExternalAuthenticationMechanism.EXTERNAL_AUTHENTICATION_TYPE, finalResult);
                         } else if (state.currentAttribute.equals(STORED_METHOD)) {
    -                        HttpString requestMethod = new HttpString(result);
    +                        HttpString requestMethod = new HttpString(finalResult);
                             Connectors.verifyToken(requestMethod);
                             exchange.setRequestMethod(requestMethod);
                         } else if (state.currentAttribute.equals(AJP_REMOTE_PORT)) {
    -                        state.remotePort = Integer.parseInt(result);
    +                        state.remotePort = Integer.parseInt(finalResult);
                         } else if (state.currentAttribute.equals(SSL_SESSION)) {
    -                        state.sslSessionId = result;
    +                        state.sslSessionId = finalResult;
                         } else if (state.currentAttribute.equals(SSL_CIPHER)) {
    -                        state.sslCipher = result;
    +                        state.sslCipher = finalResult;
                         } else if (state.currentAttribute.equals(SSL_CERT)) {
    -                        state.sslCert = result;
    +                        state.sslCert = finalResult;
                         } else if (state.currentAttribute.equals(SSL_KEY_SIZE)) {
    -                        state.sslKeySize = result;
    +                        state.sslKeySize = finalResult;
                         } else {
                             // other attributes
                             if (state.attributes == null) {
                                 state.attributes = new TreeMap<>();
                             }
                             if (ATTR_SET.contains(state.currentAttribute)) {
                                 // known attirubtes
    -                            state.attributes.put(state.currentAttribute, result);
    +                            state.attributes.put(state.currentAttribute, finalResult);
                             } else if (allowedRequestAttributesPattern != null) {
                                 // custom allowed attributes
                                 Matcher m = allowedRequestAttributesPattern.matcher(state.currentAttribute);
                                 if (m.matches()) {
    -                                state.attributes.put(state.currentAttribute, result);
    +                                state.attributes.put(state.currentAttribute, finalResult);
                                 }
                             }
                         }
    
  • core/src/main/java/io/undertow/server/protocol/http2/Http2ReceiveListener.java+1 0 modified
    @@ -243,6 +243,7 @@ void handleInitialRequest(HttpServerExchange initial, Http2Channel channel, byte
             exchange.setRequestScheme(initial.getRequestScheme());
             exchange.setRequestMethod(initial.getRequestMethod());
             exchange.setQueryString(initial.getQueryString());
    +        exchange.setDecodedQueryString(initial.getDecodedQueryString());
             for (Map.Entry<String, Deque<String>> pathParamEntry: initial.getPathParameters().entrySet()) {
                 for (String pathParamValue : pathParamEntry.getValue()) {
                     exchange.addPathParam(pathParamEntry.getKey(), pathParamValue);
    
  • core/src/main/java/io/undertow/server/protocol/http/HttpReadListener.java+8 16 modified
    @@ -27,12 +27,11 @@
     import io.undertow.server.ConnectorStatisticsImpl;
     import io.undertow.server.Connectors;
     import io.undertow.server.HttpServerExchange;
    +import io.undertow.server.handlers.HostHeaderHandler;
     import io.undertow.server.protocol.ParseTimeoutUpdater;
     import io.undertow.server.protocol.http2.Http2ReceiveListener;
     import io.undertow.util.ClosingChannelExceptionHandler;
     import io.undertow.util.ConnectionUtils;
    -import io.undertow.util.HeaderValues;
    -import io.undertow.util.Headers;
     import io.undertow.util.HttpString;
     import io.undertow.util.Methods;
     import io.undertow.util.Protocols;
    @@ -78,7 +77,6 @@ final class HttpReadListener implements ChannelListener<ConduitStreamSourceChann
         private final long maxEntitySize;
         private final boolean recordRequestStartTime;
         private final boolean allowUnknownProtocols;
    -    private final boolean requireHostHeader;
     
         //0 = new request ok, reads resumed
         //1 = request running, new request not ok
    @@ -98,7 +96,6 @@ final class HttpReadListener implements ChannelListener<ConduitStreamSourceChann
             this.maxRequestSize = connection.getUndertowOptions().get(UndertowOptions.MAX_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_HEADER_SIZE);
             this.maxEntitySize = connection.getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
             this.recordRequestStartTime = connection.getUndertowOptions().get(UndertowOptions.RECORD_REQUEST_START_TIME, false);
    -        this.requireHostHeader = connection.getUndertowOptions().get(UndertowOptions.REQUIRE_HOST_HTTP11, true);
             this.allowUnknownProtocols = connection.getUndertowOptions().get(UndertowOptions.ALLOW_UNKNOWN_PROTOCOLS, false);
             int requestParseTimeout = connection.getUndertowOptions().get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1);
             int requestIdleTimeout = connection.getUndertowOptions().get(UndertowOptions.NO_REQUEST_TIMEOUT, -1);
    @@ -109,6 +106,10 @@ final class HttpReadListener implements ChannelListener<ConduitStreamSourceChann
                 connection.addCloseListener(parseTimeoutUpdater);
             }
             state = new ParseState(connection.getUndertowOptions().get(UndertowOptions.HTTP_HEADERS_CACHE_SIZE, UndertowOptions.DEFAULT_HTTP_HEADERS_CACHE_SIZE));
    +
    +        if (connection.getUndertowOptions().contains(UndertowOptions.REQUIRE_HOST_HTTP11)) {
    +            UndertowLogger.ROOT_LOGGER.configurationNotSupported("REQUIRE_HOST_HTTP11");
    +        }
         }
     
         public void newRequest() {
    @@ -247,22 +248,13 @@ public void handleEventWithNoRunningRequest(final ConduitStreamSourceChannel cha
                     channel.suspendReads();
                 }
     
    -            HeaderValues host = httpServerExchange.getRequestHeaders().get(Headers.HOST);
    -            if(host != null && host.size() > 1) {
    -                sendBadRequestAndClose(connection.getChannel(), UndertowMessages.MESSAGES.moreThanOneHostHeader());
    -                return;
    -            }
    -            if(requireHostHeader && httpServerExchange.getProtocol().equals(Protocols.HTTP_1_1)) {
    -                if(host == null || host.size() ==0 || host.getFirst().isEmpty()) {
    -                    sendBadRequestAndClose(connection.getChannel(), UndertowMessages.MESSAGES.noHostInHttp11Request());
    -                    return;
    -                }
    -            }
                 if(!Connectors.areRequestHeadersValid(httpServerExchange.getRequestHeaders())) {
                     sendBadRequestAndClose(connection.getChannel(), UndertowMessages.MESSAGES.invalidHeaders());
                     return;
                 }
    -            Connectors.executeRootHandler(connection.getRootHandler(), httpServerExchange);
    +
    +            //TODO: make this configurable/dynamic, to either allow users provide their own here or in chain
    +            Connectors.executeRootHandler(HostHeaderHandler.WRAPPER.wrap(connection.getRootHandler()), httpServerExchange);
             } catch (Throwable t) {
                 sendBadRequestAndClose(connection.getChannel(), t);
                 return;
    
  • core/src/main/java/io/undertow/server/protocol/http/HttpRequestParser.java+2 2 modified
    @@ -552,11 +552,11 @@ final void handleQueryParameters(ByteBuffer buffer, ParseState state, HttpServer
                 }
                 if (next == ' ' || next == '\t') {
                     String queryString = stringBuilder.toString();
    +                exchange.setQueryString(queryString);
                     if(urlDecodeRequired && this.allowUnescapedCharactersInUrl) {
    -                    exchange.setNonDecodedQueryString(queryString);
                         queryString = decode(queryString, urlDecodeRequired, state, slashDecodingFlag, false);
    +                    exchange.setDecodedQueryString(queryString);
                     }
    -                exchange.setQueryString(queryString);
                     if (nextQueryParam == null) {
                         if (queryParamPos != stringBuilder.length()) {
                             exchange.addQueryParam(decode(stringBuilder.substring(queryParamPos), nextQueryParamDecodeRequired, state, true, true), "");
    
  • core/src/main/java/io/undertow/UndertowLogger.java+4 0 modified
    @@ -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);
     }
    \ No newline at end of file
    
  • core/src/main/java/io/undertow/UndertowOptions.java+7 2 modified
    @@ -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.
    
  • core/src/main/java/io/undertow/util/NetworkUtils.java+31 14 modified
    @@ -32,29 +32,46 @@
      */
     public class NetworkUtils {
     
    -    public static final String IP4_EXACT = "(?:\\d{1,3}\\.){3}\\d{1,3}";
    +    /**
    +     * IPv4Segment: (25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])
    +     */
    +    public static final String IP4_SEGMENT = "(25[0-5]|(?:2[0-4]|(?:(1{0,1}[0-9]))){0,1}(?:([0-9])))";
    +    /**
    +     * ?: - unnamed groups are used for performance reasons.
    +     * IPv4Segment: 25[0-5]|((2[0-4]|1){0,1}[0-9]){0,1}[0-9])  -extra () is not needed but makes it clear for OR
    +     * IPv4Address: (IPv4Segment\.){3,3}IPv4Segment
    +     */
    +    public static final String IP4_EXACT = "(?:"+IP4_SEGMENT+"\\.){3,3}(?:"+IP4_SEGMENT+")";
     
    +    /**
    +     * IPv6Segment: ([0-9a-fA-F]{1,4}) - 1 to 4 hex digits
    +     */
    +    public static final String IP6_SEGMENT = "([0-9a-fA-F]{1,4})";
         /**
          * IPV6 match. ?: - unnamed groups are used for performance reasons.
          * Requirements:
          * - match full or partial IPV6 ( sliding '::')
          * - match end to start - ^$ to ensure it does not match part of some random (\d:){n,m}
          * - IPv4-Embedded IPv6 Address
    +     * - TODO: special types of addresses ?
    +     * Explanation:
    +     * IPv6Segment: ([0-9a-fA-F]{1,4}) - 1 to 4 hex digits
    +     * IPv6Address: (IPv6Segment:){1,n}IPv6Segment or (IPv6Segment:){1,x}(:IPv6Segment){1,y} where x+y&lt;8
    +     * ^ shift of segments ~left to right with qualifiers
    +     * Above is just general form for pure IPv6 without any spices
          *
    -     * NO:
    -     * - IPv4 mapped/translated into IPv6
    -     *
    -     * ^(?:([0-9a-fA-F]{1,4}:){7,7}(?:[0-9a-fA-F]){1,4}                   - full address
    -     * |(?:([0-9a-fA-F]{1,4}:)){1,7}(?:(:))                               - last compressed
    -     * |(?:([0-9a-fA-F]{1,4}:)){1,6}(?:(:[0-9a-fA-F]){1,4})               - second to last
    -     * |(?:([0-9a-fA-F]{1,4}:)){1,5}(?:(:[0-9a-fA-F]{1,4})){1,2}          - etc
    -     * |(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:[0-9a-fA-F]{1,4})){1,3}
    -     * |(?:([0-9a-fA-F]{1,4}:)){1,3}(?:(:[0-9a-fA-F]{1,4})){1,4}
    -     * |(?:([0-9a-fA-F]{1,4}:)){1,2}(?:(:[0-9a-fA-F]{1,4})){1,5}
    -     * |(?:([0-9a-fA-F]{1,4}:))(?:(:[0-9a-fA-F]{1,4})){1,6}
    -     * |(?:(:))(?:((:[0-9a-fA-F]{1,4}){1,7}|(?:(:)))))$                  - all the way compressed
    +     * ^(?:([0-9a-fA-F]{1,4}:){7,7}(?:[0-9a-fA-F]){1,4}                   - 1:2:3:4:5:6:7:8
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,7}(?:(:))                               - 1::                    1:2:3:4:5:6:7::
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,6}(?:(:[0-9a-fA-F]){1,4})               - 1::8                   1:2:3:4:5:6::8
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,5}(?:(:[0-9a-fA-F]{1,4})){1,2}          - 1::7:8                 1:2:3:4:5::8
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:[0-9a-fA-F]{1,4})){1,3}          - 1::6:7:8               1:2:3:4::8
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,3}(?:(:[0-9a-fA-F]{1,4})){1,4}          - 1::5:6:7:8             1:2:3::8
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,2}(?:(:[0-9a-fA-F]{1,4})){1,5}          - 1::4:5:6:7:8           1:2::8
    +     * |(?:([0-9a-fA-F]{1,4}:))(?:(:[0-9a-fA-F]{1,4})){1,6}               - 1::3:4:5:6:7:8         1::8
    +     * |(?:(:))(?:((:[0-9a-fA-F]{1,4}){1,7}|(?:(:))))                     - ::2:3:4:5:6:7:8        ::8       ::
    +     * |(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:IPv4Address)))$                  - 1.2.3.4::192.168.1.1   1::192.168.1.1
          */
    -    public static final String IP6_EXACT = "^(?:([0-9a-fA-F]{1,4}:){7,7}(?:[0-9a-fA-F]){1,4}|(?:([0-9a-fA-F]{1,4}:)){1,7}(?:(:))|(?:([0-9a-fA-F]{1,4}:)){1,6}(?:(:[0-9a-fA-F]){1,4})|(?:([0-9a-fA-F]{1,4}:)){1,5}(?:(:[0-9a-fA-F]{1,4})){1,2}|(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:[0-9a-fA-F]{1,4})){1,3}|(?:([0-9a-fA-F]{1,4}:)){1,3}(?:(:[0-9a-fA-F]{1,4})){1,4}|(?:([0-9a-fA-F]{1,4}:)){1,2}(?:(:[0-9a-fA-F]{1,4})){1,5}|(?:([0-9a-fA-F]{1,4}:))(?:(:[0-9a-fA-F]{1,4})){1,6}|(?:(:))(?:((:[0-9a-fA-F]{1,4}){1,7}|(?:(:)))))$";
    +    public static final String IP6_EXACT = "^(?:([0-9a-fA-F]{1,4}:){7,7}(?:[0-9a-fA-F]){1,4}|(?:([0-9a-fA-F]{1,4}:)){1,7}(?:(:))|(?:([0-9a-fA-F]{1,4}:)){1,6}(?:(:[0-9a-fA-F]){1,4})|(?:([0-9a-fA-F]{1,4}:)){1,5}(?:(:[0-9a-fA-F]{1,4})){1,2}|(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:[0-9a-fA-F]{1,4})){1,3}|(?:([0-9a-fA-F]{1,4}:)){1,3}(?:(:[0-9a-fA-F]{1,4})){1,4}|(?:([0-9a-fA-F]{1,4}:)){1,2}(?:(:[0-9a-fA-F]{1,4})){1,5}|(?:([0-9a-fA-F]{1,4}:))(?:(:[0-9a-fA-F]{1,4})){1,6}|(?:(:))(?:((:[0-9a-fA-F]{1,4}){1,7}|(?:(:))))|(?:([0-9a-fA-F]{1,4}:)){1,4}(?:(:"+IP4_EXACT+")))$";
     
         public static String formatPossibleIpv6Address(String address) {
             if (address == null) {
    
  • core/src/main/java/io/undertow/util/WeakCopyOnWriteMap.java+170 0 added
    @@ -0,0 +1,170 @@
    +/*
    + * JBoss, Home of Professional Open Source.
    + * Copyright 2014 Red Hat, Inc., and individual contributors
    + * as indicated by the @author tags.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + *  Unless required by applicable law or agreed to in writing, software
    + *  distributed under the License is distributed on an "AS IS" BASIS,
    + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + *  See the License for the specific language governing permissions and
    + *  limitations under the License.
    + */
    +
    +package io.undertow.util;
    +
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.WeakHashMap;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.ConcurrentMap;
    +
    +/**
    + * A basic copy on write map. It simply delegates to an underlying map, that is swapped out
    + * every time the map is updated.
    + *
    + * Note: this is not a secure map. It should not be used in situations where the map is populated
    + * from user input.
    + *
    + * @author Stuart Douglas
    + */
    +public class WeakCopyOnWriteMap<K,V> implements ConcurrentMap<K, V> {
    +
    +    private volatile Map<K, V> delegate = Collections.emptyMap();
    +
    +    public WeakCopyOnWriteMap() {
    +    }
    +
    +    public WeakCopyOnWriteMap(Map<K, V> existing) {
    +        this.delegate = new WeakHashMap<>(existing);
    +    }
    +
    +    @Override
    +    public synchronized V putIfAbsent(K key, V value) {
    +        final Map<K, V> delegate = this.delegate;
    +        V existing = delegate.get(key);
    +        if(existing != null) {
    +            return existing;
    +        }
    +        putInternal(key, value);
    +        return null;
    +    }
    +
    +    @Override
    +    public synchronized boolean remove(Object key, Object value) {
    +        final Map<K, V> delegate = this.delegate;
    +        V existing = delegate.get(key);
    +        if(existing.equals(value)) {
    +            removeInternal(key);
    +            return true;
    +        }
    +        return false;
    +    }
    +
    +    @Override
    +    public synchronized boolean replace(K key, V oldValue, V newValue) {
    +        final Map<K, V> delegate = this.delegate;
    +        V existing = delegate.get(key);
    +        if(existing.equals(oldValue)) {
    +            putInternal(key, newValue);
    +            return true;
    +        }
    +        return false;
    +    }
    +
    +    @Override
    +    public synchronized V replace(K key, V value) {
    +        final Map<K, V> delegate = this.delegate;
    +        V existing = delegate.get(key);
    +        if(existing != null) {
    +            putInternal(key, value);
    +            return existing;
    +        }
    +        return null;
    +    }
    +
    +    @Override
    +    public int size() {
    +        return delegate.size();
    +    }
    +
    +    @Override
    +    public boolean isEmpty() {
    +        return delegate.isEmpty();
    +    }
    +
    +    @Override
    +    public boolean containsKey(Object key) {
    +        return delegate.containsKey(key);
    +    }
    +
    +    @Override
    +    public boolean containsValue(Object value) {
    +        return delegate.containsValue(value);
    +    }
    +
    +    @Override
    +    public V get(Object key) {
    +        return delegate.get(key);
    +    }
    +
    +    @Override
    +    public synchronized V put(K key, V value) {
    +        return putInternal(key, value);
    +    }
    +
    +    @Override
    +    public synchronized V remove(Object key) {
    +        return removeInternal(key);
    +    }
    +
    +    @Override
    +    public synchronized void putAll(Map<? extends K, ? extends V> m) {
    +        final Map<K, V> delegate = new WeakHashMap<>(this.delegate);
    +        for(Entry<? extends K, ? extends V> e : m.entrySet()) {
    +            delegate.put(e.getKey(), e.getValue());
    +        }
    +        this.delegate = delegate;
    +    }
    +
    +    @Override
    +    public synchronized void clear() {
    +        delegate = Collections.emptyMap();
    +    }
    +
    +    @Override
    +    public Set<K> keySet() {
    +        return delegate.keySet();
    +    }
    +
    +    @Override
    +    public Collection<V> values() {
    +        return delegate.values();
    +    }
    +
    +    @Override
    +    public Set<Entry<K, V>> entrySet() {
    +        return delegate.entrySet();
    +    }
    +
    +    //must be called under lock
    +    private V putInternal(final K key, final V value) {
    +        final Map<K, V> delegate = new WeakHashMap<>(this.delegate);
    +        V existing = delegate.put(key, value);
    +        this.delegate = delegate;
    +        return existing;
    +    }
    +
    +    public V removeInternal(final Object key) {
    +        final Map<K, V> delegate = new WeakHashMap<>(this.delegate);
    +        V existing = delegate.remove(key);
    +        this.delegate = delegate;
    +        return existing;
    +    }
    +}
    
  • core/src/main/java/io/undertow/websockets/core/WebSocketChannel.java+3 3 modified
    @@ -214,7 +214,7 @@ protected FrameHeaderData parseFrame(ByteBuffer data) throws IOException {
             } catch (WebSocketException e) {
                 //the data was corrupt
                 //send a close message
    -            WebSockets.sendClose(new CloseMessage(CloseMessage.WRONG_CODE, e.getMessage()).toByteBuffer(), this, null);
    +            WebSockets.sendClose(new CloseMessage(CloseMessage.PROTOCOL_ERROR, e.getMessage()).toByteBuffer(), this, null);
                 markReadsBroken(e);
                 if (WebSocketLogger.REQUEST_LOGGER.isDebugEnabled()) {
                     WebSocketLogger.REQUEST_LOGGER.debugf(e, "receive failed due to Exception");
    @@ -278,10 +278,10 @@ protected void handleBrokenSourceChannel(Throwable e) {
                 getFramePriority().immediateCloseFrame();
                 WebSockets.sendClose(new CloseMessage(CloseMessage.MSG_CONTAINS_INVALID_DATA, e.getMessage()).toByteBuffer(), this, null);
             } else if (e instanceof WebSocketInvalidCloseCodeException) {
    -            WebSockets.sendClose(new CloseMessage(CloseMessage.WRONG_CODE, e.getMessage()).toByteBuffer(), this, null);
    +            WebSockets.sendClose(new CloseMessage(CloseMessage.PROTOCOL_ERROR, e.getMessage()).toByteBuffer(), this, null);
             } else if (e instanceof WebSocketFrameCorruptedException) {
                 getFramePriority().immediateCloseFrame();
    -            WebSockets.sendClose(new CloseMessage(CloseMessage.WRONG_CODE, e.getMessage()).toByteBuffer(), this, null);
    +            WebSockets.sendClose(new CloseMessage(CloseMessage.PROTOCOL_ERROR, e.getMessage()).toByteBuffer(), this, null);
             }
         }
     
    
  • core/src/test/java/io/undertow/server/ConnectionTerminationTestCase.java+22 8 modified
    @@ -18,21 +18,24 @@
     
     package io.undertow.server;
     
    +import io.undertow.testutils.DefaultServer;
    +import io.undertow.testutils.HttpOneOnly;
    +import io.undertow.testutils.ProxyIgnore;
    +import io.undertow.util.FileUtils;
    +import org.junit.Assert;
    +import org.junit.Test;
    +import org.junit.runner.RunWith;
    +import org.xnio.IoUtils;
    +import org.xnio.OptionMap;
    +
     import java.io.IOException;
     import java.io.InputStream;
     import java.io.OutputStream;
     import java.net.Socket;
     import java.util.concurrent.CountDownLatch;
     import java.util.concurrent.TimeUnit;
    -import org.junit.Assert;
    -import org.junit.Test;
    -import org.junit.runner.RunWith;
    -import org.xnio.IoUtils;
     
    -import io.undertow.testutils.DefaultServer;
    -import io.undertow.testutils.HttpOneOnly;
    -import io.undertow.testutils.ProxyIgnore;
    -import io.undertow.util.FileUtils;
    +import static io.undertow.UndertowOptions.MAX_ENTITY_SIZE;
     
     /**
      * Tests abnormal connection termination
    @@ -96,4 +99,15 @@ public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener
                 IoUtils.safeClose(socket);
             }
         }
    +
    +    @DefaultServer.BeforeServerStarts
    +    public static void setupServer() {
    +        DefaultServer.setServerOptions(OptionMap.create(MAX_ENTITY_SIZE, -1L));
    +    }
    +
    +    @DefaultServer.AfterServerStops
    +    public static void cleanup() {
    +        DefaultServer.setServerOptions(OptionMap.EMPTY);
    +    }
    +
     }
    
  • core/src/test/java/io/undertow/server/ExactLengthReadTimeoutTestCase.java+143 0 added
    @@ -0,0 +1,143 @@
    +/*
    + * JBoss, Home of Professional Open Source.
    + * Copyright 2014 Red Hat, Inc., and individual contributors
    + * as indicated by the @author tags.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + *  Unless required by applicable law or agreed to in writing, software
    + *  distributed under the License is distributed on an "AS IS" BASIS,
    + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + *  See the License for the specific language governing permissions and
    + *  limitations under the License.
    + */
    +
    +package io.undertow.server;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +
    +import io.undertow.server.handlers.BlockingHandler;
    +import io.undertow.testutils.DefaultServer;
    +import io.undertow.testutils.HttpOneOnly;
    +import io.undertow.testutils.TestHttpClient;
    +import io.undertow.util.Headers;
    +import io.undertow.util.StatusCodes;
    +import org.apache.http.HttpResponse;
    +import org.apache.http.NoHttpResponseException;
    +import org.apache.http.client.methods.HttpPost;
    +import org.apache.http.entity.StringEntity;
    +import org.junit.Assert;
    +import org.junit.BeforeClass;
    +import org.junit.Test;
    +import org.junit.runner.RunWith;
    +import org.xnio.OptionMap;
    +import org.xnio.Options;
    +
    +/**
    + *
    + * Tests to ensure no read timeout after an exact read of the content-length
    + *
    + * @author Stuart Douglas
    + * @author Flavia Rainone
    + * @author Aaron Ogburn
    + */
    +@RunWith(DefaultServer.class)
    +@HttpOneOnly
    +public class ExactLengthReadTimeoutTestCase {
    +
    +    private static volatile String message;
    +
    +    private static final String DATA = "1234567890ABCDEF";
    +
    +    private static final int DATA_MULTIPLE = 2048;
    +
    +    @BeforeClass
    +    public static void setup() {
    +        final BlockingHandler blockingHandler = new BlockingHandler();
    +        DefaultServer.setRootHandler(blockingHandler);
    +        blockingHandler.setRootHandler(new HttpHandler() {
    +            @Override
    +            public void handleRequest(final HttpServerExchange exchange) {
    +                try {
    +                    final OutputStream outputStream = exchange.getOutputStream();
    +                    final InputStream inputStream =  exchange.getInputStream();
    +
    +                    long length = exchange.getRequestContentLength();
    +                    byte[] b = new byte[DATA_MULTIPLE * DATA.length()];
    +                    int i = 1;
    +                    StringBuilder builder = new StringBuilder();
    +                    // read exact content length
    +                    while (i > 0 && length > 0) {
    +                        i = inputStream.read(b);
    +                        if (i > 0) {
    +                           length -=i;
    +                           builder.append(new String(b, 0, i));
    +                        }
    +                    }
    +
    +                    // this shouldn't cause timeout after complete read
    +                    try {
    +                        Thread.sleep(200);
    +                    }  catch (InterruptedException e) {
    +                        throw new RuntimeException(e);
    +                    }
    +
    +                    Assert.assertEquals(message, builder.toString());
    +                    inputStream.close();
    +                    outputStream.close();
    +                } catch (IOException e) {
    +                    exchange.getResponseHeaders().put(Headers.CONNECTION, "close");
    +                    exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
    +                    throw new RuntimeException(e);
    +                }
    +            }
    +        });
    +    }
    +
    +    @DefaultServer.BeforeServerStarts
    +    public static void beforeClass() {
    +        DefaultServer.setServerOptions(OptionMap.create(Options.READ_TIMEOUT, 100));
    +    }
    +
    +    @DefaultServer.AfterServerStops
    +    public static void afterClass() {
    +        DefaultServer.setServerOptions(OptionMap.EMPTY);
    +    }
    +
    +    @Test
    +    public void testExactLengthReadTimeout() throws InterruptedException, IOException {
    +        StringBuilder builder = new StringBuilder(1000 * DATA.length());
    +
    +        for (int i = 0; i < DATA_MULTIPLE; ++i) {
    +            try {
    +                builder.append(DATA);
    +            } catch (Throwable e) {
    +                throw new RuntimeException("test failed with i equal to " + i, e);
    +            }
    +        }
    +
    +        message = builder.toString();
    +        final TestHttpClient client = new TestHttpClient();
    +        try {
    +            HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL() + "/path");
    +            post.setEntity(new StringEntity(message));
    +            post.addHeader(Headers.CONNECTION_STRING, "close");
    +            boolean socketFailure = false;
    +            try {
    +                // Request should succeed.
    +                HttpResponse result = client.execute(post);
    +                Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
    +            } catch (NoHttpResponseException e) {
    +                Assert.fail("No response was received, this was presumably caused by read-timeout closing the connection.");
    +            }
    +        } finally {
    +            client.getConnectionManager().shutdown();
    +        }
    +    }
    +}
    
  • core/src/test/java/io/undertow/server/handlers/blocking/SimpleBlockingServerTestCase.java+13 0 modified
    @@ -23,6 +23,7 @@
     import java.io.InputStream;
     import java.io.OutputStream;
     
    +import io.undertow.UndertowOptions;
     import io.undertow.io.IoCallback;
     import io.undertow.io.Sender;
     import io.undertow.server.HttpHandler;
    @@ -44,6 +45,7 @@
     import org.junit.BeforeClass;
     import org.junit.Test;
     import org.junit.runner.RunWith;
    +import org.xnio.OptionMap;
     
     /**
      * @author Stuart Douglas
    @@ -57,6 +59,7 @@ public class SimpleBlockingServerTestCase {
         public static void setup() {
             final BlockingHandler blockingHandler = new BlockingHandler();
             DefaultServer.setRootHandler(blockingHandler);
    +
             blockingHandler.setRootHandler(new HttpHandler() {
                 @Override
                 public void handleRequest(final HttpServerExchange exchange) {
    @@ -112,6 +115,16 @@ public void onException(final HttpServerExchange exchange, final Sender sender,
             });
         }
     
    +    @DefaultServer.BeforeServerStarts
    +    public static void setupServer() {
    +        DefaultServer.setServerOptions(OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, -1L));
    +    }
    +
    +    @DefaultServer.AfterServerStops
    +    public static void cleanup() {
    +        DefaultServer.setServerOptions(OptionMap.EMPTY);
    +    }
    +
         @Test
         public void sendHttpRequest() throws IOException {
             message = "My HTTP Request!";
    
  • core/src/test/java/io/undertow/server/handlers/ForwardedHandlerTestCase.java+17 0 modified
    @@ -1,3 +1,20 @@
    +/*
    + * JBoss, Home of Professional Open Source.
    + * Copyright 2025 Red Hat, Inc., and individual contributors
    + * as indicated by the @author tags.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + *  Unless required by applicable law or agreed to in writing, software
    + *  distributed under the License is distributed on an "AS IS" BASIS,
    + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + *  See the License for the specific language governing permissions and
    + *  limitations under the License.
    + */
     package io.undertow.server.handlers;
     
     import io.undertow.server.HttpHandler;
    
  • core/src/test/java/io/undertow/server/handlers/HostHandlerTestCase.java+282 0 added
    @@ -0,0 +1,282 @@
    +/*
    + * JBoss, Home of Professional Open Source.
    + * Copyright 2025 Red Hat, Inc., and individual contributors
    + * as indicated by the @author tags.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + *  Unless required by applicable law or agreed to in writing, software
    + *  distributed under the License is distributed on an "AS IS" BASIS,
    + *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + *  See the License for the specific language governing permissions and
    + *  limitations under the License.
    + */
    +package io.undertow.server.handlers;
    +
    +import java.io.IOException;
    +
    +import org.apache.http.HttpHost;
    +import org.apache.http.HttpResponse;
    +import org.apache.http.client.ClientProtocolException;
    +import org.apache.http.client.methods.HttpGet;
    +import org.apache.http.client.protocol.HttpClientContext;
    +import org.apache.http.conn.params.ConnRoutePNames;
    +import org.junit.Assert;
    +import org.junit.BeforeClass;
    +import org.junit.Ignore;
    +import org.junit.Test;
    +import org.junit.runner.RunWith;
    +
    +import io.undertow.Handlers;
    +import io.undertow.server.HttpHandler;
    +import io.undertow.server.HttpServerExchange;
    +import io.undertow.testutils.DefaultServer;
    +import io.undertow.testutils.ProxyIgnore;
    +import io.undertow.testutils.TestHttpClient;
    +import io.undertow.util.Headers;
    +
    +@RunWith(DefaultServer.class)
    +@ProxyIgnore
    +public class HostHandlerTestCase {
    +
    +    @BeforeClass
    +    public static void setup() {
    +        DefaultServer.setRootHandler(Handlers.hostHeaderHandler(new HttpHandler() {
    +            @Override
    +            public void handleRequest(HttpServerExchange exchange) throws Exception {
    +                exchange.getResponseSender().send("OK");
    +            }
    +        }));
    +    }
    +
    +    @Test
    +    @Ignore // ignore, since client will add one if there no present
    +    public void testNoHostHeader() throws Exception {
    +        test(new String[] {}, null, 400, null);
    +    }
    +
    +    @Test
    +    public void testTooManyHostHeader() throws Exception {
    +        test(new String[] { "212.138.1.1", "data.com" }, null, 400, null);
    +    }
    +
    +    @Test
    +    public void testIPv4HostHeader() throws Exception {
    +        // dont test bad IPv4, as this will be valid... reg-name
    +        test(new String[] { "212.138.1.1" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPv4AndPortHostHeader() throws Exception {
    +        test(new String[] { "212.138.1.1:80" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPv6HostHeader() throws Exception {
    +        test(new String[] { "[1:2:3:4::]" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPv6AndPortHostHeader() throws Exception {
    +        test(new String[] { "[1:2:3:4::]:80" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPv6HostHeader2() throws Exception {
    +        test(new String[] { "[1:2:3:4:::]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPv6AndPortHostHeader2() throws Exception {
    +        test(new String[] { "[1:2:3:4::]:80000" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_PORT);
    +    }
    +
    +    @Test
    +    public void testIPv6HostHeader3() throws Exception {
    +        test(new String[] { "[1:2:3:4::" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPv6AndPortHostHeader3() throws Exception {
    +        test(new String[] { "[1:2:3:4:::80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPv6HostHeader4() throws Exception {
    +        test(new String[] { "1:2:3:4::]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPv6HostHeader5() throws Exception {
    +        test(new String[] { "1:2:3:4::" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_PORT);
    +    }
    +
    +    @Test
    +    public void testIPv6HostHeader6() throws Exception {
    +        //this will just fall into reg-name
    +        test(new String[] { "1:2:3:4:5:6:7:8" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +    }
    +
    +    @Test
    +    public void testIPv6AndPortHostHeader4() throws Exception {
    +        test(new String[] { "1:2:3:4::]:80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPv6AndPortHostHeader6() throws Exception {
    +        test(new String[] { "1:2:3:4:5:6:7:8]:80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPv6EmbeddedIPv4HostHeader() throws Exception {
    +        test(new String[] { "[1:2:3:4::192.168.32.1]" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPv6EmbeddedIPv4HostHeader2() throws Exception {
    +        test(new String[] { "[1:2:3:4::192.168.32.1]:80" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPv6EmbeddedIPv4HostHeader3() throws Exception {
    +        test(new String[] { "[1:2:3:4::192.355.32.1]:80" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader() throws Exception {
    +        test(new String[] { "[vAF.1:2:3:4::]" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader2() throws Exception {
    +        test(new String[] { "[vG.1:2:3:4::]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader3() throws Exception {
    +        test(new String[] { "[vAF.abdc:abcd_-~AF]" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader4() throws Exception {
    +        test(new String[] { "[vAF.ImVal1.com-._~!$&'()*+,;=:]" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader5() throws Exception {
    +        test(new String[] { "[vAF.]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader6() throws Exception {
    +        test(new String[] { "[vAF]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPvFutureHostHeader7() throws Exception {
    +        test(new String[] { "[v.abcd]" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPRegNameHostHeader() throws Exception {
    +        test(new String[] { "366.66.12.12" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPRegNameHostHeader2() throws Exception {
    +        test(new String[] { "domain.com%20" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPRegNameHostHeader3() throws Exception {
    +        test(new String[] { "domain.com%2" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL);
    +    }
    +
    +    @Test
    +    public void testIPRegNameHostHeader4() throws Exception {
    +        test(new String[] { "doma&n.com%20" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPRegNameHostHeader5() throws Exception {
    +        test(new String[] { "ImVal1.com-._~!$&'()*+,;=" }, null, 200, null);
    +    }
    +
    +    @Test
    +    public void testIPRegNameHostHeader6() throws Exception {
    +        // test userinfo presence?
    +        test(new String[] { "juicyUserInfo@ImVal1.com-._~!$&'()*+,;=" }, null, 400, HostHeaderHandler.STATUS_MALFORMED_IP_LITERAL_BAD_CHARS);
    +    }
    +
    +    @Test
    +    public void testAbsoluteURLBad() throws Exception {
    +        // test userinfo presence?
    +        test(new String[] { "wrong.com:8080" }, new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort()), 400, HostHeaderHandler.STATUS_HOST_NO_MATCH);
    +    }
    +
    +    @Test
    +    public void testAbsoluteURLGood() throws Exception {
    +        // test userinfo presence?
    +        test(new String[] { DefaultServer.getHostAddress() + ":" + DefaultServer.getHostPort() },
    +                new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort()), 200, null);
    +    }
    +
    +    @Test
    +    public void testEmptyHost() throws Exception {
    +        // test userinfo presence?
    +        test(new String[] { "" },
    +                null, 200, null);
    +    }
    +
    +    @Test
    +    public void testEmptyHost2() throws Exception {
    +        // test userinfo presence?
    +        test(new String[] { "" },
    +                new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort()), 400, HostHeaderHandler.STATUS_HOST_NO_MATCH);
    +    }
    +
    +    public void test(final String[] headers, final HttpHost proxy, final int resultCode, final String statusMessage)
    +            throws ClientProtocolException, IOException {
    +        TestHttpClient client = new TestHttpClient();
    +        if (proxy != null) {
    +            client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    +        }
    +        try {
    +            HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL());
    +            for (String i : headers) {
    +                get.addHeader(Headers.HOST_STRING, i);
    +            }
    +            HttpResponse result = client.execute(get, HttpClientContext.create());
    +            Assert.assertEquals(result.getStatusLine().getReasonPhrase(), resultCode, result.getStatusLine().getStatusCode());
    +            if (statusMessage != null) {
    +                Assert.assertEquals(statusMessage, result.getStatusLine().getReasonPhrase());
    +            }
    +
    +        } finally {
    +            client.getConnectionManager().shutdown();
    +        }
    +    }
    +
    +    public void testProxyMode(final String[] headers, final int resultCode, final String statusMessage) throws ClientProtocolException, IOException {
    +        // this has to be done this way in order to trick apache to use absolute form...
    +        HttpHost proxy = new HttpHost(DefaultServer.getHostAddress(), DefaultServer.getHostPort());
    +        TestHttpClient client = new TestHttpClient();
    +        client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
    +        try {
    +            HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL());
    +            for (String i : headers) {
    +                get.addHeader(Headers.HOST_STRING, i);
    +            }
    +            HttpResponse result = client.execute(get, HttpClientContext.create());
    +            Assert.assertEquals(result.getStatusLine().getReasonPhrase(), resultCode, result.getStatusLine().getStatusCode());
    +
    +        } finally {
    +            client.getConnectionManager().shutdown();
    +        }
    +    }
    +
    +}
    
  • core/src/test/java/io/undertow/server/handlers/QueryParametersWithAllowUnescapedCharactersTestCase.java+11 6 modified
    @@ -59,16 +59,21 @@ public static void clearProxyOptions() {
         public static void setQueryStringsArray() {
             // format is: {queryString, expected result}
             queryStrings = new String[][] { new String[] { "/path?unicode=Iñtërnâtiônàližætiøn",
    -                "unicode=Iñtërnâtiônàližætiøn{unicode=>Iñtërnâtiônàližætiøn}" },
    -                new String[] { "/path?a=b&value=bb%20bb", "a=b&value=bb bb{a=>b,value=>bb bb}" },
    +                //"unicode=Iñtërnâtiônàližætiøn{unicode=>Iñtërnâtiônàližætiøn}" },
    +                "unicode=I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0li%C5%BE%C3%A6ti%C3%B8n{unicode=>Iñtërnâtiônàližætiøn}" },
    +                //new String[] { "/path?a=b&value=bb%20bb", "a=b&value=bb bb{a=>b,value=>bb bb}" },
    +                new String[] { "/path?a=b&value=bb%20bb", "a=b&value=bb%20bb{a=>b,value=>bb bb}" },
                     new String[] { "/path?a=b&value=bb&value=cc", "a=b&value=bb&value=cc{a=>b,value=>[bb,cc]}" },
                     new String[] { "/path?&a=b&value=bb&&value=cc", "&a=b&value=bb&&value=cc{a=>b,value=>[bb,cc]}" },
    -                // Specifing some query parameters with empty by intentional for the test purpose. These should be ignored.
    +                // Specifying some query parameters with empty by intentional for the test purpose. These should be ignored.
                     new String[] { "/path?a=b&value=bb&value=cc&s%20&t%20",
    -                        "a=b&value=bb&value=cc&s &t {a=>b,s =>,t =>,value=>[bb,cc]}" },
    +                        //"a=b&value=bb&value=cc&s &t {a=>b,s =>,t =>,value=>[bb,cc]}" },
    +                        "a=b&value=bb&value=cc&s%20&t%20{a=>b,s =>,t =>,value=>[bb,cc]}" },
                     new String[] { "/path?a=b&value=bb&value=cc&s%20&t%20&",
    -                        "a=b&value=bb&value=cc&s &t &{a=>b,s =>,t =>,value=>[bb,cc]}" },
    +                        //"a=b&value=bb&value=cc&s &t &{a=>b,s =>,t =>,value=>[bb,cc]}" },
    +                        "a=b&value=bb&value=cc&s%20&t%20&{a=>b,s =>,t =>,value=>[bb,cc]}" },
                     new String[] { "/path?a=b&value=bb&value=cc&s%20&t%20&u",
    -                        "a=b&value=bb&value=cc&s &t &u{a=>b,s =>,t =>,u=>,value=>[bb,cc]}" } };
    +                        //"a=b&value=bb&value=cc&s &t &u{a=>b,s =>,t =>,u=>,value=>[bb,cc]}" } };
    +                        "a=b&value=bb&value=cc&s%20&t%20&u{a=>b,s =>,t =>,u=>,value=>[bb,cc]}" } };
         }
     }
    
  • core/src/test/java/io/undertow/server/handlers/ReceiverTestCase.java+14 0 modified
    @@ -35,6 +35,7 @@
     import org.junit.BeforeClass;
     import org.junit.Test;
     import org.junit.runner.RunWith;
    +import org.xnio.OptionMap;
     
     import java.io.IOException;
     import java.io.OutputStream;
    @@ -44,6 +45,8 @@
     import java.util.concurrent.LinkedBlockingDeque;
     import java.util.concurrent.TimeUnit;
     
    +import static io.undertow.UndertowOptions.MAX_ENTITY_SIZE;
    +
     /**
      * @author Stuart Douglas
      */
    @@ -61,6 +64,17 @@ public void error(HttpServerExchange exchange, IOException e) {
             }
         };
     
    +    @DefaultServer.BeforeServerStarts
    +    public static void setupServer() {
    +        DefaultServer.setServerOptions(OptionMap.create(MAX_ENTITY_SIZE, -1L));
    +    }
    +
    +    @DefaultServer.AfterServerStops
    +    public static void cleanup() {
    +        DefaultServer.setServerOptions(OptionMap.EMPTY);
    +    }
    +
    +
         @BeforeClass
         public static void setup() {
             HttpHandler testFullString = new HttpHandler() {
    
  • core/src/test/java/io/undertow/server/protocol/ajp/AjpParsingUnitTestCase.java+2 1 modified
    @@ -126,7 +126,8 @@ public void testCharsetHandling() throws Exception {
             Assert.assertFalse(state.badRequest);
             Assert.assertEquals("/한글이름", result.getRequestPath());
             Assert.assertEquals("/한글이름", result.getRequestURI());
    -        Assert.assertEquals("param=한글이름", result.getQueryString());
    +        Assert.assertEquals("param=한글이름", result.getDecodedQueryString());
    +        Assert.assertEquals("param=í\u0095\u009Cê¸\u0080ì\u009D´ë¦\u0084", result.getQueryString());
         }
     
         @Test
    
  • servlet/src/main/java/io/undertow/servlet/attribute/ServletRelativePathAttribute.java+2 3 modified
    @@ -22,7 +22,6 @@
     import io.undertow.attribute.ExchangeAttributeBuilder;
     import io.undertow.attribute.ReadOnlyAttributeException;
     import io.undertow.attribute.RelativePathAttribute;
    -import io.undertow.attribute.RequestURLAttribute;
     import io.undertow.server.HttpServerExchange;
     import io.undertow.servlet.handlers.ServletRequestContext;
     
    @@ -48,12 +47,12 @@ private ServletRelativePathAttribute() {
         public String readAttribute(final HttpServerExchange exchange) {
             ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
             if(src == null) {
    -            return RequestURLAttribute.INSTANCE.readAttribute(exchange);
    +            return RelativePathAttribute.INSTANCE.readAttribute(exchange);
             }
             String path = (String) src.getServletRequest().getAttribute(RequestDispatcher.FORWARD_PATH_INFO);
             String sp = (String) src.getServletRequest().getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH);
             if(path == null && sp == null) {
    -            return RequestURLAttribute.INSTANCE.readAttribute(exchange);
    +            return RelativePathAttribute.INSTANCE.readAttribute(exchange);
             }
             if(sp == null) {
                 return path;
    
  • servlet/src/main/java/io/undertow/servlet/attribute/ServletRequestLineAttribute.java+2 2 modified
    @@ -57,9 +57,9 @@ public String readAttribute(final HttpServerExchange exchange) {
             if (query != null && !query.isEmpty()) {
                 sb.append('?');
                 sb.append(query);
    -        } else if (!exchange.getQueryString().isEmpty()) {
    +        } else if (!exchange.getDecodedQueryString().isEmpty()) {
                 sb.append('?');
    -            sb.append(exchange.getQueryString());
    +            sb.append(exchange.getDecodedQueryString());
             }
             sb.append(' ')
                     .append(exchange.getProtocol().toString()).toString();
    
  • servlet/src/main/java/io/undertow/servlet/spec/HttpServletRequestImpl.java+1 1 modified
    @@ -309,7 +309,7 @@ public String getContextPath() {
     
         @Override
         public String getQueryString() {
    -        return exchange.getQueryString().isEmpty() ? null : exchange.getQueryString();
    +        return exchange.getDecodedQueryString().isEmpty() ? null : exchange.getDecodedQueryString();
         }
     
         @Override
    
  • servlet/src/main/java/io/undertow/servlet/util/DispatchUtils.java+4 4 modified
    @@ -229,18 +229,18 @@ private static String assignRequestPath(final String path, final HttpServletRequ
             if (include) {
                 // include does not modify exchange paths, just add the query string and request uri
                 // the rest of attributes are added via the match later
    -            requestImpl.setAttribute(INCLUDE_QUERY_STRING, fake.getQueryString());
    +            requestImpl.setAttribute(INCLUDE_QUERY_STRING, fake.getDecodedQueryString());
                 requestImpl.setAttribute(INCLUDE_REQUEST_URI, fake.getRequestURI());
             } else {
                 exchange.setRelativePath(newRequestPath);
                 exchange.setRequestPath(fake.getRequestPath());
                 exchange.setRequestURI(fake.getRequestURI());
    -            if (!fake.getQueryString().isEmpty()) {
    -                exchange.setQueryString(fake.getQueryString());
    +            if (!fake.getDecodedQueryString().isEmpty()) {
    +                exchange.setDecodedQueryString(fake.getDecodedQueryString());
                 }
             }
             // both forward and include merge parameters by spec
    -        if (!fake.getQueryString().isEmpty()) {
    +        if (!fake.getDecodedQueryString().isEmpty()) {
                 final Map<String, Deque<String>> merged = QueryParameterUtils.mergeQueryParameters(fake.getQueryParameters(), exchange.getQueryParameters());
                 requestImpl.setQueryParameters(null);
                 exchange.getQueryParameters().clear();
    
  • servlet/src/test/java/io/undertow/servlet/test/multipart/MultiPartTestCase.java+16 3 modified
    @@ -32,7 +32,6 @@
     import io.undertow.servlet.test.util.DeploymentUtils;
     import io.undertow.testutils.DefaultServer;
     import io.undertow.testutils.HttpClientUtils;
    -import io.undertow.testutils.ProxyIgnore;
     import io.undertow.testutils.TestHttpClient;
     import io.undertow.util.StatusCodes;
     import org.apache.http.HttpResponse;
    @@ -44,18 +43,20 @@
     import org.apache.http.entity.mime.content.StringBody;
     import org.jboss.logging.Logger;
     import org.junit.Assert;
    +import org.junit.Assume;
     import org.junit.BeforeClass;
     import org.junit.Test;
     import org.junit.runner.RunWith;
    +import org.xnio.OptionMap;
     
    +import static io.undertow.UndertowOptions.MULTIPART_MAX_ENTITY_SIZE;
     import static io.undertow.servlet.Servlets.multipartConfig;
     import static io.undertow.servlet.Servlets.servlet;
     
     /**
      * @author Stuart Douglas
      */
     @RunWith(DefaultServer.class)
    -@ProxyIgnore
     public class MultiPartTestCase {
     
     
    @@ -82,6 +83,16 @@ public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servl
                             .setMultipartConfig(multipartConfig(null, 3, 0, 0)));
         }
     
    +    @DefaultServer.BeforeServerStarts
    +    public static void setupServer() {
    +        DefaultServer.setServerOptions(OptionMap.create(MULTIPART_MAX_ENTITY_SIZE, -1L));
    +    }
    +
    +    @DefaultServer.AfterServerStops
    +    public static void cleanup() {
    +        DefaultServer.setServerOptions(OptionMap.EMPTY);
    +    }
    +
         @Test
         public void testMultiPartRequestWithNoMultipartConfig() throws IOException {
             TestHttpClient client = new TestHttpClient();
    @@ -180,7 +191,9 @@ public void testMultiPartRequestWithAddedServlet() throws IOException {
         }
     
         @Test
    -    public void testMultiPartRequestToLarge() throws IOException {
    +    public void testMultiPartRequestTooLarge() throws IOException {
    +        // FIXME UNDERTOW-2572
    +        Assume.assumeFalse(DefaultServer.isH2());
             TestHttpClient client = new TestHttpClient();
             try {
                 String uri = DefaultServer.getDefaultServerURL() + "/servletContext/2";
    
  • servlet/src/test/java/io/undertow/servlet/test/streams/ServletInputStreamEarlyCloseClientSideTestCase.java+15 6 modified
    @@ -18,31 +18,31 @@
     
     package io.undertow.servlet.test.streams;
     
    +import io.undertow.UndertowOptions;
     import io.undertow.servlet.api.ServletInfo;
     import io.undertow.servlet.test.util.DeploymentUtils;
     import io.undertow.testutils.DefaultServer;
    -import io.undertow.testutils.HttpOneOnly;
     import io.undertow.testutils.TestHttpClient;
     import org.junit.Assert;
    -import org.junit.Assume;
     import org.junit.BeforeClass;
     import org.junit.Test;
     import org.junit.runner.RunWith;
    +import org.xnio.OptionMap;
     
     import javax.servlet.ServletException;
     import java.io.OutputStream;
     import java.net.Socket;
    +import java.nio.charset.StandardCharsets;
     import java.util.concurrent.TimeUnit;
     
     /**
      * Tests the behaviour of the input stream when the connection is closed on the client side
      * <p>
    - * https://issues.jboss.org/browse/WFLY-4827
    + * (see WFLY-4827)
      *
      * @author Stuart Douglas
      */
     @RunWith(DefaultServer.class)
    -@HttpOneOnly
     public class ServletInputStreamEarlyCloseClientSideTestCase {
     
         public static final String SERVLET = "servlet";
    @@ -54,9 +54,18 @@ public static void setup() throws ServletException {
                             .addMapping("/" + SERVLET));
         }
     
    +    @DefaultServer.BeforeServerStarts
    +    public static void setupServer() {
    +        DefaultServer.setServerOptions(OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, -1L));
    +    }
    +
    +    @DefaultServer.AfterServerStops
    +    public static void cleanup() {
    +        DefaultServer.setServerOptions(OptionMap.EMPTY);
    +    }
    +
         @Test
         public void testServletInputStreamEarlyClose() throws Exception {
    -        Assume.assumeFalse(DefaultServer.isH2());
             TestHttpClient client = new TestHttpClient();
             EarlyCloseClientServlet.reset();
             try (Socket socket = new Socket()) {
    @@ -70,7 +79,7 @@ public void testServletInputStreamEarlyClose() throws Exception {
                     String request = "POST /servletContext/" + SERVLET + " HTTP/1.1\r\nHost:localhost\r\nContent-Length:" + sb.length() + 100 + "\r\n\r\n" + sb.toString();
                     OutputStream outputStream = socket.getOutputStream();
     
    -                outputStream.write(request.getBytes("US-ASCII"));
    +                outputStream.write(request.getBytes(StandardCharsets.US_ASCII));
                     outputStream.flush();
                     socket.close();
     
    
  • websockets-jsr/src/main/java/io/undertow/websockets/jsr/FrameHandler.java+4 1 modified
    @@ -269,8 +269,9 @@ private void invokeTextHandler(final BufferedTextMessage data, final HandlerWrap
                 @Override
                 public void run() {
                     MessageHandler mHandler = handler.getHandler();
    +                final ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
                     try {
    -
    +                    Thread.currentThread().setContextClassLoader(mHandler.getClass().getClassLoader());
                         if (mHandler instanceof MessageHandler.Partial) {
                             if (handler.decodingNeeded) {
                                 Object object = getSession().getEncoding().decodeText(handler.getMessageType(), message);
    @@ -292,6 +293,8 @@ public void run() {
                         }
                     } catch (Exception e) {
                         invokeOnError(e);
    +                } finally {
    +                    Thread.currentThread().setContextClassLoader(oldCL);
                     }
                 }
             });
    
  • websockets-jsr/src/main/java/io/undertow/websockets/jsr/ServerWebSocketContainer.java+2 2 modified
    @@ -28,7 +28,7 @@
     import io.undertow.servlet.util.ConstructorInstanceFactory;
     import io.undertow.servlet.util.ImmediateInstanceHandle;
     import io.undertow.servlet.websockets.ServletWebSocketHttpExchange;
    -import io.undertow.util.CopyOnWriteMap;
    +import io.undertow.util.WeakCopyOnWriteMap;
     import io.undertow.util.PathTemplate;
     import io.undertow.util.StatusCodes;
     import io.undertow.websockets.WebSocketExtension;
    @@ -104,7 +104,7 @@ public class ServerWebSocketContainer implements ServerContainer, Closeable {
     
         private final ClassIntrospecter classIntrospecter;
     
    -    private final Map<Class<?>, ConfiguredClientEndpoint> clientEndpoints = new CopyOnWriteMap<>();
    +    private final Map<Class<?>, ConfiguredClientEndpoint> clientEndpoints = new WeakCopyOnWriteMap<>();
     
         private final List<ConfiguredServerEndpoint> configuredServerEndpoints = new ArrayList<>();
         private final Set<Class<?>> annotatedEndpointClasses = new HashSet<>();
    
  • websockets-jsr/src/main/java/io/undertow/websockets/jsr/UndertowContainerProvider.java+28 16 modified
    @@ -84,26 +84,38 @@ static ServerWebSocketContainer getDefaultContainer() {
                     //this is not great, as we have no way to control the lifecycle
                     //but there is not much we can do
                     //todo: what options should we use here?
    -                ByteBufferPool buffers = new DefaultByteBufferPool(directBuffers, 1024, 100, 12);
    -                defaultContainer = new ServerWebSocketContainer(defaultIntrospector, UndertowContainerProvider.class.getClassLoader(), new Supplier<XnioWorker>() {
    -                    volatile XnioWorker worker;
    -
    -                    @Override
    -                    public XnioWorker get() {
    -                        if(worker == null) {
    -                            synchronized (this) {
    -                                if(worker == null) {
    -                                    try {
    -                                        worker = Xnio.getInstance().createWorker(OptionMap.create(Options.THREAD_DAEMON, true));
    -                                    } catch (IOException e) {
    -                                        throw new RuntimeException(e);
    +                //final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
    +                try {
    +                    //Thread.currentThread().setContextClassLoader(null);
    +                    ByteBufferPool buffers = new DefaultByteBufferPool(directBuffers, 1024, 100, 12);
    +                    defaultContainer = new ServerWebSocketContainer(defaultIntrospector, UndertowContainerProvider.class.getClassLoader(), new Supplier<XnioWorker>() {
    +                        volatile XnioWorker worker;
    +
    +                        @Override
    +                        public XnioWorker get() {
    +                            final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
    +                            try {
    +                                Thread.currentThread().setContextClassLoader(null);
    +                                if (worker == null) {
    +                                    synchronized (this) {
    +                                        if (worker == null) {
    +                                            try {
    +                                                worker = Xnio.getInstance().createWorker(OptionMap.create(Options.THREAD_DAEMON, true));
    +                                            } catch (IOException e) {
    +                                                throw new RuntimeException(e);
    +                                            }
    +                                        }
                                         }
                                     }
    +                            } finally {
    +                                Thread.currentThread().setContextClassLoader(tccl);
                                 }
    +                            return worker;
                             }
    -                        return worker;
    -                    }
    -                }, buffers, Collections.EMPTY_LIST, !invokeInIoThread);
    +                    }, buffers, Collections.EMPTY_LIST, !invokeInIoThread);
    +                } finally {
    +                    //Thread.currentThread().setContextClassLoader(tccl);
    +                }
                 }
                 return defaultContainer;
             }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

29

News mentions

0

No linked articles in our index yet.