This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 8a3d707dc1 Restore sslfilter to update it for jakarta.servlet
8a3d707dc1 is described below

commit 8a3d707dc101ea07a2b40962507e1312632b21cb
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Fri Oct 28 14:32:01 2022 +0200

    Restore sslfilter to update it for jakarta.servlet
---
 http/sslfilter/pom.xml                             | 121 ++++
 .../main/appended-resources/META-INF/DEPENDENCIES  |  13 +
 .../http/sslfilter/internal/LogServiceTracker.java |  72 +++
 .../felix/http/sslfilter/internal/SslFilter.java   | 195 +++++++
 .../sslfilter/internal/SslFilterActivator.java     | 116 ++++
 .../sslfilter/internal/SslFilterConstants.java     |  78 +++
 .../http/sslfilter/internal/SslFilterRequest.java  | 125 +++++
 .../http/sslfilter/internal/SslFilterResponse.java | 221 ++++++++
 .../http/sslfilter/internal/SystemLogger.java      |  82 +++
 ...pache.felix.http.sslfilter.SslFilter.properties |  26 +
 .../org.apache.felix.http.sslfilter.SslFilter.xml  |  34 ++
 .../sslfilter/internal/SslFilterJettyTest.java     | 182 ++++++
 .../sslfilter/internal/SslFilterRequestTest.java   | 118 ++++
 .../sslfilter/internal/SslFilterResponseTest.java  | 618 +++++++++++++++++++++
 14 files changed, 2001 insertions(+)

diff --git a/http/sslfilter/pom.xml b/http/sslfilter/pom.xml
new file mode 100644
index 0000000000..39d78e0643
--- /dev/null
+++ b/http/sslfilter/pom.xml
@@ -0,0 +1,121 @@
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>org.apache.felix.http.parent</artifactId>
+        <version>13</version>
+        <relativePath>../parent/pom.xml</relativePath>
+    </parent>
+
+    <name>Apache Felix Http SSL Filter</name>
+    <artifactId>org.apache.felix.http.sslfilter</artifactId>
+    <version>1.2.7-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <scm>
+        
<connection>scm:git:https://github.com/apache/felix-dev.git</connection>
+        
<developerConnection>scm:git:https://github.com/apache/felix-dev.git</developerConnection>
+        <url>https://gitbox.apache.org/repos/asf?p=felix-dev.git</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>
+                            
org.apache.felix.http.sslfilter.internal.SslFilterActivator
+                        </Bundle-Activator>
+                        <Import-Package>
+                            javax.servlet.*;version="[3,5)",
+                            org.osgi.service.cm;resolution:=optional,
+                            *
+                        </Import-Package>
+                        <DynamicImport-Package>
+                            org.osgi.service.cm;version="[1.2,2)"
+                        </DynamicImport-Package>
+                        <Require-Capability>
+                            
osgi.implementation;filter:="(&amp;(osgi.implementation=osgi.http)(version&gt;=1.1)(!(version&gt;=2.0)))"
+                        </Require-Capability>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.http.whiteboard</artifactId>
+            <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.http.servlet-api</artifactId>
+            <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+           <groupId>org.osgi</groupId>
+           <artifactId>org.osgi.service.cm</artifactId>
+           <version>1.5.0</version>
+           <scope>provided</scope>
+       </dependency>
+       <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.log</artifactId>
+            <version>1.3.0</version>
+           <scope>provided</scope>
+        </dependency>
+        <!-- Test Dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.eclipse.jetty</groupId>
+          <artifactId>jetty-servlet</artifactId>
+          <version>9.4.35.v20201120</version>
+          <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/http/sslfilter/src/main/appended-resources/META-INF/DEPENDENCIES 
b/http/sslfilter/src/main/appended-resources/META-INF/DEPENDENCIES
new file mode 100644
index 0000000000..2559e95bbc
--- /dev/null
+++ b/http/sslfilter/src/main/appended-resources/META-INF/DEPENDENCIES
@@ -0,0 +1,13 @@
+I. Included Third-Party Software
+
+N/A
+
+II. Used Third-Party Software
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org).
+Copyright (c) OSGi Alliance (2000, 2012).
+Licensed under the Apache License 2.0.
+
+III. License Summary
+- Apache License 2.0
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/LogServiceTracker.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/LogServiceTracker.java
new file mode 100644
index 0000000000..8f1effd7b6
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/LogServiceTracker.java
@@ -0,0 +1,72 @@
+/*
+ * 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.sslfilter.internal;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ *
+ *
+ * @author <a href="mailto:[email protected]";>Felix Project Team</a>
+ */
+public class LogServiceTracker extends ServiceTracker
+{
+    private final Map<ServiceReference, LogService> logServices = new 
TreeMap<ServiceReference, LogService>(Collections.reverseOrder());
+
+    public LogServiceTracker(BundleContext context)
+    {
+        super(context, LogService.class.getName(), null);
+    }
+
+    @Override
+    public Object addingService(final ServiceReference reference)
+    {
+        final LogService result = (LogService) super.addingService(reference);
+        if ( result != null ) {
+            synchronized ( logServices ) {
+                logServices.put(reference, result);
+                
SystemLogger.setLogService(logServices.values().iterator().next());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public void removedService(final ServiceReference reference, final Object 
service)
+    {
+        synchronized ( logServices ) {
+            logServices.remove(reference);
+            final Collection<LogService> services = logServices.values();
+            if ( services.isEmpty() ) {
+                SystemLogger.setLogService(null);
+            } else {
+                SystemLogger.setLogService(services.iterator().next());
+            }
+        }
+        super.removedService(reference, service);
+    }
+}
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
new file mode 100644
index 0000000000..86a6951f87
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilter.java
@@ -0,0 +1,195 @@
+/*
+ * 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.sslfilter.internal;
+
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_SSL;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_SSL_CERTIFICATE;
+
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.util.Dictionary;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.http.whiteboard.Preprocessor;
+import org.osgi.service.log.LogService;
+
+@SuppressWarnings("rawtypes")
+public class SslFilter implements  Preprocessor
+{
+    public static final String PID = 
"org.apache.felix.http.sslfilter.SslFilter";
+
+    private static final String DEFAULT_SSL_HEADER = HDR_X_FORWARDED_SSL;
+    private static final String DEFAULT_SSL_VALUE = "on";
+    private static final String DEFAULT_CERT_HEADER = 
HDR_X_FORWARDED_SSL_CERTIFICATE;
+    private static final boolean DEFAULT_REWRITE_ABSOLUTE_URLS = false;
+
+    private static final String PROP_SSL_HEADER = "ssl-forward.header";
+    private static final String PROP_SSL_VALUE = "ssl-forward.value";
+    private static final String PROP_SSL_CERT_KEY = "ssl-forward-cert.header";
+    private static final String PROP_REWRITE_ABSOLUTE_URLS = 
"rewrite.absolute.urls";
+
+    private volatile ConfigHolder config;
+
+    SslFilter()
+    {
+        this.config = new ConfigHolder(DEFAULT_SSL_HEADER,
+                DEFAULT_SSL_VALUE,
+                DEFAULT_CERT_HEADER,
+                DEFAULT_REWRITE_ABSOLUTE_URLS);
+    }
+
+    @Override
+    public void destroy()
+    {
+        // No explicit destroy needed...
+    }
+
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain 
chain) throws IOException, ServletException
+    {
+        final ConfigHolder cfg = this.config;
+
+        HttpServletRequest httpReq = (HttpServletRequest) req;
+        HttpServletResponse httpResp = (HttpServletResponse) res;
+
+        if (cfg.sslValue.equalsIgnoreCase(httpReq.getHeader(cfg.sslHeader)))
+        {
+            try
+            {
+                httpResp = new SslFilterResponse(httpResp, httpReq, cfg);
+                // In case this fails, we fall back to the original HTTP 
request, which is better than nothing...
+                httpReq = new SslFilterRequest(httpReq, 
httpReq.getHeader(cfg.certHeader));
+            }
+            catch (CertificateException e)
+            {
+                SystemLogger.log(LogService.LOG_WARNING, "Failed to create SSL 
filter request! Problem parsing client certificates?! Client certificate will 
*not* be forwarded...", e);
+            }
+        }
+
+        // forward the request making sure any certificate is removed again 
after the request processing gets back here
+        try
+        {
+            chain.doFilter(httpReq, httpResp);
+        }
+        finally
+        {
+            if (httpReq instanceof SslFilterRequest)
+            {
+                ((SslFilterRequest) httpReq).done();
+            }
+        }
+    }
+
+    @Override
+    public void init(FilterConfig config)
+    {
+        // make sure there is some configuration
+    }
+
+    void configure(Dictionary properties) throws ConfigurationException
+    {
+        String certHeader = DEFAULT_CERT_HEADER;
+        String sslHeader = DEFAULT_SSL_HEADER;
+        String sslValue = DEFAULT_SSL_VALUE;
+        boolean rewriteUrls = DEFAULT_REWRITE_ABSOLUTE_URLS;
+
+        if (properties != null)
+        {
+            certHeader = getOptionalString(properties, PROP_SSL_CERT_KEY);
+            sslHeader = getMandatoryString(properties, PROP_SSL_HEADER);
+            sslValue = getMandatoryString(properties, PROP_SSL_VALUE);
+            rewriteUrls = getOptionalBoolean(properties, 
PROP_REWRITE_ABSOLUTE_URLS, rewriteUrls);
+        }
+
+        this.config = new ConfigHolder(sslHeader, sslValue, certHeader, 
rewriteUrls);
+
+        SystemLogger.log(LogService.LOG_INFO, "SSL filter (re)configured with: 
" + "SSL forward header = '" + sslHeader + "'; SSL forward value = '" + 
sslValue + "'; SSL certificate header = '"
+            + certHeader + "'.");
+    }
+
+    private boolean getOptionalBoolean(Dictionary properties,
+            String key,
+            boolean defaultValue) throws ConfigurationException
+    {
+        Object raw = properties.get(key);
+        if (raw == null)
+        {
+            return defaultValue;
+        }
+        if ( raw instanceof Boolean )
+        {
+            return (Boolean)raw;
+        }
+        if (!(raw instanceof String))
+        {
+            throw new ConfigurationException(key, "invalid value");
+        }
+        return Boolean.valueOf((String)raw);
+    }
+
+    private String getOptionalString(Dictionary properties, String key) throws 
ConfigurationException
+    {
+        Object raw = properties.get(key);
+        if (raw == null || "".equals(((String) raw).trim()))
+        {
+            return null;
+        }
+        if (!(raw instanceof String))
+        {
+            throw new ConfigurationException(key, "invalid value");
+        }
+        return ((String) raw).trim();
+    }
+
+    private String getMandatoryString(Dictionary properties, String key) 
throws ConfigurationException
+    {
+        String value = getOptionalString(properties, key);
+        if (value == null)
+        {
+            throw new ConfigurationException(key, "missing value");
+        }
+        return value;
+    }
+
+    static class ConfigHolder
+    {
+        final String certHeader;
+        final String sslHeader;
+        final String sslValue;
+        final boolean rewriteAbsoluteUrls;
+
+        public ConfigHolder(String sslHeader, String sslValue, String 
certHeader,
+                boolean rewriteAbsoluteUrls)
+        {
+            this.sslHeader = sslHeader;
+            this.sslValue = sslValue;
+            this.certHeader = certHeader;
+            this.rewriteAbsoluteUrls = rewriteAbsoluteUrls;
+        }
+    }
+}
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java
new file mode 100644
index 0000000000..ba83473404
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterActivator.java
@@ -0,0 +1,116 @@
+/*
+ * 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.sslfilter.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceFactory;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.http.whiteboard.HttpWhiteboardConstants;
+import org.osgi.service.http.whiteboard.Preprocessor;
+import org.osgi.service.log.LogService;
+
+public class SslFilterActivator implements BundleActivator
+{
+    /** Singleton filter to be registered with all http services. */
+    private final SslFilter filter = new SslFilter();
+
+    private volatile ServiceRegistration configReceiver;
+
+    private volatile ServiceRegistration filterReg;
+
+    private LogServiceTracker logTracker;
+
+    @Override
+    public void start(final BundleContext context)
+    {
+        this.logTracker = new LogServiceTracker(context);
+        this.logTracker.open();
+
+        final Dictionary<String, Object> props = new Hashtable<String, 
Object>();
+        props.put(Constants.SERVICE_PID, SslFilter.PID);
+
+        this.configReceiver = 
context.registerService(ManagedService.class.getName(), new ServiceFactory()
+        {
+            @Override
+            public Object getService(Bundle bundle, ServiceRegistration 
registration)
+            {
+                return new ManagedService()
+                {
+                    @Override
+                    public void updated(@SuppressWarnings("rawtypes") 
Dictionary properties) throws ConfigurationException
+                    {
+                        configureFilters(properties);
+                    }
+                };
+            }
+
+            @Override
+            public void ungetService(Bundle bundle, ServiceRegistration 
registration, Object service)
+            {
+                // Nop
+            }
+        }, props);
+
+        final Dictionary<String, Object> properties = new Hashtable<String, 
Object>();
+        properties.put(Constants.SERVICE_VENDOR, "The Apache Software 
Foundation");
+        properties.put(Constants.SERVICE_DESCRIPTION, "Apache Felix HTTP SSL 
Filter");
+
+        // any context
+        properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, 
"(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=*)");
+        properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_PATTERN, 
"/");
+
+        this.filterReg = context.registerService(Preprocessor.class.getName(), 
filter, properties);
+
+        SystemLogger.log(LogService.LOG_DEBUG, "SSL filter registered...");
+    }
+
+    @Override
+    public void stop(final BundleContext context)
+    {
+        if ( this.filterReg != null )
+        {
+            this.filterReg.unregister();
+            this.filterReg = null;
+            SystemLogger.log(LogService.LOG_DEBUG, "SSL filter 
unregistered...");
+        }
+        if (this.configReceiver != null)
+        {
+            this.configReceiver.unregister();
+            this.configReceiver = null;
+        }
+        if (this.logTracker != null)
+        {
+            this.logTracker.close();
+            this.logTracker = null;
+        }
+    }
+
+    void configureFilters(@SuppressWarnings("rawtypes") final Dictionary 
properties) throws ConfigurationException
+    {
+        this.filter.configure(properties);
+    }
+}
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterConstants.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterConstants.java
new file mode 100644
index 0000000000..0da3a36228
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterConstants.java
@@ -0,0 +1,78 @@
+/*
+ * 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.sslfilter.internal;
+
+/**
+ * Provides constants used in the SSL filter.
+ */
+interface SslFilterConstants
+{
+    /**
+     * If there is an SSL certificate associated with the request, it must be 
exposed by the servlet container to the
+     * servlet programmer as an array of objects of type 
java.security.cert.X509Certificate and accessible via a
+     * ServletRequest attribute of 
<tt>javax.servlet.request.X509Certificate</tt>.
+     * <p>
+     * The order of this array is defined as being in ascending order of 
trust. The first certificate in the chain is
+     * the one set by the client, the next is the one used to authenticate the 
first, and so on.
+     */
+    String ATTR_SSL_CERTIFICATE = "javax.servlet.request.X509Certificate";
+
+    /**
+     * De-facto header used to inform what protocol the forwarded client used 
to connect to the proxy, such as "https".
+     */
+    String HDR_X_FORWARDED_PROTO = "X-Forwarded-Proto";
+    /**
+     * De-facto header used to inform what port the forwarded client used to 
connect to the proxy, such as "443".
+     */
+    String HDR_X_FORWARDED_PORT = "X-Forwarded-Port";
+    /**
+     * De-facto header used to inform that the proxy is forwarding a SSL 
request.
+     */
+    String HDR_X_FORWARDED_SSL = "X-Forwarded-SSL";
+    /**
+     * De-facto(?) header used to pass the certificate the client used to 
connect to the proxy, in X.509 format.
+     */
+    String HDR_X_FORWARDED_SSL_CERTIFICATE = "X-Forwarded-SSL-Certificate";
+
+    /**
+     * HTTP header used to explain the client it should redirect to another 
URL.
+     */
+    String HDR_LOCATION = "Location";
+
+    /**
+     * HTTP protocol/scheme.
+     */
+    String HTTP = "http";
+    /**
+     * Default port used for HTTP.
+     */
+    int HTTP_PORT = 80;
+
+    /**
+     * HTTPS protocol/scheme.
+     */
+    String HTTPS = "https";
+    /**
+     * Default port used for HTTPS.
+     */
+    int HTTPS_PORT = 443;
+
+    String UTF_8 = "UTF-8";
+    String X_509 = "X.509";
+}
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java
new file mode 100644
index 0000000000..ff7a17d37e
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterRequest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.sslfilter.internal;
+
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.ATTR_SSL_CERTIFICATE;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_PORT;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTPS;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.UTF_8;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.X_509;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+class SslFilterRequest extends HttpServletRequestWrapper
+{
+    // The HTTP scheme prefix in an URL
+    private static final String HTTP_SCHEME_PREFIX = "http://";;
+
+    // pattern to convert the header to a PEM certificate for parsing
+    // by replacing spaces with line breaks
+    private static final Pattern HEADER_TO_CERT = Pattern.compile("(?! 
CERTIFICATE)(?= ) ");
+
+    @SuppressWarnings("unchecked")
+    SslFilterRequest(HttpServletRequest request, String clientCertHeader) 
throws CertificateException
+    {
+        super(request);
+
+        // TODO jawi: perhaps we should make this class a little smarter wrt 
the given request:
+        // it now always assumes it should rewrite its URL, while this might 
not always be the
+        // case...
+
+        if (clientCertHeader != null && !"".equals(clientCertHeader.trim()))
+        {
+            final String clientCert = 
HEADER_TO_CERT.matcher(clientCertHeader).replaceAll("\n");
+
+            try
+            {
+                CertificateFactory fac = CertificateFactory.getInstance(X_509);
+
+                InputStream instream = new 
ByteArrayInputStream(clientCert.getBytes(UTF_8));
+
+                Collection<X509Certificate> certs = 
(Collection<X509Certificate>) fac.generateCertificates(instream);
+                request.setAttribute(ATTR_SSL_CERTIFICATE, certs.toArray(new 
X509Certificate[certs.size()]));
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                // Any JRE should support UTF-8...
+                throw new InternalError("UTF-8 not supported?!");
+            }
+        }
+    }
+
+    void done()
+    {
+        getRequest().removeAttribute(ATTR_SSL_CERTIFICATE);
+    }
+
+    @Override
+    public String getScheme()
+    {
+        return HTTPS;
+    }
+
+    @Override
+    public boolean isSecure()
+    {
+        return true;
+    }
+
+    @Override
+    public StringBuffer getRequestURL()
+    {
+        StringBuffer tmp = new StringBuffer(super.getRequestURL());
+        // In case the request happened over http, simply insert an additional 
's'
+        // to make the request appear to be done over https...
+        if (tmp.indexOf(HTTP_SCHEME_PREFIX) == 0)
+        {
+            tmp.insert(4, 's');
+        }
+        return tmp;
+    }
+
+    @Override
+    public int getServerPort()
+    {
+        int port;
+
+        try
+        {
+            String fwdPort = getHeader(HDR_X_FORWARDED_PORT);
+            port = Integer.parseInt(fwdPort);
+        }
+        catch (Exception e)
+        {
+            // Use default port
+            port = 443;
+        }
+        return port;
+    }
+}
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterResponse.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterResponse.java
new file mode 100644
index 0000000000..a1a485cd5a
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SslFilterResponse.java
@@ -0,0 +1,221 @@
+/*
+ * 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.sslfilter.internal;
+
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_LOCATION;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_PORT;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_PROTO;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_SSL;
+import static org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTP;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTPS;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTPS_PORT;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTP_PORT;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.felix.http.sslfilter.internal.SslFilter.ConfigHolder;
+
+/**
+ * Provides a custom {@link HttpServletResponse} for use in SSL filter.
+ */
+class SslFilterResponse extends HttpServletResponseWrapper
+{
+    private final URL requestURL;
+    private final String serverName;
+    private final String serverProto;
+    private final int serverPort;
+    private final String clientProto;
+    private final int clientPort;
+
+    private final boolean rewriteAbsoluteUrls;
+
+    public SslFilterResponse(HttpServletResponse response, HttpServletRequest 
request, ConfigHolder config) throws MalformedURLException
+    {
+        super(response);
+
+        this.requestURL = new URL(request.getRequestURL().toString());
+
+        // Only rewrite URLs for the host & port the request was sent to...
+        this.serverName = request.getServerName();
+        this.serverPort = request.getServerPort();
+
+        String value = request.getHeader(config.sslHeader);
+
+        if ((HDR_X_FORWARDED_PROTO.equalsIgnoreCase(config.sslHeader) && 
HTTP.equalsIgnoreCase(value)) ||
+                (HDR_X_FORWARDED_SSL.equalsIgnoreCase(config.sslHeader) && 
!config.sslValue.equalsIgnoreCase(value)))
+        {
+            // Not really a useful scenario: client is talking HTTP to proxy, 
and we should rewrite all HTTPS-based URLs...
+            this.clientProto = HTTP;
+            this.serverProto = HTTPS;
+        }
+        else
+        {
+            // Client is talking HTTPS to proxy, so we should rewrite all 
HTTP-based URLs...
+            this.clientProto = HTTPS;
+            this.serverProto = HTTP;
+        }
+
+        int port;
+        try
+        {
+            String fwdPort = request.getHeader(HDR_X_FORWARDED_PORT);
+            port = Integer.valueOf(fwdPort);
+        }
+        catch (Exception e)
+        {
+            // Use default port for the used protocol...
+            port = -1;
+        }
+        // Normalize the protocol port...
+        if ((port > 0) && ((HTTPS.equals(this.clientProto) && (port == 
HTTPS_PORT)) || (HTTP.equals(this.clientProto) && (port == HTTP_PORT))))
+        {
+            // Port is the default one, do not use it...
+            port = -1;
+        }
+
+        this.clientPort = port;
+        this.rewriteAbsoluteUrls = config.rewriteAbsoluteUrls;
+    }
+
+    @Override
+    public void setHeader(String name, String value)
+    {
+        if (HDR_LOCATION.equalsIgnoreCase(name))
+        {
+               String rewritten = null;
+               try {
+                       rewritten = rewriteUrlIfNeeded(value);
+               } catch (URISyntaxException e) {
+                       // ignore
+               }
+            // Trying to set a redirect location to the original client-side 
URL, which should be https...
+            if (rewritten != null)
+            {
+                value = rewritten;
+            }
+        }
+        super.setHeader(name, value);
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException
+    {
+       String rewritten = null;
+       try {
+               rewritten = rewriteUrlIfNeeded(location);
+       } catch (URISyntaxException e) {
+               throw new IOException (e);
+       }
+        if (rewritten != null)
+        {
+            location = rewritten;
+        }
+        super.sendRedirect(location);
+    }
+
+    private int normalizePort(String protocol, int port)
+    {
+        if (port > 0)
+        {
+            return port;
+        }
+        if (HTTPS.equalsIgnoreCase(protocol))
+        {
+            return HTTPS_PORT;
+        }
+        return HTTP_PORT;
+    }
+
+    private String rewriteUrlIfNeeded(String value) throws URISyntaxException
+    {
+        if (value == null || (!this.rewriteAbsoluteUrls && 
value.contains("://")) )
+        {
+            return null;
+        }
+
+        try
+        {
+            URI uri;
+            if (value.startsWith(this.serverProto.concat("://")))
+            {
+
+                uri = new URI (value);
+            }
+            else
+            {
+                URL url = new URL(this.requestURL, value);
+                uri = url.toURI();
+            }
+
+            String actualProto = uri.getScheme();
+
+            if (!this.serverName.equals(uri.getHost()))
+            {
+                // going to a different host
+                return null;
+            }
+
+            if (normalizePort(this.serverProto, this.serverPort) != 
normalizePort(actualProto, uri.getPort()))
+            {
+                // not to default port
+                return null;
+            }
+
+            final StringBuilder sb = new StringBuilder();
+            sb.append(this.clientProto);
+            sb.append("://");
+            sb.append(this.serverName);
+            if ( this.clientPort != -1 )
+            {
+                sb.append(':');
+                sb.append(this.clientPort);
+            }
+            if ( uri.getRawPath() != null )
+            {
+                sb.append(uri.getRawPath());
+            }
+            if ( uri.getRawQuery() != null )
+            {
+                sb.append('?');
+                sb.append(uri.getRawQuery());
+            }
+            if ( uri.getRawFragment() != null )
+            {
+                sb.append('#');
+                sb.append(uri.getRawFragment());
+            }
+            return sb.toString();
+        }
+        catch (MalformedURLException e)
+        {
+            return null;
+        }
+    }
+
+
+
+}
diff --git 
a/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SystemLogger.java
 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SystemLogger.java
new file mode 100644
index 0000000000..8193165bd6
--- /dev/null
+++ 
b/http/sslfilter/src/main/java/org/apache/felix/http/sslfilter/internal/SystemLogger.java
@@ -0,0 +1,82 @@
+/*
+ * 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.sslfilter.internal;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+/**
+ * @author <a href="mailto:[email protected]";>Felix Project Team</a>
+ */
+final class SystemLogger
+{
+    private static volatile LogService log;
+
+    private SystemLogger()
+    {
+        // Nop
+    }
+
+    static void setLogService(LogService _log)
+    {
+        log = _log;
+    }
+
+    static LogService getLogService()
+    {
+        return log;
+    }
+
+    public static void log(int level, String message)
+    {
+        LogService service = getLogService();
+        if (service != null)
+        {
+            service.log(level, message);
+        }
+    }
+
+    public static void log(int level, String message, Throwable exception)
+    {
+        LogService service = getLogService();
+        if (service != null)
+        {
+            service.log(level, message, exception);
+        }
+    }
+
+    public static void log(ServiceReference sr, int level, String message)
+    {
+        LogService service = getLogService();
+        if (service != null)
+        {
+            service.log(sr, level, message);
+        }
+    }
+
+    public static void log(ServiceReference sr, int level, String message, 
Throwable exception)
+    {
+        LogService service = getLogService();
+        if (service != null)
+        {
+            service.log(sr, level, message, exception);
+        }
+    }
+}
diff --git 
a/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
 
b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
new file mode 100644
index 0000000000..c39b9f9437
--- /dev/null
+++ 
b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.properties
@@ -0,0 +1,26 @@
+# 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.
+name=Apache Felix Http Service SSL Filter
+description=Configuration for the Http Service SSL Filter. Please consult the 
documentation of your proxy for the actual headers and values to use. 
+ssl-forward.header.name=SSL forward header
+ssl-forward.header.description=HTTP Request header name that indicates a 
request is a SSL request terminated at a proxy between the client and the 
originating server. The default value is 'X-Forwarded-SSL' as is customarily 
used in the wild. Other commonly used names are\: 'X-Forwarded-Proto' (Amazon 
ELB), 'X-Forwarded-Protocol' (alternative), and 'Front-End-Https' (Microsoft 
IIS).
+ssl-forward.value.name=SSL forward value
+ssl-forward.value.description=HTTP Request header value that indicates a 
request is a SSL request terminated at a proxy. The default value is 'on'. 
Another commonly used value is 'https'.
+ssl-forward-cert.header.name=SSL client header
+ssl-forward-cert.header.description=HTTP Request header name that contains the 
client certificate forwarded by a proxy. The default value is 
'X-Forwarded-SSL-Certificate'. Another commonly used value is 
'X-Forwarded-SSL-Client-Cert'.
+rewrite.absolute.urls.name=Rewrite Absolute URLs
+rewrite.absolute.urls.description=If enabled, absolute URLs passed to either 
sendRedirect or by setting the location header are rewritten as well.
diff --git 
a/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
 
b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
new file mode 100644
index 0000000000..91a82b3d1e
--- /dev/null
+++ 
b/http/sslfilter/src/main/resources/OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<metatype:MetaData xmlns:metatype="http://www.osgi.org/xmlns/metatype/v1.0.0"; 
localization="OSGI-INF/metatype/org.apache.felix.http.sslfilter.SslFilter">
+    <OCD id="org.apache.felix.http.sslfilter.SslFilter" name="%name" 
description="%description">
+        <AD id="ssl-forward.header" name="%ssl-forward.header.name" 
description="%ssl-forward.header.description"
+               type="String" default="X-Forwarded-SSL" required="true" />
+        <AD id="ssl-forward.value" name="%ssl-forward.value.name" 
description="%ssl-forward.value.description"
+               type="String" default="on" required="true" />
+        <AD id="ssl-forward-cert.header" name="%ssl-forward-cert.header.name" 
description="%ssl-forward-cert.header.description"
+               type="String" default="X-Forwarded-SSL-Certificate" 
required="false"/>
+        <AD id="rewrite.absolute.urls" name="%rewrite.absolute.urls.name" 
description="%rewrite.absolute.urls.description"
+            type="Boolean" default="false" required="false"/>
+    </OCD>
+    <Designate pid="org.apache.felix.http.sslfilter.SslFilter">
+        <Object ocdref="org.apache.felix.http.sslfilter.SslFilter"/>
+    </Designate>
+</metatype:MetaData>
+<!--
+    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.
+-->
diff --git 
a/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterJettyTest.java
 
b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterJettyTest.java
new file mode 100644
index 0000000000..81d8ec8dba
--- /dev/null
+++ 
b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterJettyTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.sslfilter.internal;
+
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_LOCATION;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_PROTO;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_SSL;
+import static org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTP;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTPS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SslFilterJettyTest
+{
+    private InetSocketAddress serverAddress;
+
+    private Server server;
+    private ServletContextHandler context;
+    private boolean originalFollowRedirects;
+
+    @Before
+    public void setupServer() throws Exception
+    {
+        this.serverAddress = new InetSocketAddress("localhost", 8080);
+
+        this.context = new 
ServletContextHandler(ServletContextHandler.SESSIONS);
+        this.context.setContextPath("/");
+        this.context.addFilter(new FilterHolder(new SslFilter()), "/*", 
EnumSet.of(DispatcherType.REQUEST));
+
+        this.server = new Server(this.serverAddress);
+        this.server.setHandler(this.context);
+
+        this.originalFollowRedirects = HttpURLConnection.getFollowRedirects();
+        HttpURLConnection.setFollowRedirects(false);
+    }
+
+    @After
+    public void tearDown() throws Exception
+    {
+        HttpURLConnection.setFollowRedirects(this.originalFollowRedirects);
+
+        if (this.server != null)
+        {
+            this.server.stop();
+        }
+    }
+
+    @Test
+    public void testSslFilterWithRelativeRedirectURL() throws Exception
+    {
+        String servletPath = "/test";
+        String redirectPath = "/foo";
+
+        this.context.addServlet(new ServletHolder(new 
RedirectServlet(redirectPath)), servletPath);
+        this.server.start();
+
+        HttpURLConnection conn = openConnection(createURL(servletPath));
+
+        assertEquals(302, conn.getResponseCode());
+        String location = conn.getHeaderField(HDR_LOCATION);
+        assertTrue(location, location.startsWith(HTTPS));
+    }
+
+    @Test
+    public void testSslFilterWithAbsoluteRedirectURL() throws Exception
+    {
+        String servletPath = "/test";
+        String redirectPath = String.format("http://%s:%d/foo";, 
this.serverAddress.getHostName(), this.serverAddress.getPort());
+
+        this.context.addServlet(new ServletHolder(new 
RedirectServlet(redirectPath)), servletPath);
+        this.server.start();
+
+        HttpURLConnection conn = openConnection(createURL(servletPath));
+
+        assertEquals(302, conn.getResponseCode());
+
+        String location = conn.getHeaderField(HDR_LOCATION);
+        assertTrue(location, location.startsWith(HTTP));
+    }
+
+    @Test
+    public void testSslFilterWithAbsoluteRedirectURLWithoutScheme() throws 
Exception
+    {
+        String servletPath = "/test";
+        String redirectPath = String.format("//%s:%d/foo", 
this.serverAddress.getHostName(), this.serverAddress.getPort());
+
+        this.context.addServlet(new ServletHolder(new 
RedirectServlet(redirectPath)), servletPath);
+        this.server.start();
+
+        HttpURLConnection conn = openConnection(createURL(servletPath));
+
+        assertEquals(302, conn.getResponseCode());
+
+        String location = conn.getHeaderField(HDR_LOCATION);
+        assertTrue(location, location.startsWith(HTTPS));
+    }
+
+    @Test
+    public void testSslFilterWithAbsoluteRedirectURLWithHttpsScheme() throws 
Exception
+    {
+        String servletPath = "/test";
+        String redirectPath = String.format("https://%s:%d/foo";, 
this.serverAddress.getHostName(), this.serverAddress.getPort());
+
+        this.context.addServlet(new ServletHolder(new 
RedirectServlet(redirectPath)), servletPath);
+        this.server.start();
+
+        HttpURLConnection conn = openConnection(createURL(servletPath));
+
+        assertEquals(302, conn.getResponseCode());
+
+        String location = conn.getHeaderField(HDR_LOCATION);
+        assertTrue(location, location.startsWith(HTTPS));
+    }
+
+    private HttpURLConnection openConnection(URL url) throws IOException
+    {
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestProperty(HDR_X_FORWARDED_PROTO, HTTPS);
+        conn.setRequestProperty(HDR_X_FORWARDED_SSL, "on");
+        conn.connect();
+        return conn;
+    }
+
+    private URL createURL(String path) throws MalformedURLException
+    {
+        return new URL(HTTP, this.serverAddress.getHostName(), 
this.serverAddress.getPort(), path);
+    }
+
+    private static class RedirectServlet extends HttpServlet
+    {
+        private final String redirectPath;
+
+        private RedirectServlet(String redirectPath)
+        {
+            this.redirectPath = redirectPath;
+        }
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException
+        {
+            resp.sendRedirect(redirectPath);
+            assertEquals(HTTPS, req.getScheme());
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java
 
b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java
new file mode 100644
index 0000000000..a92d5ce28b
--- /dev/null
+++ 
b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterRequestTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.sslfilter.internal;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_PORT;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class SslFilterRequestTest
+{
+    @Test
+    public void test_isSecure() throws Exception
+    {
+        HttpServletRequest req = mock(HttpServletRequest.class);
+        SslFilterRequest sreq = new SslFilterRequest(req, null);
+
+        when(req.isSecure()).thenReturn(false);
+        assertFalse(req.isSecure());
+        assertTrue(sreq.isSecure());
+        assertFalse(req.isSecure());
+
+        when(req.isSecure()).thenReturn(true);
+        assertTrue(req.isSecure());
+        assertTrue(sreq.isSecure());
+        assertTrue(req.isSecure());
+    }
+
+    @Test
+    public void test_getScheme() throws Exception
+    {
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        SslFilterRequest sreq = new SslFilterRequest(req, null);
+
+        when(req.getScheme()).thenReturn("http");
+        assertEquals("http", req.getScheme());
+        assertEquals("https", sreq.getScheme());
+        assertEquals("http", req.getScheme());
+
+        when(req.getScheme()).thenReturn("https");
+        assertEquals("https", req.getScheme());
+        assertEquals("https", sreq.getScheme());
+        assertEquals("https", req.getScheme());
+    }
+
+    @Test
+    public void test_getRequestURL() throws Exception
+    {
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        SslFilterRequest sreq = new SslFilterRequest(req, null);
+
+        when(req.getRequestURL()).thenReturn(new 
StringBuffer("http://some/page";));
+        assertEquals("http://some/page";, req.getRequestURL().toString());
+        assertEquals("https://some/page";, sreq.getRequestURL().toString());
+        assertEquals("http://some/page";, req.getRequestURL().toString());
+
+        when(req.getRequestURL()).thenReturn(new 
StringBuffer("https://some/page";));
+        assertEquals("https://some/page";, req.getRequestURL().toString());
+        assertEquals("https://some/page";, sreq.getRequestURL().toString());
+        assertEquals("https://some/page";, req.getRequestURL().toString());
+    }
+    
+    @Test
+    public void test_getServerPort() throws Exception
+    {
+        HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
+        SslFilterRequest sreq = new SslFilterRequest(req, null);
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn(null);        
+        when(req.getServerPort()).thenReturn(-1);
+        assertEquals(443, sreq.getServerPort());
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn("");        
+        when(req.getServerPort()).thenReturn(-1);
+        assertEquals(443, sreq.getServerPort());
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn("WRONG");        
+        when(req.getServerPort()).thenReturn(-1);
+        assertEquals(443, sreq.getServerPort());
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn("W1");        
+        when(req.getServerPort()).thenReturn(-1);
+        assertEquals(443, sreq.getServerPort());
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn("443");        
+        assertEquals(443, sreq.getServerPort());
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn("80");        
+        assertEquals(80, sreq.getServerPort());
+        
+        when(req.getHeader(HDR_X_FORWARDED_PORT)).thenReturn("4502");        
+        assertEquals(4502, sreq.getServerPort());
+        
+    }
+}
diff --git 
a/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterResponseTest.java
 
b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterResponseTest.java
new file mode 100644
index 0000000000..cebc0feb63
--- /dev/null
+++ 
b/http/sslfilter/src/test/java/org/apache/felix/http/sslfilter/internal/SslFilterResponseTest.java
@@ -0,0 +1,618 @@
+/*
+ * 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.sslfilter.internal;
+
+import static junit.framework.Assert.assertEquals;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HDR_X_FORWARDED_PROTO;
+import static org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTP;
+import static 
org.apache.felix.http.sslfilter.internal.SslFilterConstants.HTTPS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.sslfilter.internal.SslFilter.ConfigHolder;
+import org.junit.Test;
+
+public class SslFilterResponseTest
+{
+    private static final String BACKEND_SERVER = "backend.server";
+    private static final String OTHER_SERVER = "other.server";
+
+    private static final String PATH = "http://"; + BACKEND_SERVER + "/foo";
+
+    private static final String DEFAULT_HTTP_PORT = "80";
+    private static final String ALT_HTTP_PORT = "8080";
+    private static final String DEFAULT_HTTPS_PORT = "443";
+    private static final String ALT_HTTPS_PORT = "8443";
+
+    private static final String LOCATION = "Location";
+
+    @Test
+    public void testSetHttpLocationHeaderToNullValue() throws Exception
+    {
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req, cfg);
+
+        sresp.setHeader(LOCATION, null);
+
+        assertEquals(null, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testSetHttpsLocationHeaderToOriginalRequestURI() throws 
Exception
+    {
+        String location, expected;
+
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTPS + "://" + BACKEND_SERVER + "/foo";
+        expected = location;
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+
+        req = createServletRequest(BACKEND_SERVER, PATH);
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+
+        sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + "/foo";
+        expected = HTTPS + "://" + BACKEND_SERVER + "/foo";
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testSetHttpLocationHeaderToOriginalRequestURI() throws 
Exception
+    {
+        String location, expected;
+
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + "/foo";
+        expected = location;
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+
+        sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + "/foo";
+        expected = HTTPS + "://" + BACKEND_SERVER + "/foo";
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testSetHttpLocationHeaderToOriginalRequestWithExplicitPort() 
throws Exception
+    {
+        String location, expected;
+
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + ":" + DEFAULT_HTTP_PORT + 
"/foo";
+        expected = HTTP + "://" + BACKEND_SERVER + ":" + DEFAULT_HTTP_PORT + 
"/foo";
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+
+        resp = createServletResponse();
+        req = createServletRequest(BACKEND_SERVER, PATH);
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+
+        sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + ":" + DEFAULT_HTTP_PORT + 
"/foo";
+        expected = HTTPS + "://" + BACKEND_SERVER + "/foo";
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testSetHttpLocationHeaderToOriginalRequestWithForwardedPort() 
throws Exception
+    {
+        String location, expected;
+
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, 
DEFAULT_HTTP_PORT, HTTPS, ALT_HTTPS_PORT, PATH);
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + "/foo";
+        expected = HTTP + "://" + BACKEND_SERVER + "/foo";
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+
+        resp = createServletResponse();
+        req = createServletRequest(BACKEND_SERVER, DEFAULT_HTTP_PORT, HTTPS, 
ALT_HTTPS_PORT, PATH);
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+
+        sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + "/foo";
+        expected = HTTPS + "://" + BACKEND_SERVER + ":" + ALT_HTTPS_PORT + 
"/foo";
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testSetHttpLocationHeaderToOriginalRequestWithDifferentPort() 
throws Exception
+    {
+        String location, expected;
+
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + ":" + ALT_HTTP_PORT + 
"/foo";
+        expected = location;
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+
+        resp = createServletResponse();
+        req = createServletRequest(BACKEND_SERVER, PATH);
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+
+        sresp = new SslFilterResponse(resp, req, cfg);
+
+        location = HTTP + "://" + BACKEND_SERVER + ":" + ALT_HTTP_PORT + 
"/foo";
+        expected = location;
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testSetHttpLocationHeaderToOtherRequestURI() throws Exception
+    {
+        TestHttpServletResponse resp = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+
+        SslFilterResponse sresp = new SslFilterResponse(resp, req,cfg);
+
+        String location = HTTP + "://" + OTHER_SERVER + "/foo";
+        String expected = location;
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+
+        resp = createServletResponse();
+        req = createServletRequest(BACKEND_SERVER, PATH);
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+
+        sresp = new SslFilterResponse(resp, req,cfg);
+
+        location = HTTP + "://" + OTHER_SERVER + "/foo";
+        expected = location;
+
+        sresp.setHeader(LOCATION, location);
+
+        assertEquals(expected, resp.getHeader(LOCATION));
+    }
+
+    @Test
+    public void testFragment() throws Exception
+    {
+        test("/foo#abc");
+    }
+
+    @Test
+    public void testQueryString() throws Exception
+    {
+        final String queryString = 
"?resource=%2Fen.html%3FpbOpen%3Dtrue&$$login$$=%24%24login%24%24&j_reason=errors.login.account.not.found";
+        test("/" + queryString);
+    }
+
+    @Test
+    public void testPathEncoding() throws Exception
+    {
+        test("/apps/test/content/%E4%B8%83%E6%9C%88%E5%8F%B7.redirect");
+    }
+
+
+    private void test(final String path) throws Exception
+    {
+        TestHttpServletResponse response = createServletResponse();
+        HttpServletRequest req = createServletRequest(BACKEND_SERVER, PATH);
+
+        // test - don't rewrite absolute urls / absolute http url / 
sendRedirect
+        // expected: no rewrite
+        ConfigHolder cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", 
null, false);
+        SslFilterResponse sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.sendRedirect("http://"; + BACKEND_SERVER + path);
+        assertEquals("http://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - don't rewrite absolute urls / absolute http url / setHeader
+        // expected: no rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, false);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.setHeader(SslFilterConstants.HDR_LOCATION, "http://"; + 
BACKEND_SERVER + path);
+        assertEquals("http://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - don't rewrite absolute urls / absolute https url / 
sendRedirect
+        // expected: no rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, false);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.sendRedirect("https://"; + BACKEND_SERVER + path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - don't rewrite absolute urls / absolute https url / setHeader
+        // expected: no rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, false);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.setHeader(SslFilterConstants.HDR_LOCATION, "https://"; + 
BACKEND_SERVER + path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - rewrite absolute urls / absolute http url / sendRedirect
+        // expected: rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.sendRedirect("http://"; + BACKEND_SERVER + path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - rewrite absolute urls / absolute http url / setHeader
+        // expected: rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.setHeader(SslFilterConstants.HDR_LOCATION, "http://"; + 
BACKEND_SERVER + path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - rewrite absolute urls / absolute https url / sendRedirect
+        // expected: no rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.sendRedirect("https://"; + BACKEND_SERVER + path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - rewrite absolute urls / absolute https url / setHeader
+        // expected: no rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.setHeader(SslFilterConstants.HDR_LOCATION, "https://"; + 
BACKEND_SERVER + path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - don't rewrite absolute urls / relative path / setHeader
+        // expected: rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, false);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.sendRedirect(path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+
+        // test - rewrite absolute urls / relative path / sendRedirect
+        // expected: rewrite
+        cfg = new ConfigHolder(HDR_X_FORWARDED_PROTO, "https", null, true);
+        sresp = new SslFilterResponse(response, req, cfg);
+
+        sresp.setHeader(SslFilterConstants.HDR_LOCATION, path);
+        assertEquals("https://"; + BACKEND_SERVER + path, 
sresp.getHeader(SslFilterConstants.HDR_LOCATION));
+    }
+
+    private HttpServletRequest createServletRequest(String serverName, String 
requestURL)
+    {
+        return createServletRequest(serverName, DEFAULT_HTTP_PORT, HTTPS, 
DEFAULT_HTTPS_PORT, requestURL);
+    }
+
+    private HttpServletRequest createServletRequest(String serverName, String 
serverPort, String forwardedProto, String forwardedPort, String requestURL)
+    {
+        HttpServletRequest req = mock(HttpServletRequest.class);
+        when(req.getServerName()).thenReturn(serverName);
+        when(req.getServerPort()).thenReturn(Integer.parseInt(serverPort));
+        when(req.getRequestURL()).thenReturn(new StringBuffer(requestURL));
+        when(req.getHeader("X-Forwarded-Proto")).thenReturn(forwardedProto);
+        when(req.getHeader("X-Forwarded-Port")).thenReturn(forwardedPort);
+        return req;
+    }
+
+    private TestHttpServletResponse createServletResponse()
+    {
+        return new TestHttpServletResponse();
+    }
+
+    private static class TestHttpServletResponse implements HttpServletResponse
+    {
+        private final Map<String, String> headers = new HashMap<String, 
String>();
+        private int status = -1;
+        private boolean committed = false;
+
+        @Override
+        public void setLocale(Locale loc)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setContentType(String type)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setContentLength(int len)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setContentLengthLong(long len)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setCharacterEncoding(String charset)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setBufferSize(int size)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void resetBuffer()
+        {
+        }
+
+        @Override
+        public void reset()
+        {
+        }
+
+        @Override
+        public boolean isCommitted()
+        {
+            return this.committed;
+        }
+
+        @Override
+        public PrintWriter getWriter() throws IOException
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ServletOutputStream getOutputStream() throws IOException
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Locale getLocale()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getContentType()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getCharacterEncoding()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getBufferSize()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void flushBuffer() throws IOException
+        {
+            committed = true;
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public void setStatus(int sc, String sm)
+        {
+            status = sc;
+            committed = true;
+        }
+
+        @Override
+        public void setStatus(int sc)
+        {
+            status = sc;
+            committed = true;
+        }
+
+        @Override
+        public void setIntHeader(String name, int value)
+        {
+            headers.put(name, Integer.toString(value));
+        }
+
+        @Override
+        public void setHeader(String name, String value)
+        {
+            headers.put(name, value);
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void sendRedirect(String location) throws IOException
+        {
+            this.setHeader(SslFilterConstants.HDR_LOCATION, location);
+        }
+
+        @Override
+        public void sendError(int sc, String msg) throws IOException
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void sendError(int sc) throws IOException
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getStatus()
+        {
+            return status;
+        }
+
+        @Override
+        public Collection<String> getHeaders(String name)
+        {
+            return Collections.singleton(headers.get(name));
+        }
+
+        @Override
+        public Collection<String> getHeaderNames()
+        {
+            return headers.keySet();
+        }
+
+        @Override
+        public String getHeader(String name)
+        {
+            return headers.get(name);
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public String encodeUrl(String url)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String encodeURL(String url)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public String encodeRedirectUrl(String url)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String encodeRedirectURL(String url)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean containsHeader(String name)
+        {
+            return headers.containsKey(name);
+        }
+
+        @Override
+        public void addIntHeader(String name, int value)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void addCookie(Cookie cookie)
+        {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

Reply via email to