Merge branch 'master' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project
This commit is contained in:
commit
b0a9ec30f9
|
@ -32,7 +32,7 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
|
||||
/**
|
||||
* <p>Implementation of the
|
||||
* <a href="http://dev.w3.org/2006/waf/access-control/">cross-origin resource sharing</a>.</p>
|
||||
* <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing</a>.</p>
|
||||
* <p>A typical example is to use this filter to allow cross-domain
|
||||
* <a href="http://cometd.org">cometd</a> communication using the standard
|
||||
* long polling transport instead of the JSONP transport (that is less
|
||||
|
@ -74,38 +74,39 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*/
|
||||
public class CrossOriginFilter implements Filter
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(CrossOriginFilter.class);
|
||||
private static final Logger logger = Log.getLogger(CrossOriginFilter.class);
|
||||
|
||||
// Request headers
|
||||
private static final String ORIGIN_HEADER = "Origin";
|
||||
private static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method";
|
||||
private static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers";
|
||||
public static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method";
|
||||
public static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers";
|
||||
// Response headers
|
||||
private static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";
|
||||
private static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods";
|
||||
private static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers";
|
||||
private static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age";
|
||||
private static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials";
|
||||
public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";
|
||||
public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods";
|
||||
public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers";
|
||||
public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age";
|
||||
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials";
|
||||
// Implementation constants
|
||||
private static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins";
|
||||
private static final String ALLOWED_METHODS_PARAM = "allowedMethods";
|
||||
private static final String ALLOWED_HEADERS_PARAM = "allowedHeaders";
|
||||
private static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge";
|
||||
private static final String ALLOWED_CREDENTIALS_PARAM = "allowCredentials";
|
||||
public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins";
|
||||
public static final String ALLOWED_METHODS_PARAM = "allowedMethods";
|
||||
public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders";
|
||||
public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge";
|
||||
public static final String ALLOW_CREDENTIALS_PARAM = "allowCredentials";
|
||||
private static final String ANY_ORIGIN = "*";
|
||||
private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
|
||||
|
||||
private boolean anyOriginAllowed = false;
|
||||
private boolean anyOriginAllowed;
|
||||
private List<String> allowedOrigins = new ArrayList<String>();
|
||||
private List<String> allowedMethods = new ArrayList<String>();
|
||||
private List<String> allowedHeaders = new ArrayList<String>();
|
||||
private int preflightMaxAge = 0;
|
||||
private boolean allowCredentials = true;
|
||||
private boolean allowCredentials;
|
||||
|
||||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM);
|
||||
if (allowedOriginsConfig == null) allowedOriginsConfig = "*";
|
||||
if (allowedOriginsConfig == null)
|
||||
allowedOriginsConfig = "*";
|
||||
String[] allowedOrigins = allowedOriginsConfig.split(",");
|
||||
for (String allowedOrigin : allowedOrigins)
|
||||
{
|
||||
|
@ -126,34 +127,38 @@ public class CrossOriginFilter implements Filter
|
|||
}
|
||||
|
||||
String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM);
|
||||
if (allowedMethodsConfig == null) allowedMethodsConfig = "GET,POST";
|
||||
if (allowedMethodsConfig == null)
|
||||
allowedMethodsConfig = "GET,POST,HEAD";
|
||||
allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(",")));
|
||||
|
||||
String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM);
|
||||
if (allowedHeadersConfig == null) allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin";
|
||||
if (allowedHeadersConfig == null)
|
||||
allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin";
|
||||
allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(",")));
|
||||
|
||||
String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM);
|
||||
if (preflightMaxAgeConfig == null) preflightMaxAgeConfig = "1800"; // Default is 30 minutes
|
||||
if (preflightMaxAgeConfig == null)
|
||||
preflightMaxAgeConfig = "1800"; // Default is 30 minutes
|
||||
try
|
||||
{
|
||||
preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig);
|
||||
}
|
||||
catch (NumberFormatException x)
|
||||
{
|
||||
LOG.info("Cross-origin filter, could not parse '{}' parameter as integer: {}", PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig);
|
||||
logger.info("Cross-origin filter, could not parse '{}' parameter as integer: {}", PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig);
|
||||
}
|
||||
|
||||
String allowedCredentialsConfig = config.getInitParameter(ALLOWED_CREDENTIALS_PARAM);
|
||||
if (allowedCredentialsConfig == null) allowedCredentialsConfig = "false";
|
||||
String allowedCredentialsConfig = config.getInitParameter(ALLOW_CREDENTIALS_PARAM);
|
||||
if (allowedCredentialsConfig == null)
|
||||
allowedCredentialsConfig = "true";
|
||||
allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig);
|
||||
|
||||
LOG.debug("Cross-origin filter configuration: " +
|
||||
logger.debug("Cross-origin filter configuration: " +
|
||||
ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " +
|
||||
ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " +
|
||||
ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " +
|
||||
PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
|
||||
ALLOWED_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig);
|
||||
ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig);
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
|
@ -171,18 +176,23 @@ public class CrossOriginFilter implements Filter
|
|||
{
|
||||
if (isSimpleRequest(request))
|
||||
{
|
||||
LOG.debug("Cross-origin request to {} is a simple cross-origin request", request.getRequestURI());
|
||||
logger.debug("Cross-origin request to {} is a simple cross-origin request", request.getRequestURI());
|
||||
handleSimpleResponse(request, response, origin);
|
||||
}
|
||||
else
|
||||
else if (isPreflightRequest(request))
|
||||
{
|
||||
LOG.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI());
|
||||
logger.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI());
|
||||
handlePreflightResponse(request, response, origin);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI());
|
||||
handleSimpleResponse(request, response, origin);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed origins " + allowedOrigins);
|
||||
logger.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed origins " + allowedOrigins);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,23 +211,42 @@ public class CrossOriginFilter implements Filter
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean originMatches(String origin)
|
||||
private boolean originMatches(String originList)
|
||||
{
|
||||
if (anyOriginAllowed) return true;
|
||||
if (anyOriginAllowed)
|
||||
return true;
|
||||
|
||||
if (originList.trim().length() == 0)
|
||||
return false;
|
||||
|
||||
String[] origins = originList.split(" ");
|
||||
for (String origin : origins)
|
||||
{
|
||||
if (origin.trim().length() == 0)
|
||||
continue;
|
||||
|
||||
boolean allowed = false;
|
||||
for (String allowedOrigin : allowedOrigins)
|
||||
{
|
||||
if (allowedOrigin.equals(origin)) return true;
|
||||
if (allowedOrigin.equals(origin))
|
||||
{
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allowed)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSimpleRequest(HttpServletRequest request)
|
||||
{
|
||||
String method = request.getMethod();
|
||||
if (SIMPLE_HTTP_METHODS.contains(method))
|
||||
{
|
||||
// TODO: implement better section 6.1
|
||||
// Section 6.1 says that for a request to be simple, custom request headers must be simple.
|
||||
// TODO: implement better detection of simple headers
|
||||
// The specification says that for a request to be simple, custom request headers must be simple.
|
||||
// Here for simplicity I just check if there is a Access-Control-Request-Method header,
|
||||
// which is required for preflight requests
|
||||
return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null;
|
||||
|
@ -225,50 +254,55 @@ public class CrossOriginFilter implements Filter
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean isPreflightRequest(HttpServletRequest request)
|
||||
{
|
||||
String method = request.getMethod();
|
||||
if (!"OPTIONS".equalsIgnoreCase(method))
|
||||
return false;
|
||||
if (request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin)
|
||||
{
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
|
||||
if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
|
||||
if (allowCredentials)
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
|
||||
}
|
||||
|
||||
private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin)
|
||||
{
|
||||
// Implementation of section 5.2
|
||||
|
||||
// 5.2.3 and 5.2.5
|
||||
boolean methodAllowed = isMethodAllowed(request);
|
||||
if (!methodAllowed) return;
|
||||
// 5.2.4 and 5.2.6
|
||||
if (!methodAllowed)
|
||||
return;
|
||||
boolean headersAllowed = areHeadersAllowed(request);
|
||||
if (!headersAllowed) return;
|
||||
// 5.2.7
|
||||
if (!headersAllowed)
|
||||
return;
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
|
||||
if (allowCredentials) response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
|
||||
// 5.2.8
|
||||
if (preflightMaxAge > 0) response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge));
|
||||
// 5.2.9
|
||||
if (allowCredentials)
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
|
||||
if (preflightMaxAge > 0)
|
||||
response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge));
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods));
|
||||
// 5.2.10
|
||||
response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders));
|
||||
}
|
||||
|
||||
private boolean isMethodAllowed(HttpServletRequest request)
|
||||
{
|
||||
String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER);
|
||||
LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod);
|
||||
logger.debug("{} is {}", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod);
|
||||
boolean result = false;
|
||||
if (accessControlRequestMethod != null)
|
||||
{
|
||||
result = allowedMethods.contains(accessControlRequestMethod);
|
||||
}
|
||||
LOG.debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}", accessControlRequestMethod, allowedMethods);
|
||||
logger.debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}", accessControlRequestMethod, allowedMethods);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean areHeadersAllowed(HttpServletRequest request)
|
||||
{
|
||||
String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER);
|
||||
LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders);
|
||||
logger.debug("{} is {}", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders);
|
||||
boolean result = true;
|
||||
if (accessControlRequestHeaders != null)
|
||||
{
|
||||
|
@ -291,7 +325,7 @@ public class CrossOriginFilter implements Filter
|
|||
}
|
||||
}
|
||||
}
|
||||
LOG.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", accessControlRequestHeaders, allowedHeaders);
|
||||
logger.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", accessControlRequestHeaders, allowedHeaders);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.FilterMapping;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.testing.ServletTester;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CrossOriginFilterTest
|
||||
{
|
||||
private ServletTester tester;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception
|
||||
{
|
||||
tester = new ServletTester();
|
||||
tester.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception
|
||||
{
|
||||
if (tester != null)
|
||||
tester.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestWithNoOriginArrivesToApplication() throws Exception
|
||||
{
|
||||
tester.getContext().addFilter(CrossOriginFilter.class, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithNonMatchingOrigin() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
String origin = "http://localhost";
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin);
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String otherOrigin = origin.replace("localhost", "127.0.0.1");
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Origin: " + otherOrigin + "\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithMatchingOrigin() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
String origin = "http://localhost";
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin);
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Origin: " + origin + "\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithMatchingMultipleOrigins() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
String origin = "http://localhost";
|
||||
String otherOrigin = origin.replace("localhost", "127.0.0.1");
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin + "," + otherOrigin);
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
// Use 2 spaces as separator to test that the implementation does not fail
|
||||
"Origin: " + otherOrigin + " " + " " + origin + "\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithoutCredentials() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOW_CREDENTIALS_PARAM, "false");
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonSimpleRequestWithoutPreflight() throws Exception
|
||||
{
|
||||
// We cannot know if an actual request has performed the preflight before:
|
||||
// we'll trust browsers to do it right, so responses to actual requests
|
||||
// will contain the CORS response headers.
|
||||
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"PUT / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsRequestButNotPreflight() throws Exception
|
||||
{
|
||||
// We cannot know if an actual request has performed the preflight before:
|
||||
// we'll trust browsers to do it right, so responses to actual requests
|
||||
// will contain the CORS response headers.
|
||||
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"OPTIONS / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPUTRequestWithPreflight() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "PUT");
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
// Preflight request
|
||||
String request = "" +
|
||||
"OPTIONS / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": PUT\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_MAX_AGE_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
// Preflight request was ok, now make the actual request
|
||||
request = "" +
|
||||
"PUT / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDELETERequestWithPreflightAndAllowedCustomHeaders() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,HEAD,POST,PUT,DELETE");
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin,X-Custom");
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
// Preflight request
|
||||
String request = "" +
|
||||
"OPTIONS / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": DELETE\r\n" +
|
||||
CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": origin,x-custom,x-requested-with\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_MAX_AGE_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
// Preflight request was ok, now make the actual request
|
||||
request = "" +
|
||||
"DELETE / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"X-Custom: value\r\n" +
|
||||
"X-Requested-With: local\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDELETERequestWithPreflightAndNotAllowedCustomHeaders() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,HEAD,POST,PUT,DELETE");
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
// Preflight request
|
||||
String request = "" +
|
||||
"OPTIONS / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD_HEADER + ": DELETE\r\n" +
|
||||
CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS_HEADER + ": origin,x-custom,x-requested-with\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
// The preflight request failed because header X-Custom is not allowed, actual request not issued
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrossOriginFilterDisabledForWebSocketUpgrade() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
tester.getContext().addFilter(filterHolder, "/*", FilterMapping.DEFAULT);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Upgrade: WebSocket\r\n" +
|
||||
"Origin: http://localhost\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public static class ResourceServlet extends HttpServlet
|
||||
{
|
||||
private final CountDownLatch latch;
|
||||
|
||||
public ResourceServlet(CountDownLatch latch)
|
||||
{
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue