diff options
Diffstat (limited to 'container-core/src/main/java/com/yahoo/jdisc/http/filter')
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; |