Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java')
-rw-r--r--jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java159
1 files changed, 104 insertions, 55 deletions
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
index e1ba9b988c..a4f8cabc9d 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
@@ -26,6 +26,7 @@ import java.util.Enumeration;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -35,48 +36,76 @@ 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;
/**
- * <p>Implementation of the
- * <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
+ * Implementation of the
+ * <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing</a>.
+ * <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
- * efficient and less reactive to failures).</p>
- * <p>This filter allows the following configuration parameters:
- * <ul>
- * <li><b>allowedOrigins</b>, a comma separated list of origins that are
+ * efficient and less reactive to failures).
+ * <p>
+ * This filter allows the following configuration parameters:
+ * <dl>
+ * <dt>allowedOrigins</dt>
+ * <dd>a comma separated list of origins that are
* allowed to access the resources. Default value is <b>*</b>, meaning all
- * origins.<br />
+ * origins.
+ * <p>
* If an allowed origin contains one or more * characters (for example
* http://*.domain.com), then "*" characters are converted to ".*", "."
* characters are escaped to "\." and the resulting allowed origin
- * interpreted as a regular expression.<br />
+ * interpreted as a regular expression.
+ * <p>
* Allowed origins can therefore be more complex expressions such as
* https?://*.domain.[a-z]{3} that matches http or https, multiple subdomains
- * and any 3 letter top-level domain (.com, .net, .org, etc.).</li>
- * <li><b>allowedMethods</b>, a comma separated list of HTTP methods that
+ * and any 3 letter top-level domain (.com, .net, .org, etc.).</dd>
+ *
+ * <dt>allowedTimingOrigins</dt>
+ * <dd>a comma separated list of origins that are
+ * allowed to time the resource. Default value is the empty string, meaning
+ * no origins.
+ * <p>
+ * The check whether the timing header is set, will be performed only if
+ * the user gets general access to the resource using the <b>allowedOrigins</b>.
+ *
+ * <dt>allowedMethods</dt>
+ * <dd>a comma separated list of HTTP methods that
* are allowed to be used when accessing the resources. Default value is
- * <b>GET,POST,HEAD</b></li>
- * <li><b>allowedHeaders</b>, a comma separated list of HTTP headers that
+ * <b>GET,POST,HEAD</b></dd>
+ *
+ *
+ * <dt>allowedHeaders</dt>
+ * <dd>a comma separated list of HTTP headers that
* are allowed to be specified when accessing the resources. Default value
* is <b>X-Requested-With,Content-Type,Accept,Origin</b>. If the value is a single "*",
- * this means that any headers will be accepted.</li>
- * <li><b>preflightMaxAge</b>, the number of seconds that preflight requests
+ * this means that any headers will be accepted.</dd>
+ *
+ * <dt>preflightMaxAge</dt>
+ * <dd>the number of seconds that preflight requests
* can be cached by the client. Default value is <b>1800</b> seconds, or 30
- * minutes</li>
- * <li><b>allowCredentials</b>, a boolean indicating if the resource allows
- * requests with credentials. Default value is <b>true</b></li>
- * <li><b>exposedHeaders</b>, a comma separated list of HTTP headers that
+ * minutes</dd>
+ *
+ * <dt>allowCredentials</dt>
+ * <dd>a boolean indicating if the resource allows
+ * requests with credentials. Default value is <b>true</b></dd>
+ *
+ * <dt>exposedHeaders</dt>
+ * <dd>a comma separated list of HTTP headers that
* are allowed to be exposed on the client. Default value is the
- * <b>empty list</b></li>
- * <li><b>chainPreflight</b>, if true preflight requests are chained to their
+ * <b>empty list</b></dd>
+ *
+ * <dt>chainPreflight</dt>
+ * <dd>if true preflight requests are chained to their
* target resource for normal handling (as an OPTION request). Otherwise the
- * filter will response to the preflight. Default is <b>true</b>.</li>
- * </ul></p>
- * <p>A typical configuration could be:</p>
+ * filter will response to the preflight. Default is <b>true</b>.</dd>
+ *
+ * </dl>
+ * A typical configuration could be:
* <pre>
* &lt;web-app ...&gt;
* ...
@@ -107,8 +136,10 @@ public class CrossOriginFilter implements Filter
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";
public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers";
+ public static final String TIMING_ALLOW_ORIGIN_HEADER = "Timing-Allow-Origin";
// Implementation constants
public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins";
+ public static final String ALLOWED_TIMING_ORIGINS_PARAM = "allowedTimingOrigins";
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";
@@ -117,13 +148,17 @@ public class CrossOriginFilter implements Filter
public static final String OLD_CHAIN_PREFLIGHT_PARAM = "forwardPreflight";
public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight";
private static final String ANY_ORIGIN = "*";
+ private static final String DEFAULT_ALLOWED_ORIGINS = "*";
+ private static final String DEFAULT_ALLOWED_TIMING_ORIGINS = "";
private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
private static final List<String> DEFAULT_ALLOWED_METHODS = Arrays.asList("GET", "POST", "HEAD");
private static final List<String> DEFAULT_ALLOWED_HEADERS = Arrays.asList("X-Requested-With", "Content-Type", "Accept", "Origin");
private boolean anyOriginAllowed;
+ private boolean anyTimingOriginAllowed;
private boolean anyHeadersAllowed;
private List<String> allowedOrigins = new ArrayList<String>();
+ private List<String> allowedTimingOrigins = new ArrayList<String>();
private List<String> allowedMethods = new ArrayList<String>();
private List<String> allowedHeaders = new ArrayList<String>();
private List<String> exposedHeaders = new ArrayList<String>();
@@ -134,32 +169,16 @@ public class CrossOriginFilter implements Filter
public void init(FilterConfig config) throws ServletException
{
String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM);
- if (allowedOriginsConfig == null)
- allowedOriginsConfig = "*";
- String[] allowedOrigins = allowedOriginsConfig.split(",");
- for (String allowedOrigin : allowedOrigins)
- {
- allowedOrigin = allowedOrigin.trim();
- if (allowedOrigin.length() > 0)
- {
- if (ANY_ORIGIN.equals(allowedOrigin))
- {
- anyOriginAllowed = true;
- this.allowedOrigins.clear();
- break;
- }
- else
- {
- this.allowedOrigins.add(allowedOrigin);
- }
- }
- }
+ String allowedTimingOriginsConfig = config.getInitParameter(ALLOWED_TIMING_ORIGINS_PARAM);
+
+ anyOriginAllowed = generateAllowedOrigins(allowedOrigins, allowedOriginsConfig, DEFAULT_ALLOWED_ORIGINS);
+ anyTimingOriginAllowed = generateAllowedOrigins(allowedTimingOrigins, allowedTimingOriginsConfig, DEFAULT_ALLOWED_TIMING_ORIGINS);
String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM);
if (allowedMethodsConfig == null)
allowedMethods.addAll(DEFAULT_ALLOWED_METHODS);
else
- allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(",")));
+ allowedMethods.addAll(Arrays.asList(StringUtil.csvSplit(allowedMethodsConfig)));
String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM);
if (allowedHeadersConfig == null)
@@ -167,7 +186,7 @@ public class CrossOriginFilter implements Filter
else if ("*".equals(allowedHeadersConfig))
anyHeadersAllowed = true;
else
- allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(",")));
+ allowedHeaders.addAll(Arrays.asList(StringUtil.csvSplit(allowedHeadersConfig)));
String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM);
if (preflightMaxAgeConfig == null)
@@ -189,7 +208,7 @@ public class CrossOriginFilter implements Filter
String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM);
if (exposedHeadersConfig == null)
exposedHeadersConfig = "";
- exposedHeaders.addAll(Arrays.asList(exposedHeadersConfig.split(",")));
+ exposedHeaders.addAll(Arrays.asList(StringUtil.csvSplit(exposedHeadersConfig)));
String chainPreflightConfig = config.getInitParameter(OLD_CHAIN_PREFLIGHT_PARAM);
if (chainPreflightConfig != null)
@@ -204,6 +223,7 @@ public class CrossOriginFilter implements Filter
{
LOG.debug("Cross-origin filter configuration: " +
ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " +
+ ALLOWED_TIMING_ORIGINS_PARAM + " = " + allowedTimingOriginsConfig + ", " +
ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " +
ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " +
PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
@@ -214,6 +234,29 @@ public class CrossOriginFilter implements Filter
}
}
+ private boolean generateAllowedOrigins(List<String> allowedOriginStore, String allowedOriginsConfig, String defaultOrigin)
+ {
+ if (allowedOriginsConfig == null)
+ allowedOriginsConfig = defaultOrigin;
+ String[] allowedOrigins = StringUtil.csvSplit(allowedOriginsConfig);
+ for (String allowedOrigin : allowedOrigins)
+ {
+ if (allowedOrigin.length() > 0)
+ {
+ if (ANY_ORIGIN.equals(allowedOrigin))
+ {
+ allowedOriginStore.clear();
+ return true;
+ }
+ else
+ {
+ allowedOriginStore.add(allowedOrigin);
+ }
+ }
+ }
+ return false;
+ }
+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
handle((HttpServletRequest)request, (HttpServletResponse)response, chain);
@@ -225,7 +268,7 @@ public class CrossOriginFilter implements Filter
// Is it a cross origin request ?
if (origin != null && isEnabled(request))
{
- if (originMatches(origin))
+ if (anyOriginAllowed || originMatches(allowedOrigins, origin))
{
if (isSimpleRequest(request))
{
@@ -246,6 +289,15 @@ public class CrossOriginFilter implements Filter
LOG.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI());
handleSimpleResponse(request, response, origin);
}
+
+ if (anyTimingOriginAllowed || originMatches(allowedTimingOrigins, origin))
+ {
+ response.setHeader(TIMING_ALLOW_ORIGIN_HEADER, origin);
+ }
+ else
+ {
+ LOG.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed timing origins " + allowedTimingOrigins);
+ }
}
else
{
@@ -260,12 +312,12 @@ public class CrossOriginFilter implements Filter
{
// WebSocket clients such as Chrome 5 implement a version of the WebSocket
// protocol that does not accept extra response headers on the upgrade response
- for (Enumeration connections = request.getHeaders("Connection"); connections.hasMoreElements();)
+ for (Enumeration<String> connections = request.getHeaders("Connection"); connections.hasMoreElements();)
{
String connection = (String)connections.nextElement();
if ("Upgrade".equalsIgnoreCase(connection))
{
- for (Enumeration upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements();)
+ for (Enumeration<String> upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements();)
{
String upgrade = (String)upgrades.nextElement();
if ("WebSocket".equalsIgnoreCase(upgrade))
@@ -276,11 +328,8 @@ public class CrossOriginFilter implements Filter
return true;
}
- private boolean originMatches(String originList)
+ private boolean originMatches(List<String> allowedOrigins, String originList)
{
- if (anyOriginAllowed)
- return true;
-
if (originList.trim().length() == 0)
return false;
@@ -400,7 +449,7 @@ public class CrossOriginFilter implements Filter
return Collections.emptyList();
List<String> requestedHeaders = new ArrayList<String>();
- String[] headers = accessControlRequestHeaders.split(",");
+ String[] headers = StringUtil.csvSplit(accessControlRequestHeaders);
for (String header : headers)
{
String h = header.trim();

Back to the top