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();
     }

Reply via email to