Merge remote-tracking branch 'origin/jetty-9.4.x'

This commit is contained in:
Joakim Erdfelt 2017-06-16 14:33:01 -07:00
commit 5638f7b25b
16 changed files with 1188 additions and 114 deletions

View File

@ -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[]

View File

@ -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}"]
----
<filter>
<filter-name>HeaderFilter</filter-name>
<filter-class>org.eclipse.jetty.servlets.HeaderFilter</filter-class>
<init-param>
<param-name>headerConfig</param-name>
<param-value>
set X-Frame-Options: DENY,
"add Cache-Control: no-cache, no-store, must-revalidate",
setDate Expires: 31540000000,
addDate Date: 0
</param-value>
</init-param>
</filter>
----
[[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.

View File

@ -71,6 +71,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>${pluginToolsVersion}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>

View File

@ -341,7 +341,7 @@ public abstract class AbstractJettyMojo extends AbstractMojo
{
try
{
List<URL> provided = new ArrayList<URL>();
List<URL> provided = new ArrayList<>();
URL[] urls = null;
for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )

View File

@ -18,24 +18,29 @@
package org.eclipse.jetty.maven.plugin;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.jetty.util.PathWatcher;
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.eclipse.jetty.util.PathWatcher;
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
/**
* This goal is used in-situ on a Maven project without first requiring that the project
* is assembled into a war, saving time during the development cycle.
@ -154,18 +159,20 @@ public class JettyRunMojo extends AbstractJettyMojo
* List of deps that are wars
*/
protected List<Artifact> warArtifacts;
@Parameter(defaultValue = "${reactorProjects}", readonly = true, required = true)
private List<MavenProject> reactorProjects;
/**
* @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#execute()
*/
@Override
public void execute() throws MojoExecutionException, MojoFailureException
{
if ( !"war".equals( project.getPackaging() ) || skip )
{
return;
}
warPluginInfo = new WarPluginInfo(project);
super.execute();
}
@ -273,7 +280,8 @@ public class JettyRunMojo extends AbstractJettyMojo
webApp.setClasses (classesDirectory);
if (useTestScope && (testClassesDirectory != null))
webApp.setTestClasses (testClassesDirectory);
webApp.getClassPathFiles().addAll( getDependencyProjects() );
webApp.setWebInfLib (getDependencyFiles());
//get copy of a list of war artifacts
@ -556,18 +564,22 @@ public class JettyRunMojo extends AbstractJettyMojo
/**
* @return
*/
private List<File> getDependencyFiles ()
private List<File> getDependencyFiles()
{
List<File> dependencyFiles = new ArrayList<File>();
for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
{
Artifact artifact = (Artifact) iter.next();
Artifact artifact = iter.next();
// Include runtime and compile time libraries, and possibly test libs too
if(artifact.getType().equals("war"))
{
continue;
}
if (getProjectReferences( artifact, project )!=null)
{
continue;
}
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
continue; //never add dependencies of scope=provided to the webapp's classpath (see also <useProvidedScope> param)
@ -581,6 +593,57 @@ public class JettyRunMojo extends AbstractJettyMojo
return dependencyFiles;
}
private List<File> getDependencyProjects()
{
List<File> dependencyFiles = new ArrayList<File>();
for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
{
Artifact artifact = iter.next();
// Include runtime and compile time libraries, and possibly test libs too
if(artifact.getType().equals("war"))
{
continue;
}
if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
continue; //never add dependencies of scope=provided to the webapp's classpath (see also <useProvidedScope> param)
if (Artifact.SCOPE_TEST.equals(artifact.getScope()) && !useTestScope)
continue; //only add dependencies of scope=test if explicitly required
MavenProject mavenProject = getProjectReferences( artifact, project );
if (mavenProject != null)
{
dependencyFiles.add( Paths.get(mavenProject.getBuild().getOutputDirectory()).toFile() );
getLog().debug( "Adding project reference " + mavenProject.getBuild().getOutputDirectory()
+ " for WEB-INF/classes " );
}
}
return dependencyFiles;
}
private MavenProject getProjectReferences( Artifact artifact, MavenProject project )
{
if ( project.getProjectReferences() == null || project.getProjectReferences().isEmpty() )
{
return null;
}
Collection<MavenProject> mavenProjects = project.getProjectReferences().values();
for ( MavenProject mavenProject : mavenProjects )
{
if ( StringUtils.equals( mavenProject.getId(), artifact.getId() ) )
{
return mavenProject;
}
}
return null;
}

View File

@ -69,10 +69,10 @@ public class JettyWebAppContext extends WebAppContext
private File _classes = null;
private File _testClasses = null;
private final List<File> _webInfClasses = new ArrayList<File>();
private final List<File> _webInfJars = new ArrayList<File>();
private final List<File> _webInfClasses = new ArrayList<>();
private final List<File> _webInfJars = new ArrayList<>();
private final Map<String, File> _webInfJarMap = new HashMap<String, File>();
private List<File> _classpathFiles; //webInfClasses+testClasses+webInfJars
private List<File> _classpathFiles = new ArrayList<>(); //webInfClasses+testClasses+webInfJars
private String _jettyEnvXml;
private List<Overlay> _overlays;
@ -352,7 +352,6 @@ public class JettyWebAppContext extends WebAppContext
_webInfClasses.add(_classes);
// Set up the classpath
_classpathFiles = new ArrayList<File>();
_classpathFiles.addAll(_webInfClasses);
_classpathFiles.addAll(_webInfJars);

View File

@ -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
* <p>
* This filter sets or adds a header to the response.
* <p>
* The {@code headerConfig} init param is a CSV of actions to perform on headers, with the following syntax: <br>
* [action] [header name]: [header value] <br>
* [action] can be one of <code>set</code>, <code>add</code>, <code>setDate</code>, or <code>addDate</code> <br>
* The date actions will add the header value in milliseconds to the current system time before setting a date header.
* <p>
* Below is an example value for <code>headerConfig</code>:<br>
*
* <pre>
* set X-Frame-Options: DENY,
* "add Cache-Control: no-cache, no-store, must-revalidate",
* setDate Expires: 31540000000,
* addDate Date: 0
* </pre>
*
* @see IncludeExcludeBasedFilter
*/
public class HeaderFilter extends IncludeExcludeBasedFilter
{
private List<ConfiguredHeader> _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;
}
}
}

View File

@ -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
* <p>
* This is an abstract filter which helps with filtering based on include/exclude of paths, mime types, and/or http methods.
* <p>
* 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:
* <ul>
* <li><code>includedPaths</code> - CSV of path specs to include</li>
* <li><code>excludedPaths</code> - CSV of path specs to exclude</li>
* <li><code>includedMimeTypes</code> - CSV of mime types to include</li>
* <li><code>excludedMimeTypes</code> - CSV of mime types to exclude</li>
* <li><code>includedHttpMethods</code> - CSV of http methods to include</li>
* <li><code>excludedHttpMethods</code> - CSV of http methods to exclude</li>
* </ul>
* <p>
* Path spec rules:
* <ul>
* <li>If the spec starts with <code>'^'</code> the spec is assumed to be a regex based path spec and will match with normal Java regex rules.</li>
* <li>If the spec starts with <code>'/'</code> the spec is assumed to be a Servlet url-pattern rules path spec for either an exact match or prefix based
* match.</li>
* <li>If the spec starts with <code>'*.'</code> the spec is assumed to be a Servlet url-pattern rules path spec for a suffix based match.</li>
* <li>All other syntaxes are unsupported.</li>
* </ul>
* <p>
* CSVs are parsed with {@link StringUtil#csvSplit(String)}
*
* @see PathSpecSet
* @see IncludeExcludeSet
*/
public abstract class IncludeExcludeBasedFilter implements Filter
{
private final IncludeExclude<String> _mimeTypes = new IncludeExclude<>();
private final IncludeExclude<String> _httpMethods = new IncludeExclude<>();
private final IncludeExclude<String> _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();
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -22,7 +22,7 @@ package org.eclipse.jetty.websocket.api;
* The <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455 specified status codes</a> and <a
* href="https://www.iana.org/assignments/websocket/websocket.xml#close-code-number-rules">IANA: WebSocket Close Code Number Registry</a>
*/
public class StatusCode
public final class StatusCode
{
/**
* 1000 indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.
@ -137,6 +137,13 @@ public class StatusCode
*/
public final static int TRY_AGAIN_LATER = 1013;
/**
* 1014 indicates that a gateway or proxy received and invalid upstream response.
* <p>
* See <a href="https://www.ietf.org/mail-archive/web/hybi/current/msg10748.html">[hybi] WebSocket Subprotocol Close Code: Bad Gateway</a>
*/
public final static int INVALID_UPSTREAM_RESPONSE = 1014;
/**
* 1015 is a reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting
* a status code to indicate that the connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).
@ -144,4 +151,28 @@ public class StatusCode
* See <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1">RFC 6455, Section 7.4.1 Defined Status Codes</a>.
*/
public final static int FAILED_TLS_HANDSHAKE = 1015;
/**
* Test if provided status code can be sent/received on a WebSocket close.
* <p>
* This honors the RFC6455 rules and IANA rules.
* </p>
* @param statusCode the statusCode to test
* @return true if transmittable
*/
public static boolean isTransmittable(int statusCode)
{
return (statusCode == NORMAL) ||
(statusCode == SHUTDOWN) ||
(statusCode == PROTOCOL) ||
(statusCode == BAD_DATA) ||
(statusCode == BAD_PAYLOAD) ||
(statusCode == POLICY_VIOLATION) ||
(statusCode == MESSAGE_TOO_LARGE) ||
(statusCode == REQUIRED_EXTENSION) ||
(statusCode == SERVER_ERROR) ||
(statusCode == SERVICE_RESTART) ||
(statusCode == TRY_AGAIN_LATER) ||
(statusCode == INVALID_UPSTREAM_RESPONSE);
}
}

View File

@ -33,7 +33,7 @@ import org.eclipse.jetty.websocket.common.frames.CloseFrame;
public class CloseInfo
{
private int statusCode;
private int statusCode = 0;
private String reason;
public CloseInfo()
@ -71,11 +71,7 @@ public class CloseInfo
if (validate)
{
if ((statusCode < StatusCode.NORMAL) || (statusCode == StatusCode.UNDEFINED) || (statusCode == StatusCode.NO_CLOSE)
|| (statusCode == StatusCode.NO_CODE) || ((statusCode > 1011) && (statusCode <= 2999)) || (statusCode >= 5000))
{
throw new ProtocolException("Invalid close code: " + statusCode);
}
assertValidStatusCode(statusCode);
}
if (data.remaining() > 0)
@ -128,7 +124,28 @@ public class CloseInfo
this.statusCode = statusCode;
this.reason = reason;
}
private void assertValidStatusCode(int statusCode)
{
// Status Codes outside of RFC6455 defined scope
if ((statusCode <= 999) || (statusCode >= 5000))
{
throw new ProtocolException("Out of range close status code: " + statusCode);
}
// Status Codes not allowed to exist in a Close frame (per RFC6455)
if ((statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || (statusCode == StatusCode.FAILED_TLS_HANDSHAKE))
{
throw new ProtocolException("Frame forbidden close status code: " + statusCode);
}
// Status Code is in defined "reserved space" and is declared (all others are invalid)
if ((statusCode >= 1000) && (statusCode <= 2999) && !StatusCode.isTransmittable(statusCode))
{
throw new ProtocolException("RFC6455 and IANA Undefined close status code: " + statusCode);
}
}
/**
* Convert a raw status code and reason into a WebSocket Close frame payload buffer.
*
@ -143,11 +160,11 @@ public class CloseInfo
// codes that are not allowed to be used in endpoint.
return null;
}
int len = 2; // status code
byte reasonBytes[] = null;
if (reason != null)
{
byte[] utf8Bytes = reason.getBytes(StandardCharsets.UTF_8);
@ -160,24 +177,24 @@ public class CloseInfo
{
reasonBytes = utf8Bytes;
}
if ((reasonBytes != null) && (reasonBytes.length > 0))
{
len += reasonBytes.length;
}
}
ByteBuffer buf = BufferUtil.allocate(len);
BufferUtil.flipToFill(buf);
buf.put((byte) ((statusCode >>> 8) & 0xFF));
buf.put((byte) ((statusCode >>> 0) & 0xFF));
if ((reasonBytes != null) && (reasonBytes.length > 0))
{
buf.put(reasonBytes, 0, reasonBytes.length);
}
BufferUtil.flipToFlush(buf, 0);
return buf;
}
@ -190,17 +207,15 @@ public class CloseInfo
{
CloseFrame frame = new CloseFrame();
frame.setFin(true);
if ((statusCode >= 1000) && (statusCode != StatusCode.NO_CLOSE) && (statusCode != StatusCode.NO_CODE))
// Frame forbidden codes result in no status code (and no reason string)
if ((statusCode != StatusCode.NO_CLOSE) && (statusCode != StatusCode.NO_CODE) && (statusCode != StatusCode.FAILED_TLS_HANDSHAKE))
{
if (statusCode == StatusCode.FAILED_TLS_HANDSHAKE)
{
throw new ProtocolException("Close Frame with status code " + statusCode + " not allowed (per RFC6455)");
}
assertValidStatusCode(statusCode);
frame.setPayload(asByteBuffer());
}
return frame;
}
public String getReason()
{
return this.reason;

View File

@ -22,11 +22,9 @@ import static org.eclipse.jetty.websocket.api.StatusCode.FAILED_TLS_HANDSHAKE;
import static org.eclipse.jetty.websocket.api.StatusCode.NORMAL;
import static org.eclipse.jetty.websocket.api.StatusCode.NO_CLOSE;
import static org.eclipse.jetty.websocket.api.StatusCode.NO_CODE;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
@ -102,17 +100,11 @@ public class CloseInfoTest
assertThat("close.code",close.getStatusCode(),is(FAILED_TLS_HANDSHAKE));
assertThat("close.reason",close.getReason(),nullValue());
try
{
@SuppressWarnings("unused")
CloseFrame frame = close.asFrame();
fail("Expected " + ProtocolException.class.getName());
}
catch (ProtocolException e)
{
// expected path
assertThat("ProtocolException message",e.getMessage(),containsString("not allowed (per RFC6455)"));
}
CloseFrame frame = close.asFrame();
assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE));
// should result in no payload
assertThat("close frame has payload",frame.hasPayload(),is(false));
assertThat("close frame payload length",frame.getPayloadLength(),is(0));
}
/**

View File

@ -46,37 +46,37 @@ public class CloseHandling_BadStatusCodesTest extends AbstractLocalServerCase
{
// The various Good UTF8 sequences as a String (hex form)
List<Object[]> data = new ArrayList<>();
// @formatter:off
data.add(new Object[]{"7.9.1", 0});
data.add(new Object[]{"7.9.2", 999});
data.add(new Object[]{"7.9.3", 1004});
data.add(new Object[]{"7.9.4", 1005});
data.add(new Object[]{"7.9.5", 1006});
data.add(new Object[]{"7.9.6", 1012});
data.add(new Object[]{"7.9.7", 1013});
data.add(new Object[]{"7.9.8", 1014});
data.add(new Object[]{"7.9.9", 1015});
data.add(new Object[]{"7.9.10", 1016});
data.add(new Object[]{"7.9.11", 1100});
data.add(new Object[]{"7.9.12", 2000});
data.add(new Object[]{"7.9.13", 2999});
data.add(new Object[] { "7.9.1", 0 });
data.add(new Object[] { "7.9.2", 999 });
data.add(new Object[] { "7.9.3", 1004 }); // RFC6455/UNDEFINED
data.add(new Object[] { "7.9.4", 1005 }); // RFC6455/Cannot Be Transmitted
data.add(new Object[] { "7.9.5", 1006 }); // RFC6455/Cannot Be Transmitted
// data.add(new Object[] { "7.9.6", 1012 }); - IANA Defined
// data.add(new Object[] { "7.9.7", 1013 }); - IANA Defined
// data.add(new Object[] { "7.9.8", 1014 }); - IANA Defined
data.add(new Object[] { "7.9.9", 1015 }); // RFC6455/Cannot Be Transmitted
data.add(new Object[] { "7.9.10", 1016 });
data.add(new Object[] { "7.9.11", 1100 });
data.add(new Object[] { "7.9.12", 2000 });
data.add(new Object[] { "7.9.13", 2999 });
// -- close status codes, with undefined events in spec
data.add(new Object[]{"7.13.1", 5000});
data.add(new Object[]{"7.13.2", 65536});
// @formatter:on
return data;
}
private final int statusCode;
public CloseHandling_BadStatusCodesTest(String testId, int statusCode)
{
LOG.debug("Test ID: {}", testId);
this.statusCode = statusCode;
}
/**
* just the close code, no reason
*
@ -89,20 +89,20 @@ public class CloseHandling_BadStatusCodesTest extends AbstractLocalServerCase
BufferUtil.clearToFill(payload);
payload.putChar((char) statusCode);
BufferUtil.flipToFlush(payload, 0);
List<WebSocketFrame> send = new ArrayList<>();
send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (LocalFuzzer session = server.newLocalFuzzer())
{
session.sendBulk(send);
session.expect(expect);
}
}
/**
* the bad close code, with reason
*
@ -116,13 +116,13 @@ public class CloseHandling_BadStatusCodesTest extends AbstractLocalServerCase
payload.putChar((char) statusCode);
payload.put(StringUtil.getBytes("Reason"));
BufferUtil.flipToFlush(payload, 0);
List<WebSocketFrame> send = new ArrayList<>();
send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (LocalFuzzer session = server.newLocalFuzzer())
{
session.sendBulk(send);

View File

@ -43,40 +43,43 @@ import org.junit.runners.Parameterized.Parameters;
public class CloseHandling_GoodStatusCodesTest extends AbstractLocalServerCase
{
private static final Logger LOG = Log.getLogger(CloseHandling_GoodStatusCodesTest.class);
@Parameters(name = "{0} {1}")
public static Collection<Object[]> data()
{
// The various Good UTF8 sequences as a String (hex form)
List<Object[]> data = new ArrayList<>();
// @formatter:off
data.add(new Object[]{"7.7.1", 1000});
data.add(new Object[]{"7.7.2", 1001});
data.add(new Object[]{"7.7.3", 1002});
data.add(new Object[]{"7.7.4", 1003});
data.add(new Object[]{"7.7.5", 1007});
data.add(new Object[]{"7.7.6", 1008});
data.add(new Object[]{"7.7.7", 1009});
data.add(new Object[]{"7.7.8", 1010});
data.add(new Object[]{"7.7.9", 1011});
data.add(new Object[]{"7.7.10", 3000});
data.add(new Object[]{"7.7.11", 3999});
data.add(new Object[]{"7.7.12", 4000});
data.add(new Object[]{"7.7.13", 4999});
data.add(new Object[] { "7.7.1", 1000 });
data.add(new Object[] { "7.7.2", 1001 });
data.add(new Object[] { "7.7.3", 1002 });
data.add(new Object[] { "7.7.4", 1003 });
data.add(new Object[] { "7.7.5", 1007 });
data.add(new Object[] { "7.7.6", 1008 });
data.add(new Object[] { "7.7.7", 1009 });
data.add(new Object[] { "7.7.8", 1010 });
data.add(new Object[] { "7.7.9", 1011 });
data.add(new Object[] { "IANA Assigned", 1012 });
data.add(new Object[] { "IANA Assigned", 1013 });
data.add(new Object[] { "IANA Assigned", 1014 });
data.add(new Object[] { "7.7.10", 3000 });
data.add(new Object[] { "7.7.11", 3999 });
data.add(new Object[] { "7.7.12", 4000 });
data.add(new Object[] { "7.7.13", 4999 });
// @formatter:on
return data;
}
private final int statusCode;
public CloseHandling_GoodStatusCodesTest(String testId, int statusCode)
{
LOG.debug("Test ID: {}", testId);
this.statusCode = statusCode;
}
/**
* just the close code, no reason
*
@ -89,20 +92,20 @@ public class CloseHandling_GoodStatusCodesTest extends AbstractLocalServerCase
BufferUtil.clearToFill(payload);
payload.putChar((char) statusCode);
BufferUtil.flipToFlush(payload, 0);
List<WebSocketFrame> send = new ArrayList<>();
send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseFrame().setPayload(DataUtils.copyOf(payload)));
try (LocalFuzzer session = server.newLocalFuzzer())
{
session.sendBulk(send);
session.expect(expect);
}
}
/**
* the good close code, with reason
*
@ -115,13 +118,13 @@ public class CloseHandling_GoodStatusCodesTest extends AbstractLocalServerCase
payload.putChar((char) statusCode);
payload.put(StringUtil.getBytes("Reason"));
payload.flip();
List<WebSocketFrame> send = new ArrayList<>();
send.add(new CloseFrame().setPayload(payload.slice()));
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseFrame().setPayload(DataUtils.copyOf(payload)));
try (LocalFuzzer session = server.newLocalFuzzer())
{
session.sendBulk(send);

View File

@ -53,7 +53,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{
@ -80,7 +80,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new TextFrame().setPayload("small")); // echo on good frame
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{
@ -107,7 +107,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new TextFrame().setPayload("small")); // echo on good frame
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{
@ -134,7 +134,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new TextFrame().setPayload("small")); // echo on good frame
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{
@ -161,7 +161,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{
@ -188,7 +188,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{
@ -216,7 +216,7 @@ public class ReservedBitTest extends AbstractLocalServerCase
List<WebSocketFrame> expect = new ArrayList<>();
expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame());
try (StacklessLogging ignored = new StacklessLogging(Parser.class);
LocalFuzzer session = server.newLocalFuzzer())
{