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:="(&(osgi.implementation=osgi.http)(version>=1.1)(!(version>=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();
+ }
+ }
+}