This is an automated email from the ASF dual-hosted git repository. paulrutter pushed a commit to branch feature/FELIX-6782-Allow-adding-custom-headers-to-Jetty-error-pages in repository https://gitbox.apache.org/repos/asf/felix-dev.git
commit b48bd00369c26f26a036c468f08b22b135e1ddf0 Author: Paul Rütter <[email protected]> AuthorDate: Mon May 19 21:01:29 2025 +0200 FELIX-6782 Allow adding custom headers to Jetty error pages - Allow adding custom headers to Jetty error pages org.apache.felix.http.jetty.errorPageCustomHeaders=Strict-Transport-Security=max-age=31536000##X-Custom-Header=123 --- .../jetty/internal/ConfigMetaTypeProvider.java | 6 +++ .../felix/http/jetty/internal/JettyConfig.java | 7 ++++ .../http/jetty/internal/JettyErrorHandler.java | 43 ++++++++++++++++++++++ .../felix/http/jetty/internal/JettyService.java | 32 ++++++++++++++-- .../jetty/it/JettyUriComplianceModeDefaultIT.java | 7 +++- 5 files changed, 90 insertions(+), 5 deletions(-) diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java index 3c6b13eff0..98403fcfb0 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/ConfigMetaTypeProvider.java @@ -220,6 +220,12 @@ class ConfigMetaTypeProvider implements MetaTypeProvider 204800, bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT))); + adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS, + "Custom headers to add to error pages", + "Felix specific property to configure the custom headers to add to all error pages served by Jetty. Separate key-value pairs with ##.", + 204800, + bundle.getBundleContext().getProperty(JettyConfig.FELIX_JETTY_RESPONSE_SIZE_LIMIT))); + adList.add(new AttributeDefinitionImpl(JettyConfig.FELIX_HTTP_PATH_EXCLUSIONS, "Path Exclusions", "Contains a list of context path prefixes. If a Web Application Bundle is started with a context path matching any " + diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java index c2961631b7..4116bba139 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyConfig.java @@ -115,6 +115,9 @@ public final class JettyConfig /** 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 */ public static final String FELIX_JETTY_RESPONSE_SIZE_LIMIT = "org.apache.felix.http.jetty.responseSizeLimit"; + /** Felix specific property to configure the custom headers to add to all error pages served by Jetty. Separate key-value pairs with ##. */ + public static final String FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS = "org.apache.felix.http.jetty.errorPageCustomHeaders"; + /** Felix specific property to enable Jetty MBeans. Valid values are "true", "false". Default is false */ public static final String FELIX_HTTP_MBEANS = "org.apache.felix.http.mbeans"; @@ -509,6 +512,10 @@ public final class JettyConfig return getIntProperty(FELIX_JETTY_RESPONSE_SIZE_LIMIT, -1); } + public String getFelixJettyErrorPageCustomHeaders() { + return getProperty(FELIX_JETTY_ERROR_PAGE_CUSTOM_HEADERS, null); + } + /** * Returns the configured session timeout in minutes or zero if not * configured. diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyErrorHandler.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyErrorHandler.java new file mode 100644 index 0000000000..e6754fbc1d --- /dev/null +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyErrorHandler.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.felix.http.jetty.internal; + +import java.util.Map; + +import org.eclipse.jetty.ee10.servlet.ErrorHandler; +import org.eclipse.jetty.http.HttpFields.Mutable; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.Callback; + +public class JettyErrorHandler extends ErrorHandler { + private final Map<String, String> customHeaders; + + public JettyErrorHandler(Map<String, String> customHeaders) { + this.customHeaders = customHeaders; + } + + @Override + public boolean handle(Request request, Response response, Callback callback) throws Exception { + if (!customHeaders.isEmpty()) { + Mutable headers = response.getHeaders(); + customHeaders.forEach(headers::add); + } + + return super.handle(request, response, callback); + } +} diff --git a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java index 047f4594ec..04dfc4e5f8 100644 --- a/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java +++ b/http/jetty12/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java @@ -30,18 +30,23 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; +import java.util.HashMap; import java.util.Hashtable; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import jakarta.servlet.SessionCookieConfig; +import jakarta.servlet.SessionTrackingMode; + import org.apache.felix.http.base.internal.HttpServiceController; import org.apache.felix.http.base.internal.logger.SystemLogger; import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.ServletHandler; import org.eclipse.jetty.ee10.servlet.ServletHolder; import org.eclipse.jetty.ee10.servlet.SessionHandler; -import org.eclipse.jetty.ee10.servlet.ServletHandler; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; @@ -72,9 +77,6 @@ import org.osgi.framework.ServiceFactory; import org.osgi.framework.ServiceRegistration; import org.osgi.service.servlet.runtime.HttpServiceRuntimeConstants; -import jakarta.servlet.SessionCookieConfig; -import jakarta.servlet.SessionTrackingMode; - public final class JettyService { /** PID for configuration of the HTTP service. */ @@ -310,6 +312,13 @@ public final class JettyService loginService.setUserStore(new UserStore()); this.server.addBean(loginService); + // Check if custom headers are configured for Jetty error pages + // If so, split them on ## and pass as map to the JettyErrorHandler + final String errorPageCustomHeaders = this.config.getFelixJettyErrorPageCustomHeaders(); + if (errorPageCustomHeaders != null) { + addErrorHandler(errorPageCustomHeaders); + } + ServletContextHandler context = new ServletContextHandler(this.config.getContextPath(), ServletContextHandler.SESSIONS); @@ -471,6 +480,21 @@ public final class JettyService } } + private void addErrorHandler(String errorPageCustomHeaders) { + final String[] customHeaders = errorPageCustomHeaders.split("##"); + final Map<String, String> headers = new HashMap<>(); + for (String customHeader : customHeaders) { + if (customHeader == null || !customHeader.contains("=") || customHeader.endsWith("=")) { + SystemLogger.LOGGER.warn("Ignoring invalid error page custom header: {}", customHeader); + continue; + } + final String key = customHeader.substring(0, customHeader.indexOf("=")); + final String value = customHeader.substring(customHeader.indexOf("=") + 1); + headers.put(key.trim(), value.trim()); + } + this.server.setErrorHandler(new JettyErrorHandler(headers)); + } + private static String fixJettyVersion(final BundleContext ctx) { // FELIX-4311: report the real version of Jetty... diff --git a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyUriComplianceModeDefaultIT.java b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyUriComplianceModeDefaultIT.java index 359d3678d9..dd43f9efc9 100644 --- a/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyUriComplianceModeDefaultIT.java +++ b/http/jetty12/src/test/java/org/apache/felix/http/jetty/it/JettyUriComplianceModeDefaultIT.java @@ -74,6 +74,7 @@ public class JettyUriComplianceModeDefaultIT extends AbstractJettyTestSupport { protected Option felixHttpConfig(int httpPort) { return newConfiguration("org.apache.felix.http") .put("org.osgi.service.http.port", httpPort) + .put("org.apache.felix.http.jetty.errorPageCustomHeaders", "Strict-Transport-Security=max-age=31536000##X-Custom-Header=123") .asOption(); } @@ -102,7 +103,11 @@ public class JettyUriComplianceModeDefaultIT extends AbstractJettyTestSupport { assertEquals("OK", response.getContentAsString()); // blocked with HTTP 400 by default - assertEquals(400, httpClient.GET(destUriAmbigousPath).getStatus()); + // validate custom headers in case of error page + ContentResponse responseAmbigousPath = httpClient.GET(destUriAmbigousPath); + assertEquals(400, responseAmbigousPath.getStatus()); + assertEquals("max-age=31536000", responseAmbigousPath.getHeaders().get("Strict-Transport-Security")); + assertEquals("123", responseAmbigousPath.getHeaders().get("X-Custom-Header")); httpClient.close(); }
