Skip to content

Commit eeedc34

Browse files
authored
Merge pull request #1918 from fl4via/backport-fixes_2.2.x
[UNDERTOW-2713 / 2667 / 2654 / 2689 / 2698 / 2688] Backport fixes and enhancements to 2.2.x
2 parents 620b953 + f1d7ff3 commit eeedc34

14 files changed

Lines changed: 234 additions & 12 deletions

File tree

CONTRIBUTING.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,13 @@ Only then, you can push it to origin. If you edited a commit that was already in
220220

221221
```bash
222222
git push origin --force UNDERTOW-XXXX-my_cool_feature
223-
```
223+
```
224+
225+
#### Legal
226+
227+
All contributions to this repository are licensed under the Apache License, version 2.0 or later, or, if another license is specified as governing the file or directory being modified, such other license.
228+
229+
All contributions are subject to the Developer Certificate of Origin (DCO). The DCO text is also included verbatim in the dco.txt file in the root directory of the repository.
230+
Compliance with Laws and Regulations
231+
232+
All contributions must comply with applicable laws and regulations, including U.S. export control and sanctions restrictions. For background, see the Linux Foundation’s guidance: Navigating Global Regulations and Open Source: US OFAC Sanctions.

core/pom.xml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
<https>false</https>
4343
<openssl>false</openssl>
4444
<test.ipv6>false</test.ipv6>
45+
<test.long.runners>true</test.long.runners>
4546
<bufferSize>8192</bufferSize>
4647
<libraryPath></libraryPath>
4748
<java.library.path></java.library.path>
@@ -108,7 +109,7 @@
108109
<artifactId>junit</artifactId>
109110
<scope>test</scope>
110111
</dependency>
111-
112+
112113
<dependency>
113114
<groupId>org.apache.directory.server</groupId>
114115
<artifactId>apacheds-all</artifactId>
@@ -245,6 +246,7 @@
245246
<test.https>${https}</test.https>
246247
<test.openssl>${openssl}</test.openssl>
247248
<test.bufferSize>${bufferSize}</test.bufferSize>
249+
<test.long.runners>${test.long.runners}</test.long.runners>
248250
<default.server.address>localhost</default.server.address>
249251
<default.server.port>7777</default.server.port>
250252
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
@@ -260,7 +262,17 @@
260262
</build>
261263

262264
<profiles>
263-
265+
<profile>
266+
<id>disable.long.runners</id>
267+
<activation>
268+
<property>
269+
<name>dlr</name>
270+
</property>
271+
</activation>
272+
<properties>
273+
<test.long.runners>false</test.long.runners>
274+
</properties>
275+
</profile>
264276
<profile>
265277
<id>mac</id>
266278
<activation>

core/src/main/java/io/undertow/client/http2/Http2ClientExchange.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.undertow.util.AbstractAttachable;
3737
import io.undertow.util.HeaderMap;
3838
import io.undertow.util.Protocols;
39+
import io.undertow.util.StatusCodes;
3940

4041
/**
4142
* @author Stuart Douglas
@@ -140,6 +141,6 @@ ClientResponse createResponse(Http2StreamSourceChannel result) {
140141
final String status = result.getHeaders().getFirst(Http2Channel.STATUS);
141142
int statusCode = Integer.parseInt(status);
142143
headers.remove(Http2Channel.STATUS);
143-
return new ClientResponse(statusCode, status.substring(3), Protocols.HTTP_2_0, headers);
144+
return new ClientResponse(statusCode, StatusCodes.getReason(statusCode), Protocols.HTTP_2_0, headers);
144145
}
145146
}

core/src/main/java/io/undertow/server/handlers/proxy/ProxyHandler.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
import javax.net.ssl.SSLPeerUnverifiedException;
7171
import java.io.Closeable;
7272
import java.io.IOException;
73+
import java.net.Inet6Address;
7374
import java.net.InetSocketAddress;
7475
import java.net.SocketAddress;
7576
import java.nio.channels.Channel;
@@ -577,7 +578,11 @@ public void run() {
577578

578579
if(rewriteHostHeader) {
579580
InetSocketAddress targetAddress = clientConnection.getConnection().getPeerAddress(InetSocketAddress.class);
580-
request.getRequestHeaders().put(Headers.HOST, targetAddress.getHostString() + ":" + targetAddress.getPort());
581+
if(targetAddress.getAddress() instanceof Inet6Address) {
582+
request.getRequestHeaders().put(Headers.HOST, NetworkUtils.formatPossibleIpv6Address(targetAddress.getHostString()) + ":" + targetAddress.getPort());
583+
} else {
584+
request.getRequestHeaders().put(Headers.HOST, targetAddress.getHostString() + ":" + targetAddress.getPort());
585+
}
581586
request.getRequestHeaders().put(Headers.X_FORWARDED_HOST, exchange.getRequestHeaders().getFirst(Headers.HOST));
582587
}
583588
if(log.isDebugEnabled()) {
@@ -728,6 +733,7 @@ public void completed(final ClientExchange result) {
728733
final HeaderMap inboundResponseHeaders = response.getResponseHeaders();
729734
final HeaderMap outboundResponseHeaders = exchange.getResponseHeaders();
730735
exchange.setStatusCode(response.getResponseCode());
736+
exchange.setReasonPhrase(response.getStatus());
731737
copyHeaders(exchange, outboundResponseHeaders, inboundResponseHeaders);
732738

733739
if (exchange.isUpgrade()) {

core/src/main/java/io/undertow/server/protocol/http2/Http2ServerConnection.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,6 @@
8989
*/
9090
public class Http2ServerConnection extends ServerConnection {
9191

92-
private static final HttpString STATUS = new HttpString(":status");
93-
9492
private final Http2Channel channel;
9593
private final Http2StreamSourceChannel requestChannel;
9694
private final Http2DataStreamSinkChannel responseChannel;
@@ -193,7 +191,7 @@ public StreamSinkConduit wrap(ConduitFactory<StreamSinkConduit> factory, HttpSer
193191

194192
HeaderMap headers = newExchange.getResponseHeaders();
195193
DateUtils.addDateHeaderIfRequired(exchange);
196-
headers.add(STATUS, exchange.getStatusCode());
194+
headers.add(Http2Channel.STATUS, exchange.getStatusCode());
197195
Connectors.flattenCookies(exchange);
198196
Http2HeadersStreamSinkChannel sink = new Http2HeadersStreamSinkChannel(channel, requestChannel.getStreamId(), headers);
199197

@@ -333,7 +331,7 @@ protected ConduitStreamSourceChannel getSourceChannel() {
333331
protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) {
334332
HeaderMap headers = responseChannel.getHeaders();
335333
DateUtils.addDateHeaderIfRequired(exchange);
336-
headers.add(STATUS, exchange.getStatusCode());
334+
headers.add(Http2Channel.STATUS, exchange.getStatusCode());
337335
Connectors.flattenCookies(exchange);
338336
if(!Connectors.isEntityBodyAllowed(exchange)) {
339337
//we are not allowed to send an entity body for some requests

core/src/main/java/io/undertow/util/ByteRange.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public static ByteRange parse(String rangeHeader) {
8585
//represents the last N bytes
8686
//internally we represent this using a -1 as the start position
8787
long val = Long.parseLong(part.substring(1));
88-
if(val < 0) {
88+
if(val <= 0) {
8989
UndertowLogger.REQUEST_LOGGER.debugf("Invalid range spec %s", rangeHeader);
9090
return null;
9191
}
@@ -132,9 +132,15 @@ public RangeResponseResult getResponseResult(final long resourceContentLength, S
132132
long end = getEnd(0);
133133
long rangeLength;
134134
if(ifRange != null && !ifRange.isEmpty()) {
135+
// RFC 9110 requires strong comparison for If-Range
136+
// Weak ETags (W/"...") should not match
137+
if(ifRange.length() > 2 && ifRange.startsWith("W/")){
138+
return null;
139+
}
140+
135141
if(ifRange.charAt(0) == '"') {
136142
//entity tag
137-
if(eTag != null && !eTag.equals(ifRange)) {
143+
if(eTag == null || !eTag.equals(ifRange)) {
138144
return null;
139145
}
140146
} else {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2025 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package io.undertow.server.handlers;
19+
20+
import java.io.IOException;
21+
import java.util.concurrent.ExecutionException;
22+
23+
import org.apache.http.HttpResponse;
24+
import org.apache.http.client.methods.HttpGet;
25+
import org.junit.Assert;
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
30+
import io.undertow.server.HttpHandler;
31+
import io.undertow.server.HttpServerExchange;
32+
import io.undertow.testutils.DefaultServer;
33+
import io.undertow.testutils.HttpOneOnly;
34+
import io.undertow.testutils.TestHttpClient;
35+
import io.undertow.util.StatusCodes;
36+
37+
@RunWith(DefaultServer.class)
38+
@HttpOneOnly // Http2 does NOT have way to transfer status: https://httpwg.org/specs/rfc7540.html#rfc.section.8.1.2.4
39+
// https://www.rfc-editor.org/rfc/rfc9113.html#name-response-pseudo-header-fiel
40+
public class PreseverStatusTestCase {
41+
private static final String CUSTOM_REASON = "Its just a flesh wound";
42+
43+
@Before
44+
public void setup() {
45+
46+
DefaultServer.setRootHandler(new BlockingHandler(new HttpHandler() {
47+
48+
@Override
49+
public void handleRequest(HttpServerExchange exchange) throws Exception {
50+
exchange.setStatusCode(StatusCodes.TOO_MANY_REQUESTS);
51+
exchange.setReasonPhrase(CUSTOM_REASON);
52+
}}));
53+
}
54+
55+
@Test
56+
public void testWindowSliding() throws ExecutionException, InterruptedException {
57+
TestHttpClient client = new TestHttpClient();
58+
try {
59+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL());
60+
HttpResponse result = client.execute(get);
61+
Assert.assertEquals(StatusCodes.TOO_MANY_REQUESTS, result.getStatusLine().getStatusCode());
62+
Assert.assertEquals(CUSTOM_REASON, result.getStatusLine().getReasonPhrase());
63+
} catch (IOException e) {
64+
throw new RuntimeException(e);
65+
} finally {
66+
client.getConnectionManager().shutdown();
67+
}
68+
}
69+
}

core/src/test/java/io/undertow/server/handlers/RangeRequestTestCase.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,16 @@ public void runTest(String path, boolean etag) throws IOException, InterruptedEx
265265
response = EntityUtils.toString(result.getEntity());
266266
Assert.assertEquals("0123456789", response);
267267
Assert.assertNull(result.getFirstHeader(Headers.CONTENT_RANGE_STRING));
268+
269+
// Test weak ETag in If-Range - should return full content (RFC 9110 requires strong comparison)
270+
get = new HttpGet(DefaultServer.getDefaultServerURL() + path);
271+
get.addHeader(Headers.RANGE_STRING, "bytes=2-3");
272+
get.addHeader(Headers.IF_RANGE_STRING, "W/\"someetag\"");
273+
result = client.execute(get);
274+
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
275+
response = EntityUtils.toString(result.getEntity());
276+
Assert.assertEquals("0123456789", response);
277+
Assert.assertNull(result.getFirstHeader(Headers.CONTENT_RANGE_STRING));
268278
}
269279
} finally {
270280
client.getConnectionManager().shutdown();

core/src/test/java/io/undertow/server/handlers/proxy/ProxyHandlerXForwardedForTestCase.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static io.undertow.Handlers.jvmRoute;
44
import static io.undertow.Handlers.path;
55

6+
import java.net.InetAddress;
67
import java.net.URI;
78

89
import org.apache.http.HttpResponse;
@@ -197,6 +198,14 @@ public void testRewriteHostHeader() throws Exception {
197198
Assert.assertEquals("http", result.getFirstHeader(Headers.X_FORWARDED_PROTO.toString()).getValue());
198199
Assert.assertEquals(String.format("%s:%d", DefaultServer.getHostAddress(), port), result.getFirstHeader(Headers.X_FORWARDED_HOST.toString()).getValue());
199200
Assert.assertEquals(DefaultServer.getDefaultServerAddress().getAddress().getHostAddress(), result.getFirstHeader(Headers.X_FORWARDED_FOR.toString()).getValue());
201+
final String resultHostHeader = result.getFirstHeader(Headers.HOST_STRING).getValue();
202+
final int lastInd = resultHostHeader.lastIndexOf(":"); //ipv6 will have more :, we cant split on it.
203+
Assert.assertNotEquals(-1, lastInd);
204+
final String[] parts = new String[] {resultHostHeader.substring(0,lastInd), resultHostHeader.substring(lastInd+1)};
205+
final InetAddress resultAddress = InetAddress.getByName(parts[0]);
206+
final InetAddress hostAddress = DefaultServer.getDefaultServerAddress().getAddress();
207+
Assert.assertEquals(hostAddress.toString(), resultAddress.toString());
208+
Assert.assertEquals(handlerPort+"", parts[1]);
200209

201210
} finally {
202211
client.getConnectionManager().shutdown();
@@ -240,6 +249,9 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
240249

241250
if (exchange.getRequestHeaders().contains(Headers.X_FORWARDED_PORT))
242251
exchange.getResponseHeaders().put(Headers.X_FORWARDED_PORT, exchange.getRequestHeaders().getFirst(Headers.X_FORWARDED_PORT));
252+
253+
if (exchange.getRequestHeaders().contains(Headers.HOST))
254+
exchange.getResponseHeaders().put(Headers.HOST, exchange.getRequestHeaders().getFirst(Headers.HOST));
243255
}
244256
}
245257
}

core/src/test/java/io/undertow/testutils/DefaultServer.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ public class DefaultServer extends BlockJUnit4ClassRunner {
208208
private static final boolean single = Boolean.getBoolean("test.single");
209209
private static final boolean openssl = Boolean.getBoolean("test.openssl");
210210
private static final boolean ipv6 = Boolean.getBoolean("test.ipv6");
211+
private static final boolean testLongRunners = Boolean.getBoolean("test.long.runners");
211212
private static final int runs = Integer.getInteger("test.runs", 1);
212213

213214
private static final DelegatingHandler rootHandler = new DelegatingHandler();
@@ -725,6 +726,18 @@ protected void runChild(FrameworkMethod method, RunNotifier notifier) {
725726
return;
726727
}
727728
}
729+
730+
if(!testLongRunners) {
731+
IgnoreLongRunning ignoreLongRunners = method.getAnnotation(IgnoreLongRunning.class);
732+
if (ignoreLongRunners == null) {
733+
ignoreLongRunners = method.getMethod().getDeclaringClass().getAnnotation(IgnoreLongRunning.class);
734+
}
735+
736+
if(ignoreLongRunners != null) {
737+
notifier.fireTestIgnored(describeChild(method));
738+
return;
739+
}
740+
}
728741
try {
729742
if (runs > 1) {
730743
Statement statement = methodBlock(method);

0 commit comments

Comments
 (0)