Skip to content

Commit b9e625e

Browse files
committed
[UNDERTOW-2536] Fix include paramters on error and add rudimentary test
1 parent e6eb76a commit b9e625e

File tree

8 files changed

+610
-0
lines changed

8 files changed

+610
-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,163 @@
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.TestHttpClient;
47+
import io.undertow.util.StatusCodes;
48+
import jakarta.servlet.ServletException;
49+
50+
/**
51+
* @author baranowb
52+
*/
53+
@RunWith(DefaultServer.class)
54+
public class DispatcherErrorForwardServletTestCase extends AttributeComparisonTestBase {
55+
56+
static final String TARGET = "";
57+
@BeforeClass
58+
public static void setup() throws ServletException {
59+
//we don't run this test on h2 upgrade, as if it is run with the original request
60+
//the protocols will not match
61+
Assume.assumeFalse(DefaultServer.isH2upgrade());
62+
final PathHandler root = new PathHandler();
63+
final ServletContainer container = ServletContainer.Factory.newInstance();
64+
65+
DeploymentInfo builder = new DeploymentInfo()
66+
//no idea why...
67+
.setClassLoader(SimpleServletTestCase.class.getClassLoader())
68+
.setContextPath("/servletContext")
69+
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
70+
.setDeploymentName("servletContext.war")
71+
.setResourceManager(new TestResourceLoader(DispatcherErrorForwardServletTestCase.class))
72+
//test servlet, we need info from it
73+
.addServlet(new ServletInfo("error", ErrorHandlingServlet.class)
74+
.addMapping("/error"))
75+
//return handler, which should send us stuff
76+
.addServlet(new ServletInfo("target", ErrorSpewServlet.class)
77+
.addMapping("/target"))
78+
//fwd
79+
.addServlet(new ServletInfo("fwd", ForwardServlet.class)
80+
.addMapping("/forward"))
81+
//error mapge mapping to servlet
82+
.addErrorPage(new ErrorPage("/error"));
83+
84+
DeploymentManager manager = container.addDeployment(builder);
85+
manager.deploy();
86+
root.addPrefixPath(builder.getContextPath(), manager.start());
87+
DefaultServer.setRootHandler(root);
88+
}
89+
90+
@Test
91+
public void testSimpleForwardWithError() throws IOException, InterruptedException {
92+
//Expected params:
93+
final Map<String, String> expectedParams = new TreeMap();
94+
// jakarta.servlet.async.mapping
95+
// jakarta.servlet.async.request_uri
96+
// jakarta.servlet.async.context_path
97+
// jakarta.servlet.async.servlet_path
98+
// jakarta.servlet.async.path_info
99+
// jakarta.servlet.async.query_string
100+
expectedParams.put("jakarta.servlet.forward.request_uri", "/servletContext/forward");
101+
expectedParams.put("jakarta.servlet.forward.servlet_path", "/forward");
102+
expectedParams.put("jakarta.servlet.forward.context_path", "/servletContext");
103+
expectedParams.put("jakarta.servlet.forward.mapping",
104+
"match_value=forward,pattern=/forward,servlet_name=fwd,mapping_match=EXACT");
105+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#request-attributes
106+
// jakarta.servlet.error.request_uri
107+
// jakarta.servlet.error.servlet_name
108+
// jakarta.servlet.error.exception_type
109+
// jakarta.servlet.error.exception
110+
// jakarta.servlet.error.message
111+
// jakarta.servlet.error.status_code
112+
expectedParams.put("jakarta.servlet.error.request_uri", "/servletContext/forward");
113+
expectedParams.put("jakarta.servlet.error.servlet_name", "fwd");
114+
expectedParams.put("jakarta.servlet.error.exception_type", "class jakarta.servlet.ServletException");
115+
expectedParams.put("jakarta.servlet.error.exception", "jakarta.servlet.ServletException: HEY");
116+
expectedParams.put("jakarta.servlet.error.message", "HEY");
117+
expectedParams.put("jakarta.servlet.error.status_code", "500");
118+
TestHttpClient client = new TestHttpClient();
119+
try {
120+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/forward");
121+
get.setHeader("forward", "/target");
122+
get.setHeader("throw", "true");
123+
HttpResponse result = client.execute(get);
124+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
125+
final String response = HttpClientUtils.readResponse(result);
126+
assertNotNull(response);
127+
super.testAttributes(expectedParams, response);
128+
} finally {
129+
client.getConnectionManager().shutdown();
130+
}
131+
}
132+
133+
@Test
134+
public void testSimpleForwardWithNoError() throws IOException, InterruptedException {
135+
//Expected params:
136+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#forwarded-request-parameters
137+
//Some are missing, since null == omission.
138+
// jakarta.servlet.forward.mapping
139+
// jakarta.servlet.forward.request_uri
140+
// jakarta.servlet.forward.context_path
141+
// jakarta.servlet.forward.servlet_path
142+
// jakarta.servlet.forward.path_info
143+
// jakarta.servlet.forward.query_string
144+
final Map<String, String> expectedParams = new TreeMap();
145+
expectedParams.put("jakarta.servlet.forward.request_uri", "/servletContext/forward");
146+
expectedParams.put("jakarta.servlet.forward.servlet_path", "/forward");
147+
expectedParams.put("jakarta.servlet.forward.mapping",
148+
"match_value=forward,pattern=/forward,servlet_name=fwd,mapping_match=EXACT");
149+
expectedParams.put("jakarta.servlet.forward.context_path", "/servletContext");
150+
TestHttpClient client = new TestHttpClient();
151+
try {
152+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/forward");
153+
get.setHeader("forward", "/target");
154+
HttpResponse result = client.execute(get);
155+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
156+
final String response = HttpClientUtils.readResponse(result);
157+
assertNotNull(response);
158+
super.testAttributes(expectedParams, response);
159+
} finally {
160+
client.getConnectionManager().shutdown();
161+
}
162+
}
163+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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.TestHttpClient;
47+
import io.undertow.util.StatusCodes;
48+
import jakarta.servlet.ServletException;
49+
50+
/**
51+
* @author baranowb
52+
*/
53+
@RunWith(DefaultServer.class)
54+
public class DispatcherErrorIncludeServletTestCase extends AttributeComparisonTestBase {
55+
56+
static final String TARGET = "/target";
57+
@BeforeClass
58+
public static void setup() throws ServletException {
59+
//we don't run this test on h2 upgrade, as if it is run with the original request
60+
//the protocols will not match
61+
Assume.assumeFalse(DefaultServer.isH2upgrade());
62+
final PathHandler root = new PathHandler();
63+
final ServletContainer container = ServletContainer.Factory.newInstance();
64+
65+
DeploymentInfo builder = new DeploymentInfo()
66+
//no idea why...
67+
.setClassLoader(SimpleServletTestCase.class.getClassLoader())
68+
.setContextPath("/servletContext")
69+
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
70+
.setDeploymentName("servletContext.war")
71+
.setResourceManager(new TestResourceLoader(DispatcherErrorIncludeServletTestCase.class))
72+
//test servlet, we need info from it
73+
.addServlet(new ServletInfo("error", ErrorHandlingServlet.class)
74+
.addMapping("/error"))
75+
//return handler, which should send us stuff
76+
.addServlet(new ServletInfo("target", ErrorSpewServlet.class)
77+
.addMapping(TARGET))
78+
//fwd
79+
.addServlet(new ServletInfo("inc", IncludeServlet.class)
80+
.addMapping("/include"))
81+
//error mapge mapping to servlet
82+
.addErrorPage(new ErrorPage("/error"));
83+
84+
DeploymentManager manager = container.addDeployment(builder);
85+
manager.deploy();
86+
root.addPrefixPath(builder.getContextPath(), manager.start());
87+
DefaultServer.setRootHandler(root);
88+
}
89+
90+
@Test
91+
public void testSimpleIncludeWithError() throws IOException, InterruptedException {
92+
final Map<String, String> expectedParams = new TreeMap();
93+
// jakarta.servlet.async.mapping
94+
// jakarta.servlet.async.request_uri
95+
// jakarta.servlet.async.context_path
96+
// jakarta.servlet.async.servlet_path
97+
// jakarta.servlet.async.path_info
98+
// jakarta.servlet.async.query_string
99+
100+
expectedParams.put("jakarta.servlet.forward.request_uri", "/servletContext/include");
101+
expectedParams.put("jakarta.servlet.forward.servlet_path", "/include");
102+
expectedParams.put("jakarta.servlet.error.servlet_name", "inc");
103+
expectedParams.put("jakarta.servlet.forward.mapping",
104+
"match_value=include,pattern=/include,servlet_name=inc,mapping_match=EXACT");
105+
expectedParams.put("jakarta.servlet.forward.context_path", "/servletContext");
106+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#request-attributes
107+
// jakarta.servlet.error.request_uri
108+
// jakarta.servlet.error.servlet_name
109+
// jakarta.servlet.error.exception_type
110+
// jakarta.servlet.error.exception
111+
// jakarta.servlet.error.message
112+
// jakarta.servlet.error.status_code
113+
expectedParams.put("jakarta.servlet.error.request_uri", "/servletContext/include");
114+
expectedParams.put("jakarta.servlet.error.exception_type", "class jakarta.servlet.ServletException");
115+
expectedParams.put("jakarta.servlet.error.exception", "jakarta.servlet.ServletException: HEY");
116+
expectedParams.put("jakarta.servlet.error.message", "HEY");
117+
expectedParams.put("jakarta.servlet.error.status_code", "500");
118+
TestHttpClient client = new TestHttpClient();
119+
try {
120+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/include");
121+
get.setHeader("include", TARGET);
122+
get.setHeader("throw", "true");
123+
HttpResponse result = client.execute(get);
124+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
125+
final String response = HttpClientUtils.readResponse(result);
126+
assertNotNull(response);
127+
super.testAttributes(expectedParams, response);
128+
} finally {
129+
client.getConnectionManager().shutdown();
130+
}
131+
}
132+
133+
@Test
134+
public void testSimpleIncludeWithNoError() throws IOException, InterruptedException {
135+
//https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#included-request-parameters
136+
// jakarta.servlet.include.request_uri
137+
// jakarta.servlet.include.context_path
138+
// jakarta.servlet.include.query_string
139+
// jakarta.servlet.include.servlet_path
140+
// jakarta.servlet.include.mapping
141+
// jakarta.servlet.include.path_info
142+
final Map<String, String> expectedParams = new TreeMap();
143+
expectedParams.put("jakarta.servlet.include.request_uri", "/servletContext/target");
144+
expectedParams.put("jakarta.servlet.include.context_path", "/servletContext");
145+
expectedParams.put("jakarta.servlet.include.query_string", "");
146+
expectedParams.put("jakarta.servlet.include.servlet_path", TARGET);
147+
expectedParams.put("jakarta.servlet.include.mapping",
148+
"match_value=include,pattern=/include,servlet_name=inc,mapping_match=EXACT");
149+
TestHttpClient client = new TestHttpClient();
150+
try {
151+
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/include");
152+
get.setHeader("include", TARGET);
153+
HttpResponse result = client.execute(get);
154+
assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
155+
final String response = HttpClientUtils.readResponse(result);
156+
assertNotNull(response);
157+
super.testAttributes(expectedParams, response);
158+
} finally {
159+
client.getConnectionManager().shutdown();
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)