Skip to content

Commit 09a32e5

Browse files
committed
[UNDERTOW-2536] Fix include parameters on error and add rudimentary test
1 parent 85cb914 commit 09a32e5

File tree

8 files changed

+614
-0
lines changed

8 files changed

+614
-0
lines changed

servlet/src/main/java/io/undertow/servlet/spec/RequestDispatcherImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,13 @@ private void error(ServletRequestContext servletRequestContext, final ServletReq
401401
final ServletRequest oldRequest = servletRequestContext.getServletRequest();
402402
final ServletResponse oldResponse = servletRequestContext.getServletResponse();
403403

404+
request.removeAttribute(INCLUDE_REQUEST_URI);
405+
request.removeAttribute(INCLUDE_CONTEXT_PATH);
406+
request.removeAttribute(INCLUDE_SERVLET_PATH);
407+
request.removeAttribute(INCLUDE_PATH_INFO);
408+
request.removeAttribute(INCLUDE_QUERY_STRING);
409+
request.removeAttribute(INCLUDE_MAPPING);
410+
404411
ServletPathMatch pathMatch;
405412
try {
406413
pathMatch = DispatchUtils.dispatchError(path, servletName, exception, message, requestImpl, responseImpl, servletContext);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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.servlet.test.dispatcher.attributes;
19+
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertNull;
22+
import static org.junit.Assert.assertTrue;
23+
24+
import java.util.Arrays;
25+
import java.util.Map;
26+
import java.util.TreeMap;
27+
28+
public abstract class AttributeComparisonTestBase {
29+
30+
protected void testAttributes(final Map<String, String> expected, final String incoming) throws RuntimeException {
31+
32+
final Map<String, String> incomingParameters = new TreeMap<String, String>();
33+
final Map<String, String> expectedParameters = new TreeMap<String, String>();
34+
expectedParameters.putAll(expected);
35+
System.err.println("INCOMING: "+incoming);
36+
final String[] array = incoming.split("\n");
37+
System.err.println("INCOMING: "+Arrays.toString(array));
38+
for (String kvp : array) {
39+
// this might not be best way, but...
40+
final String[] split = kvp.split(";");
41+
final String splitValue = split.length == 2 ? split[1] : ""; //thats empty string.
42+
// sanity check
43+
final String errorIfNotNull = incomingParameters.put(split[0], splitValue);
44+
// should not happen?
45+
assertNull("Doubled value of key[" + split[0] + "] = [" + errorIfNotNull + "," + splitValue + "]", errorIfNotNull);
46+
assertTrue("Expected parameters does not contain '" + split[0] + "'", expectedParameters.containsKey(split[0]));
47+
assertEquals(expectedParameters.remove(split[0]), splitValue);
48+
}
49+
assertTrue("Too many expected parameters: " + expectedParameters, expectedParameters.size() == 0);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
19+
package io.undertow.servlet.test.dispatcher.attributes;
20+
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertNotNull;
23+
24+
import java.io.IOException;
25+
import java.util.Map;
26+
import java.util.TreeMap;
27+
28+
import org.apache.http.HttpResponse;
29+
import org.apache.http.client.methods.HttpGet;
30+
import org.junit.Assume;
31+
import org.junit.BeforeClass;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
35+
import io.undertow.server.handlers.PathHandler;
36+
import io.undertow.servlet.api.DeploymentInfo;
37+
import io.undertow.servlet.api.DeploymentManager;
38+
import io.undertow.servlet.api.ErrorPage;
39+
import io.undertow.servlet.api.ServletContainer;
40+
import io.undertow.servlet.api.ServletInfo;
41+
import io.undertow.servlet.test.SimpleServletTestCase;
42+
import io.undertow.servlet.test.util.TestClassIntrospector;
43+
import io.undertow.servlet.test.util.TestResourceLoader;
44+
import io.undertow.testutils.DefaultServer;
45+
import io.undertow.testutils.HttpClientUtils;
46+
import io.undertow.testutils.ProxyIgnore;
47+
import io.undertow.testutils.TestHttpClient;
48+
import io.undertow.util.StatusCodes;
49+
import jakarta.servlet.ServletException;
50+
51+
/**
52+
* @author baranowb
53+
*/
54+
@RunWith(DefaultServer.class)
55+
@ProxyIgnore
56+
public class DispatcherErrorForwardServletTestCase extends AttributeComparisonTestBase {
57+
58+
static final String TARGET = "";
59+
@BeforeClass
60+
public static void setup() throws ServletException {
61+
//we don't run this test on h2 upgrade, as if it is run with the original request
62+
//the protocols will not match
63+
Assume.assumeFalse(DefaultServer.isH2upgrade());
64+
final PathHandler root = new PathHandler();
65+
final ServletContainer container = ServletContainer.Factory.newInstance();
66+
67+
DeploymentInfo builder = new DeploymentInfo()
68+
//no idea why...
69+
.setClassLoader(SimpleServletTestCase.class.getClassLoader())
70+
.setContextPath("/servletContext")
71+
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
72+
.setDeploymentName("servletContext.war")
73+
.setResourceManager(new TestResourceLoader(DispatcherErrorForwardServletTestCase.class))
74+
//test servlet, we need info from it
75+
.addServlet(new ServletInfo("error", ErrorHandlingServlet.class)
76+
.addMapping("/error"))
77+
//return handler, which should send us stuff
78+
.addServlet(new ServletInfo("target", ErrorSpewServlet.class)
79+
.addMapping("/target"))
80+
//fwd
81+
.addServlet(new ServletInfo("fwd", ForwardServlet.class)
82+
.addMapping("/forward"))
83+
//error mapge mapping to servlet
84+
.addErrorPage(new ErrorPage("/error"));
85+
86+
DeploymentManager manager = container.addDeployment(builder);
87+
manager.deploy();
88+
root.addPrefixPath(builder.getContextPath(), manager.start());
89+
DefaultServer.setRootHandler(root);
90+
}
91+
92+
@Test
93+
public void testSimpleForwardWithError() throws IOException, InterruptedException {
94+
//Expected params:
95+
final Map<String, String> expectedParams = new TreeMap();
96+
// jakarta.servlet.async.mapping
97+
// jakarta.servlet.async.request_uri
98+
// jakarta.servlet.async.context_path
99+
// jakarta.servlet.async.servlet_path
100+
// jakarta.servlet.async.path_info
101+
// jakarta.servlet.async.query_string
102+
expectedParams.put("jakarta.servlet.forward.request_uri", "/servletContext/forward");
103+
expectedParams.put("jakarta.servlet.forward.servlet_path", "/forward");
104+
expectedParams.put("jakarta.servlet.forward.context_path", "/servletContext");
105+
expectedParams.put("jakarta.servlet.forward.mapping",
106+
"match_value=forward,pattern=/forward,servlet_name=fwd,mapping_match=EXACT");
107+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#request-attributes
108+
// jakarta.servlet.error.request_uri
109+
// jakarta.servlet.error.servlet_name
110+
// jakarta.servlet.error.exception_type
111+
// jakarta.servlet.error.exception
112+
// jakarta.servlet.error.message
113+
// jakarta.servlet.error.status_code
114+
expectedParams.put("jakarta.servlet.error.request_uri", "/servletContext/forward");
115+
expectedParams.put("jakarta.servlet.error.servlet_name", "fwd");
116+
expectedParams.put("jakarta.servlet.error.exception_type", "class jakarta.servlet.ServletException");
117+
expectedParams.put("jakarta.servlet.error.exception", "jakarta.servlet.ServletException: HEY");
118+
expectedParams.put("jakarta.servlet.error.message", "HEY");
119+
expectedParams.put("jakarta.servlet.error.status_code", "500");
120+
TestHttpClient client = new TestHttpClient();
121+
try {
122+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/forward");
123+
get.setHeader("forward", "/target");
124+
get.setHeader("throw", "true");
125+
HttpResponse result = client.execute(get);
126+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
127+
final String response = HttpClientUtils.readResponse(result);
128+
assertNotNull(response);
129+
super.testAttributes(expectedParams, response);
130+
} finally {
131+
client.getConnectionManager().shutdown();
132+
}
133+
}
134+
135+
@Test
136+
public void testSimpleForwardWithNoError() throws IOException, InterruptedException {
137+
//Expected params:
138+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#forwarded-request-parameters
139+
//Some are missing, since null == omission.
140+
// jakarta.servlet.forward.mapping
141+
// jakarta.servlet.forward.request_uri
142+
// jakarta.servlet.forward.context_path
143+
// jakarta.servlet.forward.servlet_path
144+
// jakarta.servlet.forward.path_info
145+
// jakarta.servlet.forward.query_string
146+
final Map<String, String> expectedParams = new TreeMap();
147+
expectedParams.put("jakarta.servlet.forward.request_uri", "/servletContext/forward");
148+
expectedParams.put("jakarta.servlet.forward.servlet_path", "/forward");
149+
expectedParams.put("jakarta.servlet.forward.mapping",
150+
"match_value=forward,pattern=/forward,servlet_name=fwd,mapping_match=EXACT");
151+
expectedParams.put("jakarta.servlet.forward.context_path", "/servletContext");
152+
TestHttpClient client = new TestHttpClient();
153+
try {
154+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/forward");
155+
get.setHeader("forward", "/target");
156+
HttpResponse result = client.execute(get);
157+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
158+
final String response = HttpClientUtils.readResponse(result);
159+
assertNotNull(response);
160+
super.testAttributes(expectedParams, response);
161+
} finally {
162+
client.getConnectionManager().shutdown();
163+
}
164+
}
165+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
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+
19+
package io.undertow.servlet.test.dispatcher.attributes;
20+
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertNotNull;
23+
24+
import java.io.IOException;
25+
import java.util.Map;
26+
import java.util.TreeMap;
27+
28+
import org.apache.http.HttpResponse;
29+
import org.apache.http.client.methods.HttpGet;
30+
import org.junit.Assume;
31+
import org.junit.BeforeClass;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
35+
import io.undertow.server.handlers.PathHandler;
36+
import io.undertow.servlet.api.DeploymentInfo;
37+
import io.undertow.servlet.api.DeploymentManager;
38+
import io.undertow.servlet.api.ErrorPage;
39+
import io.undertow.servlet.api.ServletContainer;
40+
import io.undertow.servlet.api.ServletInfo;
41+
import io.undertow.servlet.test.SimpleServletTestCase;
42+
import io.undertow.servlet.test.util.TestClassIntrospector;
43+
import io.undertow.servlet.test.util.TestResourceLoader;
44+
import io.undertow.testutils.DefaultServer;
45+
import io.undertow.testutils.HttpClientUtils;
46+
import io.undertow.testutils.ProxyIgnore;
47+
import io.undertow.testutils.TestHttpClient;
48+
import io.undertow.util.StatusCodes;
49+
import jakarta.servlet.ServletException;
50+
51+
/**
52+
* @author baranowb
53+
*/
54+
@RunWith(DefaultServer.class)
55+
@ProxyIgnore
56+
public class DispatcherErrorIncludeServletTestCase extends AttributeComparisonTestBase {
57+
58+
static final String TARGET = "/target";
59+
@BeforeClass
60+
public static void setup() throws ServletException {
61+
//we don't run this test on h2 upgrade, as if it is run with the original request
62+
//the protocols will not match
63+
Assume.assumeFalse(DefaultServer.isH2upgrade());
64+
final PathHandler root = new PathHandler();
65+
final ServletContainer container = ServletContainer.Factory.newInstance();
66+
67+
DeploymentInfo builder = new DeploymentInfo()
68+
//no idea why...
69+
.setClassLoader(SimpleServletTestCase.class.getClassLoader())
70+
.setContextPath("/servletContext")
71+
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
72+
.setDeploymentName("servletContext.war")
73+
.setResourceManager(new TestResourceLoader(DispatcherErrorIncludeServletTestCase.class))
74+
//test servlet, we need info from it
75+
.addServlet(new ServletInfo("error", ErrorHandlingServlet.class)
76+
.addMapping("/error"))
77+
//return handler, which should send us stuff
78+
.addServlet(new ServletInfo("target", ErrorSpewServlet.class)
79+
.addMapping(TARGET))
80+
//fwd
81+
.addServlet(new ServletInfo("inc", IncludeServlet.class)
82+
.addMapping("/include"))
83+
//error mapge mapping to servlet
84+
.addErrorPage(new ErrorPage("/error"));
85+
86+
DeploymentManager manager = container.addDeployment(builder);
87+
manager.deploy();
88+
root.addPrefixPath(builder.getContextPath(), manager.start());
89+
DefaultServer.setRootHandler(root);
90+
}
91+
92+
@Test
93+
public void testSimpleIncludeWithError() throws IOException, InterruptedException {
94+
final Map<String, String> expectedParams = new TreeMap();
95+
// jakarta.servlet.async.mapping
96+
// jakarta.servlet.async.request_uri
97+
// jakarta.servlet.async.context_path
98+
// jakarta.servlet.async.servlet_path
99+
// jakarta.servlet.async.path_info
100+
// jakarta.servlet.async.query_string
101+
102+
expectedParams.put("jakarta.servlet.forward.request_uri", "/servletContext/include");
103+
expectedParams.put("jakarta.servlet.forward.servlet_path", "/include");
104+
expectedParams.put("jakarta.servlet.error.servlet_name", "inc");
105+
expectedParams.put("jakarta.servlet.forward.mapping",
106+
"match_value=include,pattern=/include,servlet_name=inc,mapping_match=EXACT");
107+
expectedParams.put("jakarta.servlet.forward.context_path", "/servletContext");
108+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#request-attributes
109+
// jakarta.servlet.error.request_uri
110+
// jakarta.servlet.error.servlet_name
111+
// jakarta.servlet.error.exception_type
112+
// jakarta.servlet.error.exception
113+
// jakarta.servlet.error.message
114+
// jakarta.servlet.error.status_code
115+
expectedParams.put("jakarta.servlet.error.request_uri", "/servletContext/include");
116+
expectedParams.put("jakarta.servlet.error.exception_type", "class jakarta.servlet.ServletException");
117+
expectedParams.put("jakarta.servlet.error.exception", "jakarta.servlet.ServletException: HEY");
118+
expectedParams.put("jakarta.servlet.error.message", "HEY");
119+
expectedParams.put("jakarta.servlet.error.status_code", "500");
120+
TestHttpClient client = new TestHttpClient();
121+
try {
122+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/include");
123+
get.setHeader("include", TARGET);
124+
get.setHeader("throw", "true");
125+
HttpResponse result = client.execute(get);
126+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
127+
final String response = HttpClientUtils.readResponse(result);
128+
assertNotNull(response);
129+
super.testAttributes(expectedParams, response);
130+
} finally {
131+
client.getConnectionManager().shutdown();
132+
}
133+
}
134+
135+
@Test
136+
public void testSimpleIncludeWithNoError() throws IOException, InterruptedException {
137+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#included-request-parameters
138+
// jakarta.servlet.include.request_uri
139+
// jakarta.servlet.include.context_path
140+
// jakarta.servlet.include.query_string
141+
// jakarta.servlet.include.servlet_path
142+
// jakarta.servlet.include.mapping
143+
// jakarta.servlet.include.path_info
144+
final Map<String, String> expectedParams = new TreeMap();
145+
expectedParams.put("jakarta.servlet.include.request_uri", "/servletContext/target");
146+
expectedParams.put("jakarta.servlet.include.context_path", "/servletContext");
147+
expectedParams.put("jakarta.servlet.include.query_string", "");
148+
expectedParams.put("jakarta.servlet.include.servlet_path", TARGET);
149+
expectedParams.put("jakarta.servlet.include.mapping",
150+
"match_value=include,pattern=/include,servlet_name=inc,mapping_match=EXACT");
151+
TestHttpClient client = new TestHttpClient();
152+
try {
153+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/include");
154+
get.setHeader("include", TARGET);
155+
HttpResponse result = client.execute(get);
156+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
157+
final String response = HttpClientUtils.readResponse(result);
158+
assertNotNull(response);
159+
super.testAttributes(expectedParams, response);
160+
} finally {
161+
client.getConnectionManager().shutdown();
162+
}
163+
}
164+
}

0 commit comments

Comments
 (0)