Skip to content

Commit 0f2decb

Browse files
authored
Merge pull request #426 from apache/feature/FELIX-6782-Allow-adding-custom-headers-to-Jetty-error-pages
FELIX-6782 allow adding custom headers to jetty error pages
2 parents 5ad1a58 + cd77630 commit 0f2decb

6 files changed

Lines changed: 97 additions & 5 deletions

File tree

http/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ properties can be used (some legacy property names still exist but are not docum
414414
| `org.apache.felix.http.jetty.maxFormSize` | The maximum size accepted for a form post, in bytes (ony applies to form parameters). Defaults to 200 KB. |
415415
| `org.apache.felix.http.jetty.requestSizeLimit` | Maximum size of the request body in bytes. Default is unlimited. Added in Jetty12 1.0.30. |
416416
| `org.apache.felix.http.jetty.responseSizeLimit` | Maximum size of the response body in bytes. Default is unlimited. Default is unlimited. Added in Jetty12 1.0.30. |
417+
| `org.apache.felix.http.jetty.errorPageCustomHeaders` | Configures the custom headers to add to all error pages served by Jetty. Separate key-value pairs with `##`, e.g. `X-Custom-Header=Value##X-Custom-Header2=Value2`. Added in Jetty12 1.0.32. |
417418
| `org.apache.felix.http.mbeans` | If `true`, enables the MBean server functionality. The default is `false`. |
418419
| `org.apache.felix.http.jetty.sendServerHeader` | If `false`, the `Server` HTTP header is no longer included in responses. The default is `false`. |
419420
| `org.eclipse.jetty.servlet.SessionCookie` | Name of the cookie used to transport the Session ID. The default is `JSESSIONID`. |

http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ public ObjectClassDefinition getObjectClassDefinition( String id, String locale
220220
204800,
221221
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT)));
222222

223+
adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS,
224+
"Custom headers to add to error pages",
225+
"Felix specific property to configure the custom headers to add to all error pages served by Jetty. Separate key-value pairs with ##.",
226+
null,
227+
bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS)));
228+
223229
adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP_PATH_EXCLUSIONS,
224230
"Path Exclusions",
225231
"Contains a list of context path prefixes. If a Web Application Bundle is started with a context path matching any " +

http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ public final class JettyConfig
115115
/** Felix specific property to configure the response size limit. Default is unlimited. See https://jetty.org/docs/jetty/12/programming-guide/server/http.html#handler-use-size-limit */
116116
public static final String FELIX_JETTY_RESPONSE_SIZE_LIMIT = "org.apache.felix.http.jetty.responseSizeLimit";
117117

118+
/** Felix specific property to configure the custom headers to add to all error pages served by Jetty. Separate key-value pairs with ##. */
119+
public static final String FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS = "org.apache.felix.http.jetty.errorPageCustomHeaders";
120+
118121
/** Felix specific property to enable Jetty MBeans. Valid values are "true", "false". Default is false */
119122
public static final String FELIX_HTTP_MBEANS = "org.apache.felix.http.mbeans";
120123

@@ -509,6 +512,10 @@ public int getResponseSizeLimit()
509512
return getIntProperty(FELIX_JETTY_RESPONSE_SIZE_LIMIT, -1);
510513
}
511514

515+
public String getFelixJettyErrorPageCustomHeaders() {
516+
return getProperty(FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS, null);
517+
}
518+
512519
/**
513520
* Returns the configured session timeout in minutes or zero if not
514521
* configured.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.felix.http.jetty.internal;
18+
19+
import java.util.Map;
20+
21+
import org.eclipse.jetty.ee10.servlet.ErrorHandler;
22+
import org.eclipse.jetty.http.HttpFields.Mutable;
23+
import org.eclipse.jetty.server.Request;
24+
import org.eclipse.jetty.server.Response;
25+
import org.eclipse.jetty.util.Callback;
26+
27+
public class JettyErrorHandler extends ErrorHandler {
28+
private final Map<String, String> customHeaders;
29+
30+
public JettyErrorHandler(Map<String, String> customHeaders) {
31+
this.customHeaders = customHeaders;
32+
}
33+
34+
@Override
35+
public boolean handle(Request request, Response response, Callback callback) throws Exception {
36+
if (!customHeaders.isEmpty()) {
37+
Mutable headers = response.getHeaders();
38+
customHeaders.forEach(headers::put);
39+
}
40+
41+
return super.handle(request, response, callback);
42+
}
43+
}

http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,23 @@
3030
import java.util.Collections;
3131
import java.util.Dictionary;
3232
import java.util.Enumeration;
33+
import java.util.HashMap;
3334
import java.util.Hashtable;
3435
import java.util.List;
36+
import java.util.Map;
3537
import java.util.concurrent.Executor;
3638
import java.util.concurrent.Executors;
3739

40+
import jakarta.servlet.SessionCookieConfig;
41+
import jakarta.servlet.SessionTrackingMode;
42+
3843
import org.apache.felix.http.base.internal.HttpServiceController;
3944
import org.apache.felix.http.base.internal.logger.SystemLogger;
4045
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
4146
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
47+
import org.eclipse.jetty.ee10.servlet.ServletHandler;
4248
import org.eclipse.jetty.ee10.servlet.ServletHolder;
4349
import org.eclipse.jetty.ee10.servlet.SessionHandler;
44-
import org.eclipse.jetty.ee10.servlet.ServletHandler;
4550
import org.eclipse.jetty.http.HttpVersion;
4651
import org.eclipse.jetty.http.UriCompliance;
4752
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
@@ -72,9 +77,6 @@
7277
import org.osgi.framework.ServiceRegistration;
7378
import org.osgi.service.servlet.runtime.HttpServiceRuntimeConstants;
7479

75-
import jakarta.servlet.SessionCookieConfig;
76-
import jakarta.servlet.SessionTrackingMode;
77-
7880
public final class JettyService
7981
{
8082
/** PID for configuration of the HTTP service. */
@@ -310,6 +312,13 @@ private void initializeJetty() throws Exception
310312
loginService.setUserStore(new UserStore());
311313
this.server.addBean(loginService);
312314

315+
// Check if custom headers are configured for Jetty error pages
316+
// If so, split them on ## and pass as map to the JettyErrorHandler
317+
final String errorPageCustomHeaders = this.config.getFelixJettyErrorPageCustomHeaders();
318+
if (errorPageCustomHeaders != null) {
319+
addErrorHandler(errorPageCustomHeaders);
320+
}
321+
313322
ServletContextHandler context = new ServletContextHandler(this.config.getContextPath(),
314323
ServletContextHandler.SESSIONS);
315324

@@ -471,6 +480,21 @@ private void initializeJetty() throws Exception
471480
}
472481
}
473482

483+
private void addErrorHandler(String errorPageCustomHeaders) {
484+
final String[] customHeaders = errorPageCustomHeaders.split("##");
485+
final Map<String, String> headers = new HashMap<>();
486+
for (String customHeader : customHeaders) {
487+
if (customHeader == null || !customHeader.contains("=") || customHeader.endsWith("=")) {
488+
SystemLogger.LOGGER.warn("Ignoring invalid error page custom header: {}", customHeader);
489+
continue;
490+
}
491+
final String key = customHeader.substring(0, customHeader.indexOf("="));
492+
final String value = customHeader.substring(customHeader.indexOf("=") + 1);
493+
headers.put(key.trim(), value.trim());
494+
}
495+
this.server.setErrorHandler(new JettyErrorHandler(headers));
496+
}
497+
474498
private static String fixJettyVersion(final BundleContext ctx)
475499
{
476500
// FELIX-4311: report the real version of Jetty...

http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyUriComplianceModeDefaultIT.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static org.junit.Assert.assertEquals;
2020
import static org.junit.Assert.assertNotNull;
21+
import static org.junit.Assert.assertNull;
2122
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
2223
import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
2324

@@ -74,6 +75,7 @@ protected Option[] additionalOptions() throws IOException {
7475
protected Option felixHttpConfig(int httpPort) {
7576
return newConfiguration("org.apache.felix.http")
7677
.put("org.osgi.service.http.port", httpPort)
78+
.put("org.apache.felix.http.jetty.errorPageCustomHeaders", "Strict-Transport-Security=max-age=31536000##X-Custom-Header=123")
7779
.asOption();
7880
}
7981

@@ -101,8 +103,17 @@ public void testUriCompliance() throws Exception {
101103
assertEquals(200, response.getStatus());
102104
assertEquals("OK", response.getContentAsString());
103105

106+
// Validate custom headers in case of success page, should not be present
107+
assertNull(response.getHeaders().get("Strict-Transport-Security"));
108+
assertNull(response.getHeaders().get("X-Custom-Header"));
109+
110+
104111
// blocked with HTTP 400 by default
105-
assertEquals(400, httpClient.GET(destUriAmbigousPath).getStatus());
112+
// validate custom headers in case of error page
113+
ContentResponse responseAmbiguousPath = httpClient.GET(destUriAmbigousPath);
114+
assertEquals(400, responseAmbiguousPath.getStatus());
115+
assertEquals("max-age=31536000", responseAmbiguousPath.getHeaders().get("Strict-Transport-Security"));
116+
assertEquals("123", responseAmbiguousPath.getHeaders().get("X-Custom-Header"));
106117

107118
httpClient.close();
108119
}

0 commit comments

Comments
 (0)