Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /*
   * Copyright (c) 2009-2009 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.util.List;
 import  javax.servlet.Filter;
 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;
 

Implementation of the cross-origin resource sharing.

A typical example is to use this filter to allow cross-domain cometd communication using the standard long polling transport instead of the JSONP transport (that is less efficient and less reactive to failures).

This filter allows the following configuration parameters:

  • allowedOrigins, a comma separated list of origins that are allowed to access the resources. Default value is *, meaning all origins.
    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.
    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.).
  • allowedMethods, a comma separated list of HTTP methods that are allowed to be used when accessing the resources. Default value is GET,POST
  • allowedHeaders, a comma separated list of HTTP headers that are allowed to be specified when accessing the resources. Default value is X-Requested-With
  • preflightMaxAge, the number of seconds that preflight requests can be cached by the client. Default value is 1800 seconds, or 30 minutes
  • allowCredentials, a boolean indicating if the resource allows requests with credentials. Default value is false

A typical configuration could be:

 <web-app ...>
     ...
     <filter>
         <filter-name>cross-origin</filter-name>
         <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
     </filter>
     <filter-mapping>
         <filter-name>cross-origin</filter-name>
         <url-pattern>/cometd/*</url-pattern>
     </filter-mapping>
     ...
 </web-app>
 

Version:
$Revision$ $Date$
 
 public class CrossOriginFilter implements Filter
 {
     private static final Logger LOG = Log.getLogger(CrossOriginFilter.class);
 
     // Request headers
     private static final String ORIGIN_HEADER = "Origin";
     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
     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
    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<StringSIMPLE_HTTP_METHODS = Arrays.asList("GET""POST""HEAD");
    private boolean anyOriginAllowed;
    private List<StringallowedOrigins = new ArrayList<String>();
    private List<StringallowedMethods = new ArrayList<String>();
    private List<StringallowedHeaders = new ArrayList<String>();
    private int preflightMaxAge = 0;
    private boolean allowCredentials;
    public void init(FilterConfig configthrows ServletException
    {
        String allowedOriginsConfig = config.getInitParameter();
        if (allowedOriginsConfig == null)
            allowedOriginsConfig = "*";
        String[] allowedOrigins = allowedOriginsConfig.split(",");
        for (String allowedOrigin : allowedOrigins)
        {
            allowedOrigin = allowedOrigin.trim();
            if (allowedOrigin.length() > 0)
            {
                if (.equals(allowedOrigin))
                {
                     = true;
                    this..clear();
                    break;
                }
                else
                {
                    this..add(allowedOrigin);
                }
            }
        }
        String allowedMethodsConfig = config.getInitParameter();
        if (allowedMethodsConfig == null)
            allowedMethodsConfig = "GET,POST,HEAD";
        .addAll(Arrays.asList(allowedMethodsConfig.split(",")));
        String allowedHeadersConfig = config.getInitParameter();
        if (allowedHeadersConfig == null)
            allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin";
        .addAll(Arrays.asList(allowedHeadersConfig.split(",")));
        String preflightMaxAgeConfig = config.getInitParameter();
        if (preflightMaxAgeConfig == null)
            preflightMaxAgeConfig = "1800"// Default is 30 minutes
        try
        {
             = Integer.parseInt(preflightMaxAgeConfig);
        }
        catch (NumberFormatException x)
        {
            .info("Cross-origin filter, could not parse '{}' parameter as integer: {}"preflightMaxAgeConfig);
        }
        String allowedCredentialsConfig = config.getInitParameter();
        if (allowedCredentialsConfig == null)
            allowedCredentialsConfig = "true";
         = Boolean.parseBoolean(allowedCredentialsConfig);
        if (.isDebugEnabled())
        {
            .debug("Cross-origin filter configuration: " +
                     + " = " + allowedOriginsConfig + ", " +
                     + " = " + allowedMethodsConfig + ", " +
                     + " = " + allowedHeadersConfig + ", " +
                     + " = " + preflightMaxAgeConfig + ", " +
                     + " = " + allowedCredentialsConfig);
        }
    }
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chainthrows IOException, ServletException
    {
        handle((HttpServletRequest)request, (HttpServletResponse)responsechain);
    }
    private void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chainthrows IOException, ServletException
    {
        String origin = request.getHeader();
        // Is it a cross origin request ?
        if (origin != null && isEnabled(request))
        {
            if (originMatches(origin))
            {
                if (isSimpleRequest(request))
                {
                    .debug("Cross-origin request to {} is a simple cross-origin request"request.getRequestURI());
                    handleSimpleResponse(requestresponseorigin);
                }
                else if (isPreflightRequest(request))
                {
                    .debug("Cross-origin request to {} is a preflight cross-origin request"request.getRequestURI());
                    handlePreflightResponse(requestresponseorigin);
                }
                else
                {
                    .debug("Cross-origin request to {} is a non-simple cross-origin request"request.getRequestURI());
                    handleSimpleResponse(requestresponseorigin);
                }
            }
            else
            {
                .debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed origins " + );
            }
        }
        chain.doFilter(requestresponse);
    }
    protected boolean isEnabled(HttpServletRequest request)
    {
        // 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();)
        {
            String connection = (String)connections.nextElement();
            if ("Upgrade".equalsIgnoreCase(connection))
            {
                for (Enumeration upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements();)
                {
                    String upgrade = (String)upgrades.nextElement();
                    if ("WebSocket".equalsIgnoreCase(upgrade))
                        return false;
                }
            }
        }
        return true;
    }
    private boolean originMatches(String originList)
    {
        if ()
            return true;
        if (originList.trim().length() == 0)
            return false;
        String[] origins = originList.split(" ");
        for (String origin : origins)
        {
            if (origin.trim().length() == 0)
                continue;
            for (String allowedOrigin : )
            {
                if (allowedOrigin.contains("*"))
                {
                    Matcher matcher = createMatcher(origin,allowedOrigin);
                    if (matcher.matches())
                        return true;
                }
                else if (allowedOrigin.equals(origin))
                {
                    return true;
                }
            }
        }
        return false;
    }
    private Matcher createMatcher(String originString allowedOrigin)
    {
        String regex = parseAllowedWildcardOriginToRegex(allowedOrigin);
        Pattern pattern = Pattern.compile(regex);
        return pattern.matcher(origin);
    }
    private String parseAllowedWildcardOriginToRegex(String allowedOrigin)
    {
        String regex = allowedOrigin.replace(".","\\.");
        return regex.replace("*",".*"); // we want to be greedy here to match multiple subdomains, thus we use .*
    }
    private boolean isSimpleRequest(HttpServletRequest request)
    {
        String method = request.getMethod();
        if (.contains(method))
        {
            // 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() == null;
        }
        return false;
    }
    private boolean isPreflightRequest(HttpServletRequest request)
    {
        String method = request.getMethod();
        if (!"OPTIONS".equalsIgnoreCase(method))
            return false;
        if (request.getHeader() == null)
            return false;
        return true;
    }
    private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse responseString origin)
    {
        response.setHeader(origin);
        if ()
            response.setHeader("true");
    }
    private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse responseString origin)
    {
        boolean methodAllowed = isMethodAllowed(request);
        if (!methodAllowed)
            return;
        boolean headersAllowed = areHeadersAllowed(request);
        if (!headersAllowed)
            return;
        response.setHeader(origin);
        if ()
            response.setHeader("true");
        if ( > 0)
            response.setHeader(, String.valueOf());
        response.setHeader(commify());
        response.setHeader(commify());
    }
    private boolean isMethodAllowed(HttpServletRequest request)
    {
        String accessControlRequestMethod = request.getHeader();
        .debug("{} is {}"accessControlRequestMethod);
        boolean result = false;
        if (accessControlRequestMethod != null)
            result = .contains(accessControlRequestMethod);
        .debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}"accessControlRequestMethod);
        return result;
    }
    private boolean areHeadersAllowed(HttpServletRequest request)
    {
        String accessControlRequestHeaders = request.getHeader();
        .debug("{} is {}"accessControlRequestHeaders);
        boolean result = true;
        if (accessControlRequestHeaders != null)
        {
            String[] headers = accessControlRequestHeaders.split(",");
            for (String header : headers)
            {
                boolean headerAllowed = false;
                for (String allowedHeader : )
                {
                    if (header.trim().equalsIgnoreCase(allowedHeader.trim()))
                    {
                        headerAllowed = true;
                        break;
                    }
                }
                if (!headerAllowed)
                {
                    result = false;
                    break;
                }
            }
        }
        .debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}"accessControlRequestHeaders);
        return result;
    }
    private String commify(List<Stringstrings)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < strings.size(); ++i)
        {
            if (i > 0) builder.append(",");
            String string = strings.get(i);
            builder.append(string);
        }
        return builder.toString();
    }
    public void destroy()
    {
         = false;
        .clear();
        .clear();
        .clear();
         = 0;
         = false;
    }
New to GrepCode? Check out our FAQ X