summaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/jdisc/http/filter
diff options
context:
space:
mode:
Diffstat (limited to 'container-core/src/main/java/com/yahoo/jdisc/http/filter')
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java543
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java154
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java42
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java79
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java133
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java67
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java14
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java9
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java45
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java14
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java9
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java108
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java13
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java77
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java8
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java101
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java169
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java81
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java24
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java24
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java55
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java54
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java29
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java5
-rw-r--r--container-core/src/main/java/com/yahoo/jdisc/http/filter/package-info.java7
25 files changed, 1864 insertions, 0 deletions
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java
new file mode 100644
index 00000000000..f7ab399574c
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterRequest.java
@@ -0,0 +1,543 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.HeaderFields;
+import com.yahoo.jdisc.http.Cookie;
+import com.yahoo.jdisc.http.HttpHeaders;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.HttpRequest.Version;
+import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpRequest;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+/**
+ * The Request class on which all filters will operate upon.
+ * Test cases that need a concrete
+ * instance should create a {@link JdiscFilterRequest}.
+ */
+public abstract class DiscFilterRequest {
+
+ protected static final String HTTPS_PREFIX = "https";
+ protected static final int DEFAULT_HTTP_PORT = 80;
+ protected static final int DEFAULT_HTTPS_PORT = 443;
+
+ private final ServletOrJdiscHttpRequest parent;
+ protected final Map<String, List<String>> untreatedParams;
+ private final HeaderFields untreatedHeaders;
+ private List<Cookie> untreatedCookies = null;
+ private String remoteUser = null;
+ private String[] roles = null;
+ private boolean overrideIsUserInRole = false;
+
+ public DiscFilterRequest(ServletOrJdiscHttpRequest parent) {
+ this.parent = parent;
+
+ // save untreated headers from parent
+ untreatedHeaders = new HeaderFields();
+ parent.copyHeaders(untreatedHeaders);
+
+ untreatedParams = new HashMap<>(parent.parameters());
+ }
+
+ public abstract String getMethod();
+
+ public Version getVersion() {
+ return parent.getVersion();
+ }
+
+ public URI getUri() {
+ return parent.getUri();
+ }
+
+ public abstract void setUri(URI uri);
+
+ public HttpRequest getParentRequest() {
+ throw new UnsupportedOperationException("getParentRequest is not supported for " + parent.getClass().getName());
+ }
+
+ /**
+ * Returns the Internet Protocol (IP) address of the client
+ * or last proxy that sent the request.
+ */
+ public String getRemoteAddr() {
+ return parent.getRemoteHostAddress();
+ }
+
+ /**
+ * Set the IP address of the remote client associated with this Request.
+ */
+ public void setRemoteAddr(String remoteIpAddress) {
+ InetSocketAddress remoteAddress = new InetSocketAddress(remoteIpAddress, this.getRemotePort());
+ parent.setRemoteAddress(remoteAddress);
+ }
+
+ /**
+ * Returns the Internet Protocol (IP) address of the interface
+ * on which the request was received.
+ */
+ public String getLocalAddr() {
+ InetSocketAddress localAddress = localAddress();
+ if (localAddress.getAddress() == null) return null;
+ return localAddress.getAddress().getHostAddress();
+ }
+
+ private InetSocketAddress localAddress() {
+ int port = parent.getUri().getPort();
+ if (port < 0)
+ port = 0;
+ return new InetSocketAddress(parent.getUri().getHost(), port);
+ }
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(parent.context().keySet());
+ }
+
+ public Object getAttribute(String name) {
+ return parent.context().get(name);
+ }
+
+ public void setAttribute(String name, Object value) {
+ parent.context().put(name, value);
+ }
+
+ public boolean containsAttribute(String name) {
+ return parent.context().containsKey(name);
+ }
+
+ public void removeAttribute(String name) {
+ parent.context().remove(name);
+ }
+
+ public abstract String getParameter(String name);
+
+ public abstract Enumeration<String> getParameterNames();
+
+ public List<String> getParameterNamesAsList() {
+ return new ArrayList<String>(parent.parameters().keySet());
+ }
+
+ public Enumeration<String> getParameterValues(String name) {
+ return Collections.enumeration(parent.parameters().get(name));
+ }
+
+ public List<String> getParameterValuesAsList(String name) {
+ return parent.parameters().get(name);
+ }
+
+ public Map<String,List<String>> getParameterMap() {
+ return parent.parameters();
+ }
+
+
+ /**
+ * Returns the hostName of remoteHost, or null if none
+ */
+ public String getRemoteHost() {
+ return parent.getRemoteHostName();
+ }
+
+ /**
+ * Returns the Internet Protocol (IP) port number of
+ * the interface on which the request was received.
+ */
+ public int getLocalPort() {
+ return localAddress().getPort();
+ }
+
+ /**
+ * Returns the port of remote host
+ */
+ public int getRemotePort() {
+ return parent.getRemotePort();
+ }
+
+ /**
+ * Returns a unmodifiable map of untreatedParameters from the
+ * parent request.
+ */
+ public Map<String, List<String>> getUntreatedParams() {
+ return Collections.unmodifiableMap(untreatedParams);
+ }
+
+
+ /**
+ * Returns the untreatedHeaders from
+ * parent request
+ */
+ public HeaderFields getUntreatedHeaders() {
+ return untreatedHeaders;
+ }
+
+ /**
+ * Returns the untreatedCookies from
+ * parent request
+ */
+ public List<Cookie> getUntreatedCookies() {
+ if (untreatedCookies == null) {
+ this.untreatedCookies = parent.decodeCookieHeader();
+ }
+ return Collections.unmodifiableList(untreatedCookies);
+ }
+
+ /**
+ * Sets a header with the given name and value.
+ * If the header had already been set, the new value overwrites the previous one.
+ */
+ public abstract void addHeader(String name, String value);
+
+ public long getDateHeader(String name) {
+ String value = getHeader(name);
+ if (value == null)
+ return -1L;
+
+ Date date = null;
+ for (int i = 0; (date == null) && (i < formats.length); i++) {
+ try {
+ date = formats[i].parse(value);
+ } catch (ParseException e) {
+ }
+ }
+ if (date == null) {
+ return -1L;
+ }
+
+ return date.getTime();
+ }
+
+ public abstract String getHeader(String name);
+
+ public abstract Enumeration<String> getHeaderNames();
+
+ public abstract List<String> getHeaderNamesAsList();
+
+ public abstract Enumeration<String> getHeaders(String name);
+
+ public abstract List<String> getHeadersAsList(String name);
+
+ public abstract void removeHeaders(String name);
+
+ /**
+ * Sets a header with the given name and value.
+ * If the header had already been set, the new value overwrites the previous one.
+ *
+ */
+ public abstract void setHeaders(String name, String value);
+
+ /**
+ * Sets a header with the given name and value.
+ * If the header had already been set, the new value overwrites the previous one.
+ *
+ */
+ public abstract void setHeaders(String name, List<String> values);
+
+ public int getIntHeader(String name) {
+ String value = getHeader(name);
+ if (value == null) {
+ return -1;
+ } else {
+ return Integer.parseInt(value);
+ }
+ }
+
+
+ public List<Cookie> getCookies() {
+ return parent.decodeCookieHeader();
+ }
+
+ public void setCookies(List<Cookie> cookies) {
+ parent.encodeCookieHeader(cookies);
+ }
+
+ public long getConnectedAt(TimeUnit unit) {
+ return parent.getConnectedAt(unit);
+ }
+
+ public String getProtocol() {
+ return getVersion().name();
+ }
+
+ /**
+ * Returns the query string that is contained in the request URL.
+ * Returns the undecoded value uri.getRawQuery()
+ */
+ public String getQueryString() {
+ return getUri().getRawQuery();
+ }
+
+ /**
+ * Returns the login of the user making this request,
+ * if the user has been authenticated, or null if the user has not been authenticated.
+ */
+ public String getRemoteUser() {
+ return remoteUser;
+ }
+
+ public String getRequestURI() {
+ return getUri().getRawPath();
+ }
+
+ public String getRequestedSessionId() {
+ return null;
+ }
+
+ public String getScheme() {
+ return getUri().getScheme();
+ }
+
+ public void setScheme(String scheme, boolean isSecure) {
+ String uri = getUri().toString();
+ String arr [] = uri.split("://");
+ URI newUri = URI.create(scheme + "://" + arr[1]);
+ setUri(newUri);
+ }
+
+ public String getServerName() {
+ return getUri().getHost();
+ }
+
+ public int getServerPort() {
+ int port = getUri().getPort();
+ if(port == -1) {
+ if(isSecure()) {
+ port = DEFAULT_HTTPS_PORT;
+ }
+ else {
+ port = DEFAULT_HTTP_PORT;
+ }
+ }
+
+ return port;
+ }
+
+ public abstract Principal getUserPrincipal();
+
+ public boolean isSecure() {
+ if(getScheme().equalsIgnoreCase(HTTPS_PREFIX)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns a boolean indicating whether the authenticated user
+ * is included in the specified logical "role".
+ */
+ public boolean isUserInRole(String role) {
+ if (overrideIsUserInRole) {
+ if (roles != null) {
+ for (String role1 : roles) {
+ if (role1 != null && role1.trim().length() > 0) {
+ String userRole = role1.trim();
+ if (userRole.equals(role)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+ else {
+ return false;
+ }
+ }
+
+ public void setOverrideIsUserInRole(boolean overrideIsUserInRole) {
+ this.overrideIsUserInRole = overrideIsUserInRole;
+ }
+
+ public void setRemoteHost(String remoteAddr) { }
+
+ public void setRemoteUser(String remoteUser) {
+ this.remoteUser = remoteUser;
+ }
+
+ public abstract void setUserPrincipal(Principal principal);
+
+ /**
+ * @return The client certificate chain in ascending order of trust. The first certificate is the one sent from the client.
+ * Returns an empty list if the client did not provide a certificate.
+ */
+ public abstract List<X509Certificate> getClientCertificateChain();
+
+ public void setUserRoles(String[] roles) {
+ this.roles = roles;
+ }
+
+ /**
+ * Returns the content-type for the request
+ */
+ public String getContentType() {
+ return getHeader(HttpHeaders.Names.CONTENT_TYPE);
+ }
+
+
+ /**
+ * Get character encoding
+ */
+ public String getCharacterEncoding() {
+ return getCharsetFromContentType(this.getContentType());
+ }
+
+ /**
+ * Set character encoding
+ */
+ public void setCharacterEncoding(String encoding) {
+ String charEncoding = setCharsetFromContentType(this.getContentType(), encoding);
+ if (charEncoding != null && !charEncoding.isEmpty()) {
+ removeHeaders(HttpHeaders.Names.CONTENT_TYPE);
+ setHeaders(HttpHeaders.Names.CONTENT_TYPE, charEncoding);
+ }
+ }
+
+ /**
+ * Can be called multiple times to add Cookies
+ */
+ public void addCookie(JDiscCookieWrapper cookie) {
+ if (cookie != null) {
+ List<Cookie> cookies = new ArrayList<>();
+ // Get current set of cookies first
+ List<Cookie> c = getCookies();
+ if (c != null && !c.isEmpty()) {
+ cookies.addAll(c);
+ }
+ cookies.add(cookie.getCookie());
+ setCookies(cookies);
+ }
+ }
+
+ public abstract void clearCookies();
+
+ public JDiscCookieWrapper[] getWrappedCookies() {
+ List<Cookie> cookies = getCookies();
+ if (cookies == null) {
+ return null;
+ }
+ List<JDiscCookieWrapper> cookieWrapper = new ArrayList<>(cookies.size());
+ for(Cookie cookie : cookies) {
+ cookieWrapper.add(JDiscCookieWrapper.wrap(cookie));
+ }
+
+ return cookieWrapper.toArray(new JDiscCookieWrapper[cookieWrapper.size()]);
+ }
+
+ private String setCharsetFromContentType(String contentType,String charset) {
+ String newContentType = "";
+ if (contentType == null)
+ return (null);
+ int start = contentType.indexOf("charset=");
+ if (start < 0) {
+ //No charset present:
+ newContentType = contentType + ";charset=" + charset;
+ return newContentType;
+ }
+ String encoding = contentType.substring(start + 8);
+ int end = encoding.indexOf(';');
+ if (end >= 0) {
+ newContentType = contentType.substring(0,start);
+ newContentType = newContentType + "charset=" + charset;
+ newContentType = newContentType + encoding.substring(end,encoding.length());
+ }
+ else {
+ newContentType = contentType.substring(0,start);
+ newContentType = newContentType + "charset=" + charset;
+ }
+
+ return (newContentType.trim());
+
+ }
+
+ private String getCharsetFromContentType(String contentType) {
+
+ if (contentType == null)
+ return (null);
+ int start = contentType.indexOf("charset=");
+ if (start < 0)
+ return (null);
+ String encoding = contentType.substring(start + 8);
+ int end = encoding.indexOf(';');
+ if (end >= 0)
+ encoding = encoding.substring(0, end);
+ encoding = encoding.trim();
+ if ((encoding.length() > 2) && (encoding.startsWith("\""))
+ && (encoding.endsWith("\"")))
+ encoding = encoding.substring(1, encoding.length() - 1);
+ return (encoding.trim());
+
+ }
+
+ public static boolean isMultipart(DiscFilterRequest request) {
+ if (request == null) {
+ return false;
+ }
+
+ String contentType = request.getContentType();
+
+ if (contentType == null) {
+ return false;
+ }
+
+ String[] parts = Pattern.compile(";").split(contentType);
+ if (parts.length == 0) {
+ return false;
+ }
+
+ for (String part : parts) {
+ if ("multipart/form-data".equals(part)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected static ThreadLocalSimpleDateFormat formats[] = {
+ new ThreadLocalSimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+ new ThreadLocalSimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+ new ThreadLocalSimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };
+
+ /**
+ * The set of SimpleDateFormat formats to use in getDateHeader().
+ *
+ * Notice that because SimpleDateFormat is not thread-safe, we can't declare
+ * formats[] as a static variable.
+ */
+ protected static final class ThreadLocalSimpleDateFormat extends ThreadLocal<SimpleDateFormat> {
+
+ private final String format;
+ private final Locale locale;
+
+ public ThreadLocalSimpleDateFormat(String format, Locale locale) {
+ super();
+ this.format = format;
+ this.locale = locale;
+ }
+
+ // @see java.lang.ThreadLocal#initialValue()
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat(format, locale);
+ }
+
+ public Date parse(String value) throws ParseException {
+ return get().parse(value);
+ }
+
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java
new file mode 100644
index 00000000000..4e8b779c516
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/DiscFilterResponse.java
@@ -0,0 +1,154 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
+import com.yahoo.jdisc.http.servlet.ServletOrJdiscHttpResponse;
+
+import com.yahoo.jdisc.HeaderFields;
+import com.yahoo.jdisc.http.Cookie;
+
+
+import com.yahoo.jdisc.http.HttpResponse;
+
+/**
+ * This class was made abstract from 5.27. Test cases that need
+ * a concrete instance should create a {@link JdiscFilterResponse}.
+ *
+ * @author tejalk
+ */
+public abstract class DiscFilterResponse {
+
+ private final ServletOrJdiscHttpResponse parent;
+ private final HeaderFields untreatedHeaders;
+ private final List<Cookie> untreatedCookies;
+
+ public DiscFilterResponse(ServletOrJdiscHttpResponse parent) {
+ this.parent = parent;
+
+ this.untreatedHeaders = new HeaderFields();
+ parent.copyHeaders(untreatedHeaders);
+
+ this.untreatedCookies = getCookies();
+ }
+
+ /* Attributes on the response are only used for unit testing.
+ * There is no such thing as 'attributes' in the underlying response. */
+
+ public Enumeration<String> getAttributeNames() {
+ return Collections.enumeration(parent.context().keySet());
+ }
+
+ public Object getAttribute(String name) {
+ return parent.context().get(name);
+ }
+
+ public void setAttribute(String name, Object value) {
+ parent.context().put(name, value);
+ }
+
+ public void removeAttribute(String name) {
+ parent.context().remove(name);
+ }
+
+ /**
+ * Returns the untreatedHeaders from the parent request
+ */
+ public HeaderFields getUntreatedHeaders() {
+ return untreatedHeaders;
+ }
+
+ /**
+ * Returns the untreatedCookies from the parent request
+ */
+ public List<Cookie> getUntreatedCookies() {
+ return untreatedCookies;
+ }
+
+ /**
+ * Sets a header with the given name and value.
+ * <p>
+ * If the header had already been set, the new value overwrites the previous one.
+ */
+ public abstract void setHeader(String name, String value);
+
+ public abstract void removeHeaders(String name);
+
+ /**
+ * Sets a header with the given name and value.
+ * <p>
+ * If the header had already been set, the new value overwrites the previous one.
+ */
+ public abstract void setHeaders(String name, String value);
+
+ /**
+ * Sets a header with the given name and value.
+ * <p>
+ * If the header had already been set, the new value overwrites the previous one.
+ */
+ public abstract void setHeaders(String name, List<String> values);
+
+ /**
+ * Adds a header with the given name and value
+ * @see com.yahoo.jdisc.HeaderFields#add
+ */
+ public abstract void addHeader(String name, String value);
+
+ public abstract String getHeader(String name);
+
+ public List<Cookie> getCookies() {
+ return parent.decodeSetCookieHeader();
+ }
+
+ public abstract void setCookies(List<Cookie> cookies);
+
+ public int getStatus() {
+ return parent.getStatus();
+ }
+
+ public abstract void setStatus(int status);
+
+ /**
+ * Return the parent HttpResponse
+ */
+ public HttpResponse getParentResponse() {
+ if (parent instanceof HttpResponse)
+ return (HttpResponse)parent;
+ throw new UnsupportedOperationException(
+ "getParentResponse is not supported for " + parent.getClass().getName());
+ }
+
+ public void addCookie(JDiscCookieWrapper cookie) {
+ if(cookie != null) {
+ List<Cookie> cookies = new ArrayList<>();
+ //Get current set of cookies first
+ List<Cookie> c = getCookies();
+ if((c != null) && (! c.isEmpty())) {
+ cookies.addAll(c);
+ }
+ cookies.add(cookie.getCookie());
+ setCookies(cookies);
+ }
+ }
+
+ /**
+ * This method does not actually send the response as it
+ * does not have access to responseHandler but
+ * just sets the status. The methodName is misleading
+ * for historical reasons.
+ */
+ public void sendError(int errorCode) throws IOException {
+ setStatus(errorCode);
+ }
+
+ public void setCookie(String name, String value) {
+ Cookie cookie = new Cookie(name, value);
+ setCookies(Arrays.asList(cookie));
+ }
+
+ }
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java
new file mode 100644
index 00000000000..af9e2b5e99a
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/FilterConfig.java
@@ -0,0 +1,42 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import java.util.Collection;
+
+/**
+ * Legacy filter config. Prefer to use a regular stringly typed config class for new filters.
+ *
+ * @author tejalk
+ */
+public interface FilterConfig {
+
+ /** Returns the filter-name of this filter */
+ String getFilterName();
+
+ /** Returns the filter-class of this filter */
+ String getFilterClass();
+
+ /**
+ * Returns a String containing the value of the
+ * named initialization parameter, or null if
+ * the parameter does not exist.
+ *
+ * @param name a String specifying the name of the initialization parameter
+ * @return a String containing the value of the initialization parameter
+ */
+ String getInitParameter(String name);
+
+ /**
+ * Returns the boolean value of the init parameter. If not present returns default value
+ *
+ * @return boolean value of init parameter
+ */
+ boolean getBooleanInitParameter(String name, boolean defaultValue);
+
+ /**
+ * Returns the names of the filter's initialization parameters as an Collection of String objects,
+ * or an empty Collection if the filter has no initialization parameters.
+ */
+ Collection<String> getInitParameterNames();
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java
new file mode 100644
index 00000000000..2b9c650d545
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JDiscCookieWrapper.java
@@ -0,0 +1,79 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.http.Cookie;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Wrapper of Cookie.
+ *
+ * @author Tejal Knot
+ *
+ */
+public class JDiscCookieWrapper {
+
+ private Cookie cookie;
+
+ protected JDiscCookieWrapper(Cookie cookie) {
+ this.cookie = cookie;
+ }
+
+ public static JDiscCookieWrapper wrap(Cookie cookie) {
+ return new JDiscCookieWrapper(cookie);
+ }
+
+ public String getDomain() {
+ return cookie.getDomain();
+ }
+
+ public int getMaxAge() {
+ return cookie.getMaxAge(TimeUnit.SECONDS);
+ }
+
+ public String getName() {
+ return cookie.getName();
+ }
+
+ public String getPath() {
+ return cookie.getPath();
+ }
+
+ public boolean getSecure() {
+ return cookie.isSecure();
+ }
+
+ public String getValue() {
+ return cookie.getValue();
+ }
+
+ public void setDomain(String pattern) {
+ cookie.setDomain(pattern);
+ }
+
+ public void setMaxAge(int expiry) {
+ cookie.setMaxAge(expiry, TimeUnit.SECONDS);
+ }
+
+ public void setPath(String uri) {
+ cookie.setPath(uri);
+ }
+
+ public void setSecure(boolean flag) {
+ cookie.setSecure(flag);
+ }
+
+ public void setValue(String newValue) {
+ cookie.setValue(newValue);
+ }
+
+ /**
+ * Return com.yahoo.jdisc.http.Cookie
+ *
+ * @return - cookie
+ */
+ public Cookie getCookie() {
+ return cookie;
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java
new file mode 100644
index 00000000000..f8d9e6b2642
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterRequest.java
@@ -0,0 +1,133 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.http.HttpHeaders;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.servlet.ServletRequest;
+
+import java.net.URI;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * JDisc implementation of a filter request.
+ *
+ * @since 5.27
+ */
+public class JdiscFilterRequest extends DiscFilterRequest {
+
+ private final HttpRequest parent;
+
+ public JdiscFilterRequest(HttpRequest parent) {
+ super(parent);
+ this.parent = parent;
+ }
+
+ public HttpRequest getParentRequest() {
+ return parent;
+ }
+
+ public void setUri(URI uri) {
+ parent.setUri(uri);
+ }
+
+ @Override
+ public String getMethod() {
+ return parent.getMethod().name();
+ }
+
+ @Override
+ public String getParameter(String name) {
+ if(parent.parameters().containsKey(name)) {
+ return parent.parameters().get(name).get(0);
+ }
+ else {
+ return null;
+ }
+ }
+
+ @Override
+ public Enumeration<String> getParameterNames() {
+ return Collections.enumeration(parent.parameters().keySet());
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ parent.headers().add(name, value);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ List<String> values = parent.headers().get(name);
+ if (values == null || values.isEmpty()) {
+ return null;
+ }
+ return values.get(values.size() - 1);
+ }
+
+ public Enumeration<String> getHeaderNames() {
+ return Collections.enumeration(parent.headers().keySet());
+ }
+
+ public List<String> getHeaderNamesAsList() {
+ return new ArrayList<String>(parent.headers().keySet());
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ return Collections.enumeration(getHeadersAsList(name));
+ }
+
+ public List<String> getHeadersAsList(String name) {
+ List<String> values = parent.headers().get(name);
+ if(values == null) {
+ return Collections.<String>emptyList();
+ }
+ return parent.headers().get(name);
+ }
+
+ @Override
+ public void removeHeaders(String name) {
+ parent.headers().remove(name);
+ }
+
+ @Override
+ public void setHeaders(String name, String value) {
+ parent.headers().put(name, value);
+ }
+
+ @Override
+ public void setHeaders(String name, List<String> values) {
+ parent.headers().put(name, values);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return parent.getUserPrincipal();
+ }
+
+ @Override
+ public void setUserPrincipal(Principal principal) {
+ this.parent.setUserPrincipal(principal);
+ }
+
+ @Override
+ public List<X509Certificate> getClientCertificateChain() {
+ return Optional.ofNullable(parent.context().get(ServletRequest.JDISC_REQUEST_X509CERT))
+ .map(X509Certificate[].class::cast)
+ .map(Arrays::asList)
+ .orElse(Collections.emptyList());
+ }
+
+ @Override
+ public void clearCookies() {
+ parent.headers().remove(HttpHeaders.Names.COOKIE);
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java
new file mode 100644
index 00000000000..ff81359f93c
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/JdiscFilterResponse.java
@@ -0,0 +1,67 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.http.Cookie;
+import com.yahoo.jdisc.http.HttpResponse;
+
+import java.util.List;
+
+/**
+ * JDisc implementation of a filter request.
+ *
+ * @since 5.27
+ */
+public class JdiscFilterResponse extends DiscFilterResponse {
+
+ private final HttpResponse parent;
+
+ public JdiscFilterResponse(HttpResponse parent) {
+ super(parent);
+ this.parent = parent;
+ }
+
+ @Override
+ public void setStatus(int status) {
+ parent.setStatus(status);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ parent.headers().put(name, value);
+ }
+
+ @Override
+ public void removeHeaders(String name) {
+ parent.headers().remove(name);
+ }
+
+ @Override
+ public void setHeaders(String name, String value) {
+ parent.headers().put(name, value);
+ }
+
+ @Override
+ public void setHeaders(String name, List<String> values) {
+ parent.headers().put(name, values);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ parent.headers().add(name, value);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ List<String> values = parent.headers().get(name);
+ if (values == null || values.isEmpty()) {
+ return null;
+ }
+ return values.get(values.size() - 1);
+ }
+
+ @Override
+ public void setCookies(List<Cookie> cookies) {
+ parent.encodeSetCookieHeader(cookies);
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java
new file mode 100644
index 00000000000..977e3ab5d1d
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilter.java
@@ -0,0 +1,14 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.HttpRequest;
+
+/**
+ * @author Einar M R Rosenvinge
+ */
+public interface RequestFilter extends com.yahoo.jdisc.SharedResource, RequestFilterBase {
+
+ void filter(HttpRequest request, ResponseHandler handler);
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java
new file mode 100644
index 00000000000..4eb7091f378
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestFilterBase.java
@@ -0,0 +1,9 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+/**
+ * @author gjoranv
+ * @since 2.4
+ */
+public interface RequestFilterBase {
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java
new file mode 100644
index 00000000000..e5e7ae1ef56
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/RequestView.java
@@ -0,0 +1,45 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.http.HttpRequest.Method;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Read-only view of the request for use by SecurityResponseFilters.
+ *
+ * @author Tony Vaagenes
+ */
+public interface RequestView {
+
+ /**
+ * Returns a named attribute.
+ *
+ * @see <a href="http://docs.oracle.com/javaee/7/api/javax/servlet/ServletRequest.html#getAttribute%28java.lang.String%29">javax.servlet.ServletRequest.getAttribute(java.lang.String)</a>
+ * @see com.yahoo.jdisc.Request#context()
+ * @return the named data associated with the request that are private to this runtime (not exposed to the client)
+ */
+ Object getAttribute(String name);
+
+ /**
+ * Returns an immutable view of all values of a named header field.
+ * Returns an empty list if no such header is present.
+ */
+ List<String> getHeaders(String name);
+
+ /**
+ * Convenience method for retrieving the first value of a named header field.
+ * Returns empty if the header is not set, or if the value list is empty.
+ */
+ Optional<String> getFirstHeader(String name);
+
+ /**
+ * Returns the Http method. Only present if the underlying request has http-like semantics.
+ */
+ Optional<Method> getMethod();
+
+ URI getUri();
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java
new file mode 100644
index 00000000000..44fe7d9fcf1
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilter.java
@@ -0,0 +1,14 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.SharedResource;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public interface ResponseFilter extends SharedResource, ResponseFilterBase {
+
+ public void filter(Response response, Request request);
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java
new file mode 100644
index 00000000000..b869c882351
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ResponseFilterBase.java
@@ -0,0 +1,9 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+/**
+ * @author gjoranv
+ * @since 2.4
+ */
+public interface ResponseFilterBase {
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java
new file mode 100644
index 00000000000..cbed273b7ee
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityFilterInvoker.java
@@ -0,0 +1,108 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.google.common.annotations.Beta;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.HttpRequest.Method;
+import com.yahoo.jdisc.http.servlet.ServletRequest;
+
+import com.yahoo.jdisc.http.servlet.ServletResponse;
+import com.yahoo.jdisc.http.server.jetty.FilterInvoker;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Only intended for internal vespa use.
+ *
+ * Runs JDisc security filter without using JDisc request/response.
+ * Only intended to be used in a servlet context, as the error messages are tailored for that.
+ *
+ * Assumes that SecurityResponseFilters mutate DiscFilterResponse in the thread they are invoked from.
+ *
+ * @author Tony Vaagenes
+ */
+@Beta
+public class SecurityFilterInvoker implements FilterInvoker {
+
+ /**
+ * Returns the servlet request to be used in any servlets invoked after this.
+ */
+ @Override
+ public HttpServletRequest invokeRequestFilterChain(RequestFilter requestFilterChain,
+ URI uri, HttpServletRequest httpRequest,
+ ResponseHandler responseHandler) {
+
+ SecurityRequestFilterChain securityChain = cast(SecurityRequestFilterChain.class, requestFilterChain).
+ orElseThrow(SecurityFilterInvoker::newUnsupportedOperationException);
+
+ ServletRequest wrappedRequest = new ServletRequest(httpRequest, uri);
+ securityChain.filter(new ServletFilterRequest(wrappedRequest), responseHandler);
+ return wrappedRequest;
+ }
+
+ @Override
+ public void invokeResponseFilterChain(
+ ResponseFilter responseFilterChain,
+ URI uri,
+ HttpServletRequest request,
+ HttpServletResponse response) {
+
+ SecurityResponseFilterChain securityChain = cast(SecurityResponseFilterChain.class, responseFilterChain).
+ orElseThrow(SecurityFilterInvoker::newUnsupportedOperationException);
+
+ ServletFilterResponse wrappedResponse = new ServletFilterResponse(new ServletResponse(response));
+ securityChain.filter(new ServletRequestView(uri, request), wrappedResponse);
+ }
+
+ private static UnsupportedOperationException newUnsupportedOperationException() {
+ return new UnsupportedOperationException(
+ "Filter type not supported. If a request is handled by servlets or jax-rs, then any filters invoked for that request must be security filters.");
+ }
+
+ private <T> Optional<T> cast(Class<T> securityFilterChainClass, Object filter) {
+ return (securityFilterChainClass.isInstance(filter))?
+ Optional.of(securityFilterChainClass.cast(filter)):
+ Optional.empty();
+ }
+
+ private static class ServletRequestView implements RequestView {
+ private final HttpServletRequest request;
+ private final URI uri;
+
+ public ServletRequestView(URI uri, HttpServletRequest request) {
+ this.request = request;
+ this.uri = uri;
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return request.getAttribute(name);
+ }
+
+ @Override
+ public List<String> getHeaders(String name) {
+ return Collections.unmodifiableList(Collections.list(request.getHeaders(name)));
+ }
+
+ @Override
+ public Optional<String> getFirstHeader(String name) {
+ return getHeaders(name).stream().findFirst();
+ }
+
+ @Override
+ public Optional<Method> getMethod() {
+ return Optional.of(Method.valueOf(request.getMethod()));
+ }
+
+ @Override
+ public URI getUri() {
+ return uri;
+ }
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java
new file mode 100644
index 00000000000..e6f4add49de
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilter.java
@@ -0,0 +1,13 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.handler.ResponseHandler;
+
+/**
+ * @author Simon Thoresen Hult
+ */
+public interface SecurityRequestFilter extends RequestFilterBase {
+
+ void filter(DiscFilterRequest request, ResponseHandler handler);
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java
new file mode 100644
index 00000000000..2d97bbdc494
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityRequestFilterChain.java
@@ -0,0 +1,77 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+
+import com.yahoo.jdisc.http.HttpRequest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Implementation of TypedFilterChain for DiscFilterRequest
+ *
+ * @author tejalk
+ */
+public final class SecurityRequestFilterChain extends AbstractResource implements RequestFilter {
+
+ private final List<SecurityRequestFilter> filters = new ArrayList<>();
+
+ private SecurityRequestFilterChain(Iterable<? extends SecurityRequestFilter> filters) {
+ for (SecurityRequestFilter filter : filters) {
+ this.filters.add(filter);
+ }
+ }
+
+ @Override
+ public void filter(HttpRequest request, ResponseHandler responseHandler) {
+ DiscFilterRequest discFilterRequest = new JdiscFilterRequest(request);
+ filter(discFilterRequest, responseHandler);
+ }
+
+ public void filter(DiscFilterRequest request, ResponseHandler responseHandler) {
+ ResponseHandlerGuard guard = new ResponseHandlerGuard(responseHandler);
+ for (int i = 0, len = filters.size(); i < len && !guard.isDone(); ++i) {
+ filters.get(i).filter(request, guard);
+ }
+ }
+
+ public static RequestFilter newInstance(SecurityRequestFilter... filters) {
+ return newInstance(Arrays.asList(filters));
+ }
+
+ public static RequestFilter newInstance(List<? extends SecurityRequestFilter> filters) {
+ return new SecurityRequestFilterChain(filters);
+ }
+
+ private static class ResponseHandlerGuard implements ResponseHandler {
+
+ private final ResponseHandler responseHandler;
+ private boolean done = false;
+
+ public ResponseHandlerGuard(ResponseHandler handler) {
+ this.responseHandler = handler;
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ done = true;
+ return responseHandler.handleResponse(response);
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+ }
+
+ /** Returns an unmodifiable view of the filters in this */
+ public List<SecurityRequestFilter> getFilters() {
+ return Collections.unmodifiableList(filters);
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java
new file mode 100644
index 00000000000..aa4f7d29b89
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilter.java
@@ -0,0 +1,8 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+public interface SecurityResponseFilter extends ResponseFilterBase {
+
+ void filter(DiscFilterResponse response, RequestView request);
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java
new file mode 100644
index 00000000000..d45b406a375
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/SecurityResponseFilterChain.java
@@ -0,0 +1,101 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.HttpResponse;
+
+/**
+ * Implementation of TypedFilterChain for DiscFilterResponse
+ * @author tejalk
+ *
+ */
+public class SecurityResponseFilterChain extends AbstractResource implements ResponseFilter {
+
+ private final List<SecurityResponseFilter> filters = new ArrayList<>();
+
+ private SecurityResponseFilterChain(Iterable<? extends SecurityResponseFilter> filters) {
+ for (SecurityResponseFilter filter : filters) {
+ this.filters.add(filter);
+ }
+ }
+
+ @Override
+ public void filter(Response response, Request request) {
+ if(response instanceof HttpResponse) {
+ DiscFilterResponse discFilterResponse = new JdiscFilterResponse((HttpResponse)response);
+ RequestView requestView = new RequestViewImpl(request);
+ filter(requestView, discFilterResponse);
+ }
+
+ }
+
+ public void filter(RequestView requestView, DiscFilterResponse response) {
+ for (SecurityResponseFilter filter : filters) {
+ filter.filter(response, requestView);
+ }
+ }
+
+ public static ResponseFilter newInstance(SecurityResponseFilter... filters) {
+ return newInstance(Arrays.asList(filters));
+ }
+
+ public static ResponseFilter newInstance(List<? extends SecurityResponseFilter> filters) {
+ return new SecurityResponseFilterChain(filters);
+ }
+
+ /** Returns an unmodifiable view of the filters in this */
+ public List<SecurityResponseFilter> getFilters() {
+ return Collections.unmodifiableList(filters);
+ }
+
+ static class RequestViewImpl implements RequestView {
+
+ private final Request request;
+ private final Optional<HttpRequest.Method> method;
+
+ public RequestViewImpl(Request request) {
+ this.request = request;
+ method = request instanceof HttpRequest ?
+ Optional.of(((HttpRequest) request).getMethod()):
+ Optional.empty();
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ return request.context().get(name);
+ }
+
+ @Override
+ public List<String> getHeaders(String name) {
+ List<String> headers = request.headers().get(name);
+ return headers == null ? Collections.emptyList() : Collections.unmodifiableList(headers);
+ }
+
+ @Override
+ public Optional<String> getFirstHeader(String name) {
+ return getHeaders(name).stream().findFirst();
+ }
+
+ @Override
+ public Optional<HttpRequest.Method> getMethod() {
+ return method;
+ }
+
+ @Override
+ public URI getUri() {
+ return request.getUri();
+ }
+
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java
new file mode 100644
index 00000000000..f06f9e256ff
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterRequest.java
@@ -0,0 +1,169 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.jdisc.http.HttpHeaders;
+import com.yahoo.jdisc.http.servlet.ServletRequest;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Servlet implementation for JDisc filter requests.
+ */
+class ServletFilterRequest extends DiscFilterRequest {
+
+ private final ServletRequest parent;
+
+ public ServletFilterRequest(ServletRequest parent) {
+ super(parent);
+ this.parent = parent;
+ }
+
+ ServletRequest getServletRequest() {
+ return parent;
+ }
+
+ public void setUri(URI uri) {
+ parent.setUri(uri);
+ }
+
+ @Override
+ public String getMethod() {
+ return parent.getRequest().getMethod();
+ }
+
+ @Override
+ public void setRemoteAddr(String remoteIpAddress) {
+ throw new UnsupportedOperationException(
+ "Setting remote address is not supported for " + this.getClass().getName());
+ }
+
+ @Override
+ public Enumeration<String> getAttributeNames() {
+ Set<String> names = new HashSet<>(Collections.list(super.getAttributeNames()));
+ names.addAll(Collections.list(parent.getRequest().getAttributeNames()));
+ return Collections.enumeration(names);
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ Object jdiscAttribute = super.getAttribute(name);
+ return jdiscAttribute != null ?
+ jdiscAttribute :
+ parent.getRequest().getAttribute(name);
+ }
+
+ @Override
+ public void setAttribute(String name, Object value) {
+ super.setAttribute(name, value);
+ parent.getRequest().setAttribute(name, value);
+ }
+
+ @Override
+ public boolean containsAttribute(String name) {
+ return super.containsAttribute(name)
+ || parent.getRequest().getAttribute(name) != null;
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ super.removeAttribute(name);
+ parent.getRequest().removeAttribute(name);
+ }
+
+ @Override
+ public String getParameter(String name) {
+ return parent.getParameter(name);
+ }
+
+ @Override
+ public Enumeration<String> getParameterNames() {
+ return parent.getParameterNames();
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ parent.addHeader(name, value);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return parent.getHeader(name);
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ return parent.getHeaderNames();
+ }
+
+ public List<String> getHeaderNamesAsList() {
+ return Collections.list(getHeaderNames());
+ }
+
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ return parent.getHeaders(name);
+ }
+
+ @Override
+ public List<String> getHeadersAsList(String name) {
+ return Collections.list(getHeaders(name));
+ }
+
+ @Override
+ public void setHeaders(String name, String value) {
+ parent.setHeaders(name, value);
+ }
+
+ @Override
+ public void setHeaders(String name, List<String> values) {
+ parent.setHeaders(name, values);
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ return parent.getUserPrincipal();
+ }
+
+ @Override
+ public void setUserPrincipal(Principal principal) {
+ parent.setUserPrincipal(principal);
+ }
+
+ @Override
+ public List<X509Certificate> getClientCertificateChain() {
+ return Optional.ofNullable(parent.getRequest().getAttribute(ServletRequest.SERVLET_REQUEST_X509CERT))
+ .map(X509Certificate[].class::cast)
+ .map(Arrays::asList)
+ .orElse(Collections.emptyList());
+ }
+
+ @Override
+ public void removeHeaders(String name) {
+ parent.removeHeaders(name);
+ }
+
+ @Override
+ public void clearCookies() {
+ parent.removeHeaders(HttpHeaders.Names.COOKIE);
+ }
+
+ @Override
+ public void setCharacterEncoding(String encoding) {
+ super.setCharacterEncoding(encoding);
+ try {
+ parent.setCharacterEncoding(encoding);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Encoding not supported: " + encoding, e);
+ }
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java
new file mode 100644
index 00000000000..b603e7776f1
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/ServletFilterResponse.java
@@ -0,0 +1,81 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter;
+
+import com.google.common.collect.Iterables;
+import com.yahoo.jdisc.http.Cookie;
+import com.yahoo.jdisc.http.HttpHeaders;
+import com.yahoo.jdisc.http.servlet.ServletResponse;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Servlet implementation for JDisc filter responses.
+ */
+class ServletFilterResponse extends DiscFilterResponse {
+
+ private final ServletResponse parent;
+
+ public ServletFilterResponse(ServletResponse parent) {
+ super(parent);
+ this.parent = parent;
+ }
+
+ ServletResponse getServletResponse() {
+ return parent;
+ }
+
+ public void setStatus(int status) {
+ parent.setStatus(status);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ parent.setHeader(name, value);
+ }
+
+ @Override
+ public void removeHeaders(String name) {
+ HttpServletResponse parentResponse = parent.getResponse();
+ if (parentResponse instanceof org.eclipse.jetty.server.Response) {
+ org.eclipse.jetty.server.Response jettyResponse = (org.eclipse.jetty.server.Response)parentResponse;
+ jettyResponse.getHttpFields().remove(name);
+ } else {
+ throw new UnsupportedOperationException(
+ "Cannot remove headers for response of type " + parentResponse.getClass().getName());
+ }
+ }
+
+ // Why have a setHeaders that takes a single string?
+ @Override
+ public void setHeaders(String name, String value) {
+ parent.setHeader(name, value);
+ }
+
+ @Override
+ public void setHeaders(String name, List<String> values) {
+ for (String value : values)
+ parent.addHeader(name, value);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ parent.addHeader(name, value);
+ }
+
+ @Override
+ public String getHeader(String name) {
+ Collection<String> headers = parent.getHeaders(name);
+ return headers.isEmpty()
+ ? null
+ : Iterables.getLast(headers);
+ }
+
+ @Override
+ public void setCookies(List<Cookie> cookies) {
+ removeHeaders(HttpHeaders.Names.SET_COOKIE);
+ List<String> setCookieHeaders = Cookie.toSetCookieHeaders(cookies);
+ setCookieHeaders.forEach(cookie -> addHeader(HttpHeaders.Names.SET_COOKIE, cookie));
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java
new file mode 100644
index 00000000000..e1834fd8b7d
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyRequestFilter.java
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.chain;
+
+import com.yahoo.jdisc.NoopSharedResource;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public final class EmptyRequestFilter extends NoopSharedResource implements RequestFilter {
+
+ public static final RequestFilter INSTANCE = new EmptyRequestFilter();
+
+ private EmptyRequestFilter() {
+ // hide
+ }
+
+ @Override
+ public void filter(HttpRequest request, ResponseHandler handler) {
+
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java
new file mode 100644
index 00000000000..5ce3f6a496f
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/EmptyResponseFilter.java
@@ -0,0 +1,24 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.chain;
+
+import com.yahoo.jdisc.NoopSharedResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public final class EmptyResponseFilter extends NoopSharedResource implements ResponseFilter {
+
+ public static final ResponseFilter INSTANCE = new EmptyResponseFilter();
+
+ private EmptyResponseFilter() {
+ // hide
+ }
+
+ @Override
+ public void filter(Response response, Request request) {
+
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java
new file mode 100644
index 00000000000..85f71777cf3
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/RequestFilterChain.java
@@ -0,0 +1,55 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.chain;
+
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.application.ResourcePool;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.http.HttpRequest;
+import com.yahoo.jdisc.http.filter.RequestFilter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ */
+public final class RequestFilterChain extends AbstractResource implements RequestFilter {
+
+ private final List<RequestFilter> filters = new ArrayList<>();
+ private final ResourcePool filterReferences = new ResourcePool();
+
+ private RequestFilterChain(Iterable<? extends RequestFilter> filters) {
+ for (RequestFilter filter : filters) {
+ this.filters.add(filter);
+ filterReferences.retain(filter);
+ }
+ }
+
+ @Override
+ public void filter(HttpRequest request, ResponseHandler responseHandler) {
+ ResponseHandlerGuard guard = new ResponseHandlerGuard(responseHandler);
+ for (int i = 0, len = filters.size(); i < len && !guard.isDone(); ++i) {
+ filters.get(i).filter(request, guard);
+ }
+ }
+
+ @Override
+ protected void destroy() {
+ filterReferences.release();
+ }
+
+ public static RequestFilter newInstance(RequestFilter... filters) {
+ return newInstance(Arrays.asList(filters));
+ }
+
+ public static RequestFilter newInstance(List<? extends RequestFilter> filters) {
+ if (filters.size() == 0) {
+ return EmptyRequestFilter.INSTANCE;
+ }
+ if (filters.size() == 1) {
+ return filters.get(0);
+ }
+ return new RequestFilterChain(filters);
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java
new file mode 100644
index 00000000000..5c5eda1f139
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseFilterChain.java
@@ -0,0 +1,54 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.chain;
+
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ResourcePool;
+import com.yahoo.jdisc.http.filter.ResponseFilter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Simon Thoresen Hult
+ */
+public final class ResponseFilterChain extends AbstractResource implements ResponseFilter {
+
+ private final List<ResponseFilter> filters = new ArrayList<>();
+ private final ResourcePool filterReferences = new ResourcePool();
+
+ private ResponseFilterChain(Iterable<? extends ResponseFilter> filters) {
+ for (ResponseFilter filter : filters) {
+ this.filters.add(filter);
+ filterReferences.retain(filter);
+ }
+ }
+
+ @Override
+ public void filter(Response response, Request request) {
+ for (ResponseFilter filter : filters) {
+ filter.filter(response, request);
+ }
+ }
+
+ @Override
+ protected void destroy() {
+ filterReferences.release();
+ }
+
+ public static ResponseFilter newInstance(ResponseFilter... filters) {
+ return newInstance(Arrays.asList(filters));
+ }
+
+ public static ResponseFilter newInstance(List<? extends ResponseFilter> filters) {
+ if (filters.size() == 0) {
+ return EmptyResponseFilter.INSTANCE;
+ }
+ if (filters.size() == 1) {
+ return filters.get(0);
+ }
+ return new ResponseFilterChain(filters);
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java
new file mode 100644
index 00000000000..02600683e27
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/ResponseHandlerGuard.java
@@ -0,0 +1,29 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.filter.chain;
+
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+
+/**
+ * @author Simon Thoresen Hult
+ */
+final class ResponseHandlerGuard implements ResponseHandler {
+
+ private final ResponseHandler responseHandler;
+ private boolean done = false;
+
+ public ResponseHandlerGuard(ResponseHandler handler) {
+ this.responseHandler = handler;
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ done = true;
+ return responseHandler.handleResponse(response);
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+}
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java
new file mode 100644
index 00000000000..540a1be7b73
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/chain/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.jdisc.http.filter.chain;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/container-core/src/main/java/com/yahoo/jdisc/http/filter/package-info.java b/container-core/src/main/java/com/yahoo/jdisc/http/filter/package-info.java
new file mode 100644
index 00000000000..e97d447adbb
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/jdisc/http/filter/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@PublicApi
+@ExportPackage
+package com.yahoo.jdisc.http.filter;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;