From 4af81674d2d680767553d66fe96571b33ac8e202 Mon Sep 17 00:00:00 2001 From: "S K (xz64)" Date: Tue, 13 Jun 2017 20:58:22 -0700 Subject: [PATCH] add general purpose header filters Signed-off-by: S K (xz64) --- .../administration/extras/chapter.adoc | 1 + .../administration/extras/header-filter.adoc | 115 ++++++ .../eclipse/jetty/servlets/HeaderFilter.java | 197 ++++++++++ .../servlets/IncludeExcludeBasedFilter.java | 162 ++++++++ .../jetty/servlets/HeaderFilterTest.java | 137 +++++++ .../IncludeExcludeBasedFilterTest.java | 353 ++++++++++++++++++ 6 files changed, 965 insertions(+) create mode 100644 jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java create mode 100644 jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java diff --git a/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc b/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc index 9e3a200c29c..b44a7588c64 100644 --- a/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc +++ b/jetty-documentation/src/main/asciidoc/administration/extras/chapter.adoc @@ -30,6 +30,7 @@ include::balancer-servlet.adoc[] include::cgi-servlet.adoc[] include::qos-filter.adoc[] include::dos-filter.adoc[] +include::header-filter.adoc[] include::gzip-filter.adoc[] include::cross-origin-filter.adoc[] include::resource-handler.adoc[] diff --git a/jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc b/jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc new file mode 100644 index 00000000000..e7e97d9079e --- /dev/null +++ b/jetty-documentation/src/main/asciidoc/administration/extras/header-filter.adoc @@ -0,0 +1,115 @@ +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ======================================================================== +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== + +[[header-filter]] +=== Header Filter + +[[header-filter-metadata]] +==== Info + +* Classname: `org.eclipse.jetty.servlets.HeaderFilter` +* Maven Artifact: org.eclipse.jetty:jetty-servlets +* Javadoc: {JDURL}/org/eclipse/jetty/servlets/HeaderFilter.html +* Xref: {JXURL}/org/eclipse/jetty/servlets/HeaderFilter.html + +[[header-filter-usage]] +==== Usage + +The header filter sets or adds headers to each response based on an optionally included/excluded list of path specs, mime types, and/or HTTP methods. + +===== Required JARs + +To use the Header Filter, these JAR files must be available in WEB-INF/lib: + +* $JETTY_HOME/lib/jetty-http.jar +* $JETTY_HOME/lib/jetty-servlets.jar +* $JETTY_HOME/lib/jetty-util.jar + +===== Sample Configuration + +Place the configuration in a webapp's `web.xml` or `jetty-web.xml`. +This filter will perform the following actions on each response: + +* Set the X-Frame-Options header to DENY. +* Add a Cache-Control header containing no-cache, no-store, must-revalidate +* Set the Expires header to approximately one year in the future. +* Add a Date header with the current system time. + +____ +[NOTE] +Each action must be separated by a comma. +____ + +[source, xml, subs="{sub-order}"] +---- + + HeaderFilter + org.eclipse.jetty.servlets.HeaderFilter + + headerConfig + + set X-Frame-Options: DENY, + "add Cache-Control: no-cache, no-store, must-revalidate", + setDate Expires: 31540000000, + addDate Date: 0 + + + +---- + +[[header-filter-init]] +===== Configuring Header Filter Parameters + +The following `init` parameters control the behavior of the filter: + +includedPaths:: +Optional. CSV of included path specs. + +excludedPaths:: +Optional. CSV of excluded path specs. + +includedMimeTypes:: +Optional. CSV of included mime types. + +excludedMimeTypes:: +Optional. CSV of excluded mime types. + +includedHttpMethods:: +Optional. CSV of included http methods. + +excludedHttpMethods:: +Optional. CSV of excluded http methods. + +headerConfig:: +CSV of actions to perform on headers. The syntax for each action is `action headerName: headerValue`. + +Supported header actions: + +* `set` - causes set `setHeader` to be called on the response +* `add` - causes set `addHeader` to be called on the response +* `setDate` - causes `setDateHeader` to be called on the response. +* `addDate` - causes `addDateHeader` to be called on the response. + +If `setDate` or `addDate` is used, `headerValue` should be the number of milliseconds to add to the current system time before writing the header value. + +If a property is both included and excluded by the filter configuration, then it will be considered excluded. + +Path spec rules: + +* If the spec starts with `^`, the spec is assumed to be a regex based path spec and will match with normal Java regex rules. +* If the spec starts with `/`, the spec is assumed to be a Servlet url-pattern rules path spec for either an exact match or prefix based match. +* If the spec starts with `*.`, the spec is assumed to be a Servlet url-pattern rules path spec for a suffix based match. +* All other syntaxes are unsupported. diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java new file mode 100644 index 00000000000..c23b6b5b88c --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/HeaderFilter.java @@ -0,0 +1,197 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Header Filter + *

+ * This filter sets or adds a header to the response. + *

+ * The {@code headerConfig} init param is a CSV of actions to perform on headers, with the following syntax:
+ * [action] [header name]: [header value]
+ * [action] can be one of set, add, setDate, or addDate
+ * The date actions will add the header value in milliseconds to the current system time before setting a date header. + *

+ * Below is an example value for headerConfig:
+ * + *

+ * set X-Frame-Options: DENY,
+ * "add Cache-Control: no-cache, no-store, must-revalidate",
+ * setDate Expires: 31540000000,
+ * addDate Date: 0
+ * 
+ * + * @see IncludeExcludeBasedFilter + */ +public class HeaderFilter extends IncludeExcludeBasedFilter +{ + private List _configuredHeaders = new ArrayList<>(); + private static final Logger LOG = Log.getLogger(HeaderFilter.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + super.init(filterConfig); + String header_config = filterConfig.getInitParameter("headerConfig"); + + if (header_config != null) + { + String[] configs = StringUtil.csvSplit(header_config); + for (String config : configs) + _configuredHeaders.add(parseHeaderConfiguration(config)); + } + + if (LOG.isDebugEnabled()) + LOG.debug(this.toString()); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + chain.doFilter(request,response); + + HttpServletRequest http_request = (HttpServletRequest)request; + HttpServletResponse http_response = (HttpServletResponse)response; + + if (!super.shouldFilter(http_request,http_response)) + { + return; + } + + for (ConfiguredHeader header : _configuredHeaders) + { + if (header.isDate()) + { + long header_value = System.currentTimeMillis() + header.getMsOffset(); + if (header.isAdd()) + { + http_response.addDateHeader(header.getName(),header_value); + } + else + { + http_response.setDateHeader(header.getName(),header_value); + } + } + else // constant header value + { + if (header.isAdd()) + { + http_response.addHeader(header.getName(),header.getValue()); + } + else + { + http_response.setHeader(header.getName(),header.getValue()); + } + } + } + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(super.toString()).append("\n"); + sb.append("configured headers:\n"); + for (ConfiguredHeader c : _configuredHeaders) + sb.append(c).append("\n"); + + return sb.toString(); + } + + private ConfiguredHeader parseHeaderConfiguration(String config) + { + String[] config_tokens = config.trim().split(" ",2); + String method = config_tokens[0].trim(); + String header = config_tokens[1]; + String[] header_tokens = header.trim().split(":",2); + String header_name = header_tokens[0].trim(); + String header_value = header_tokens[1].trim(); + ConfiguredHeader configured_header = new ConfiguredHeader(header_name,header_value,method.startsWith("add"),method.endsWith("Date")); + return configured_header; + } + + private static class ConfiguredHeader + { + private String _name; + private String _value; + private long _msOffset; + private boolean _add; + private boolean _date; + + public ConfiguredHeader(String name, String value, boolean add, boolean date) + { + _name = name; + _value = value; + _add = add; + _date = date; + + if (_date) + { + _msOffset = Long.parseLong(_value); + } + } + + public String getName() + { + return _name; + } + + public String getValue() + { + return _value; + } + + public boolean isAdd() + { + return _add; + } + + public boolean isDate() + { + return _date; + } + + public long getMsOffset() + { + return _msOffset; + } + + @Override + public String toString() + { + return (_add?"add":"set") + (_date?"Date":"") + " " + _name + ": " + _value; + } + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java new file mode 100644 index 00000000000..b1ba2cf4342 --- /dev/null +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilter.java @@ -0,0 +1,162 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.servlets; + +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.pathmap.PathSpecSet; +import org.eclipse.jetty.util.IncludeExclude; +import org.eclipse.jetty.util.IncludeExcludeSet; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/** + * Include Exclude Based Filter + *

+ * This is an abstract filter which helps with filtering based on include/exclude of paths, mime types, and/or http methods. + *

+ * Use the {@link #shouldFilter(HttpServletRequest, HttpServletResponse)} method to determine if a request/response should be filtered. If mime types are used, + * it should be called after {@link javax.servlet.FilterChain#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} since the mime type may not + * be written until then. + * + * Supported init params: + *

    + *
  • includedPaths - CSV of path specs to include
  • + *
  • excludedPaths - CSV of path specs to exclude
  • + *
  • includedMimeTypes - CSV of mime types to include
  • + *
  • excludedMimeTypes - CSV of mime types to exclude
  • + *
  • includedHttpMethods - CSV of http methods to include
  • + *
  • excludedHttpMethods - CSV of http methods to exclude
  • + *
+ *

+ * Path spec rules: + *

    + *
  • If the spec starts with '^' the spec is assumed to be a regex based path spec and will match with normal Java regex rules.
  • + *
  • If the spec starts with '/' the spec is assumed to be a Servlet url-pattern rules path spec for either an exact match or prefix based + * match.
  • + *
  • If the spec starts with '*.' the spec is assumed to be a Servlet url-pattern rules path spec for a suffix based match.
  • + *
  • All other syntaxes are unsupported.
  • + *
+ *

+ * CSVs are parsed with {@link StringUtil#csvSplit(String)} + * + * @see PathSpecSet + * @see IncludeExcludeSet + */ +public abstract class IncludeExcludeBasedFilter implements Filter +{ + private final IncludeExclude _mimeTypes = new IncludeExclude<>(); + private final IncludeExclude _httpMethods = new IncludeExclude<>(); + private final IncludeExclude _paths = new IncludeExclude<>(PathSpecSet.class); + private static final Logger LOG = Log.getLogger(IncludeExcludeBasedFilter.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String included_paths = filterConfig.getInitParameter("includedPaths"); + String excluded_paths = filterConfig.getInitParameter("excludedPaths"); + String included_mime_types = filterConfig.getInitParameter("includedMimeTypes"); + String excluded_mime_types = filterConfig.getInitParameter("excludedMimeTypes"); + String included_http_methods = filterConfig.getInitParameter("includedHttpMethods"); + String excluded_http_methods = filterConfig.getInitParameter("excludedHttpMethods"); + + if (included_paths != null) + { + _paths.include(StringUtil.csvSplit(included_paths)); + } + if (excluded_paths != null) + { + _paths.exclude(StringUtil.csvSplit(excluded_paths)); + } + if (included_mime_types != null) + { + _mimeTypes.include(StringUtil.csvSplit(included_mime_types)); + } + if (excluded_mime_types != null) + { + _mimeTypes.exclude(StringUtil.csvSplit(excluded_mime_types)); + } + if (included_http_methods != null) + { + _httpMethods.include(StringUtil.csvSplit(included_http_methods)); + } + if (excluded_http_methods != null) + { + _httpMethods.exclude(StringUtil.csvSplit(excluded_http_methods)); + } + } + + protected boolean shouldFilter(HttpServletRequest http_request, HttpServletResponse http_response) + { + String http_method = http_request.getMethod(); + LOG.debug("HTTP method is: {}",http_method); + if (!_httpMethods.test(http_method)) + { + LOG.debug("should not apply filter because HTTP method does not match"); + return false; + } + + String content_type = http_response.getContentType(); + LOG.debug("Content Type is: {}",content_type); + content_type = (content_type == null)?"":content_type; + String mime_type = MimeTypes.getContentTypeWithoutCharset(content_type); + + LOG.debug("Mime Type is: {}",content_type); + if (!_mimeTypes.test(mime_type)) + { + LOG.debug("should not apply filter because mime type does not match"); + return false; + } + + ServletContext context = http_request.getServletContext(); + String path = context == null?http_request.getRequestURI():URIUtil.addPaths(http_request.getServletPath(),http_request.getPathInfo()); + LOG.debug("Path is: {}",path); + if (!_paths.test(path)) + { + LOG.debug("should not apply filter because path does not match"); + return false; + } + + return true; + } + + @Override + public void destroy() + { + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append("filter configuration:\n"); + sb.append("paths:\n").append(_paths).append("\n"); + sb.append("mime types:\n").append(_mimeTypes).append("\n"); + sb.append("http methods:\n").append(_httpMethods); + return sb.toString(); + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java new file mode 100644 index 00000000000..5230a96fd24 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/HeaderFilterTest.java @@ -0,0 +1,137 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletTester; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class HeaderFilterTest +{ + private ServletTester _tester; + + @Before + public void setUp() throws Exception + { + _tester = new ServletTester(); + _tester.setContextPath("/context"); + _tester.addServlet(NullServlet.class,"/test/*"); + + _tester.start(); + } + + @After + public void tearDown() throws Exception + { + _tester.stop(); + } + + @Test + public void testHeaderFilterSet() throws Exception + { + FilterHolder holder = new FilterHolder(HeaderFilter.class); + holder.setInitParameter("headerConfig","set X-Frame-Options: DENY"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Frame-Options","DENY")); + } + + @Test + public void testHeaderFilterAdd() throws Exception + { + FilterHolder holder = new FilterHolder(HeaderFilter.class); + holder.setInitParameter("headerConfig","add X-Frame-Options: DENY"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Frame-Options","DENY")); + } + + @Test + public void testHeaderFilterSetDate() throws Exception + { + FilterHolder holder = new FilterHolder(HeaderFilter.class); + holder.setInitParameter("headerConfig","setDate Expires: 100"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains(HttpHeader.EXPIRES)); + } + + @Test + public void testHeaderFilterAddDate() throws Exception + { + FilterHolder holder = new FilterHolder(HeaderFilter.class); + holder.setInitParameter("headerConfig","addDate Expires: 100"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains(HttpHeader.EXPIRES)); + } + + public static class NullServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.setStatus(HttpStatus.NO_CONTENT_204); + } + + } +} diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java new file mode 100644 index 00000000000..f9971247ad2 --- /dev/null +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/IncludeExcludeBasedFilterTest.java @@ -0,0 +1,353 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.servlets; + +import java.io.IOException; +import java.util.EnumSet; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletTester; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class IncludeExcludeBasedFilterTest +{ + private ServletTester _tester; + + @Before + public void setUp() throws Exception + { + _tester = new ServletTester(); + _tester.setContextPath("/context"); + _tester.addServlet(NullServlet.class,"/test/*"); + + _tester.start(); + } + + @After + public void tearDown() throws Exception + { + _tester.stop(); + } + + @Test + public void testIncludeExcludeFilterIncludedPathMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedPaths","^/test/0$"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludedPathNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedPaths","^/nomatchtest$"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludedPathMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("excludedPaths","^/test/0$"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludedPathNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("excludedPaths","^/nomatchtest$"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludeOverridesInclude() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedPaths","^/test/0$"); + holder.setInitParameter("excludedPaths","^/test/0$"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludeMethodMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedHttpMethods","GET"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludeMethodNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedHttpMethods","POST,PUT"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludeMethodMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("excludedHttpMethods","GET"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludeMethodNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("excludedHttpMethods","POST,PUT"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/0"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludeMimeTypeMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedMimeTypes","application/json"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/json"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludeMimeTypeNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedMimeTypes","application/xml"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/json"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludeMimeTypeMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("excludedMimeTypes","application/json"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/json"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterExcludeMimeTypeNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("excludedMimeTypes","application/xml"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/json"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludeMimeTypeSemicolonMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedMimeTypes","application/json"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/json-utf8"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertTrue(response.contains("X-Custom-Value","1")); + } + + @Test + public void testIncludeExcludeFilterIncludeMimeTypeSemicolonNoMatch() throws Exception + { + FilterHolder holder = new FilterHolder(MockIncludeExcludeFilter.class); + holder.setInitParameter("includedMimeTypes","application/xml"); + _tester.getContext().getServletHandler().addFilterWithMapping(holder,"/*",EnumSet.of(DispatcherType.REQUEST)); + + HttpTester.Request request = HttpTester.newRequest(); + request.setMethod("GET"); + request.setVersion("HTTP/1.1"); + request.setHeader("Host","localhost"); + request.setURI("/context/test/json-utf8"); + + HttpTester.Response response = HttpTester.parseResponse(_tester.getResponses(request.generate())); + Assert.assertFalse(response.contains("X-Custom-Value","1")); + } + + public static class MockIncludeExcludeFilter extends IncludeExcludeBasedFilter + { + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + chain.doFilter(request,response); + HttpServletRequest http_request = (HttpServletRequest)request; + HttpServletResponse http_response = (HttpServletResponse)response; + + if (!super.shouldFilter(http_request,http_response)) + { + return; + } + + http_response.setHeader("X-Custom-Value","1"); + } + } + + public static class NullServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + if (req.getPathInfo().equals("/json")) + { + resp.setContentType("application/json"); + } + else if (req.getPathInfo().equals("/json-utf8")) + { + resp.setContentType("application/json; charset=utf-8"); + } + resp.setStatus(HttpStatus.NO_CONTENT_204); + } + + } +}