/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.application;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.Method;
import org.restlet.engine.util.SetUtils;
import org.restlet.routing.Filter;
/**
* Filter that helps support CORS requests. This filter lets the target
* resources specify the allowed methods.
*
* Example:
*
* <pre>
* Router router = new Router(getContext());
*
* CorsFilter corsFilter = new CorsFilter(getContext(), router);
* corsFilter.setAllowedOrigins(new HashSet(Arrays.asList("http://server.com")));
* corsFilter.setAllowedCredentials(true);
* </pre>
*
* @author Manuel Boillod
*/
public class CorsFilter extends Filter {
private static final Set<Method> DEFAULT_ALLOWED_METHODS =
new HashSet<>(Arrays.asList(Method.GET, Method.POST, Method.PUT, Method.DELETE));
/**
* If true, copies the value of 'Access-Control-Request-Headers' request
* header into the 'Access-Control-Allow-Headers' response header. If false,
* use {@link #allowedHeaders}. Default is true.
*/
public boolean allowAllRequestedHeaders = true;
/**
* If true, add 'Access-Control-Allow-Credentials' header. Default is false.
*/
private boolean allowedCredentials = false;
/**
* The value of 'Access-Control-Allow-Headers' response header. Used only if
* {@link #allowAllRequestedHeaders} is false.
*/
private Set<String> allowedHeaders = null;
/** The value of 'Access-Control-Allow-Origin' header. Default is '*'. */
private Set<String> allowedOrigins = SetUtils.newHashSet("*");
/** Helper for generating CORS response. */
private CorsResponseHelper corsResponseHelper;
/** The value of 'Access-Control-Expose-Headers' response header. */
private Set<String> exposedHeaders = null;
/**
* If true, the filter does not call the server resource for OPTIONS method
* of CORS request and set Access-Control-Allow-Methods header with
* {@link #DEFAULT_ALLOWED_METHODS}. Default is false.
*/
private boolean skippingResourceForCorsOptions = false;
/**
* Constructor.
*/
public CorsFilter() {
this(null);
}
/**
* Constructor.
*
* @param context
* The context.
*/
public CorsFilter(Context context) {
super(context, null);
}
/**
* Constructor.
*
* @param context
* The context.
* @param next
* The next Restlet.
*/
public CorsFilter(Context context, Restlet next) {
super(context, next);
}
/**
* Skip the call to the server resource if the {@link #skippingResourceForCorsOptions}
* is true and if the current request use the OPTIONS method and is a CORS request.
*
* @param request
* The request to handle.
* @param response
* The response to update.
*/
@Override
protected int beforeHandle(Request request, Response response) {
if (skippingResourceForCorsOptions
&& Method.OPTIONS.equals(request.getMethod())
&& getCorsResponseHelper().isCorsRequest(request)) {
response.setAllowedMethods(DEFAULT_ALLOWED_METHODS);
return Filter.SKIP;
} else {
return Filter.CONTINUE;
}
}
/**
* Add CORS headers to response
*
* @param request
* The request to handle.
* @param response
* The response
*/
@Override
protected void afterHandle(Request request, Response response) {
getCorsResponseHelper().addCorsResponseHeaders(request, response);
}
/**
* Returns the modifiable set of headers allowed by the actual request on
* the current resource.<br>
* Note that when used with HTTP connectors, this property maps to the
* "Access-Control-Allow-Headers" header.
*
* @return The set of headers allowed by the actual request on the current
* resource.
*/
public Set<String> getAllowedHeaders() {
return allowedHeaders;
}
/**
* Returns the URI an origin server allows for the requested resource. Use
* "*" as a wildcard character.<br>
* Note that when used with HTTP connectors, this property maps to the
* "Access-Control-Allow-Origin" header.
*
* @return The origin allowed by the requested resource.
*/
public Set<String> getAllowedOrigins() {
return allowedOrigins;
}
/**
* Returns a lazy-initialized instance of
* {@link org.restlet.engine.application.CorsResponseHelper}.
*/
protected CorsResponseHelper getCorsResponseHelper() {
if (corsResponseHelper == null) {
corsResponseHelper = new CorsResponseHelper();
corsResponseHelper.setAllowedCredentials(allowedCredentials);
corsResponseHelper.setAllowedOrigins(allowedOrigins);
corsResponseHelper
.setAllowAllRequestedHeaders(allowAllRequestedHeaders);
corsResponseHelper.setAllowedHeaders(allowedHeaders);
corsResponseHelper.setExposedHeaders(exposedHeaders);
}
return corsResponseHelper;
}
/**
* Returns a modifiable whitelist of headers an origin server allows for the
* requested resource.<br>
* Note that when used with HTTP connectors, this property maps to the
* "Access-Control-Expose-Headers" header.
*
* @return The set of headers an origin server allows for the requested
* resource.
*/
public Set<String> getExposedHeaders() {
return exposedHeaders;
}
/**
* If true, indicates that the value of 'Access-Control-Request-Headers'
* request header will be copied into the 'Access-Control-Allow-Headers'
* response header. If false, use {@link #allowedHeaders}.
*/
public boolean isAllowAllRequestedHeaders() {
return allowAllRequestedHeaders;
}
/**
* If true, adds 'Access-Control-Allow-Credentials' header.
*
* @return True, if the 'Access-Control-Allow-Credentials' header will be
* added.
*/
public boolean isAllowedCredentials() {
return allowedCredentials;
}
/**
* If true, the filter does not call the server resource for OPTIONS method
* of CORS request and set Access-Control-Allow-Methods header with
* {@link #DEFAULT_ALLOWED_METHODS}. Default is false.
*
* @return True if the filter does not call the server resource for
* OPTIONS method of CORS request.
*/
public boolean isSkippingResourceForCorsOptions() {
return skippingResourceForCorsOptions;
}
/**
* If true, copies the value of 'Access-Control-Request-Headers' request
* header into the 'Access-Control-Allow-Headers' response header. If false,
* use {@link #allowedHeaders}.
*
* @param allowingAllRequestedHeaders
* True to copy the value of 'Access-Control-Request-Headers'
* request header into the 'Access-Control-Allow-Headers'
* response header. If false, use {@link #allowedHeaders}.
* @return Itself for chaining methods calls.
*/
public CorsFilter setAllowingAllRequestedHeaders(
boolean allowingAllRequestedHeaders) {
this.allowAllRequestedHeaders = allowingAllRequestedHeaders;
return this;
}
/**
* If true, adds 'Access-Control-Allow-Credentials' header.
*
* @param allowedCredentials
* True to add the 'Access-Control-Allow-Credentials' header.
* @return Itself for chaining methods calls.
*/
public CorsFilter setAllowedCredentials(boolean allowedCredentials) {
this.allowedCredentials = allowedCredentials;
return this;
}
/**
* Sets the value of the 'Access-Control-Allow-Headers' response header.
* Used only if {@link #allowAllRequestedHeaders} is false.
*
* @param allowedHeaders
* The value of 'Access-Control-Allow-Headers' response header.
* @return Itself for chaining methods calls.
*/
public CorsFilter setAllowedHeaders(Set<String> allowedHeaders) {
this.allowedHeaders = allowedHeaders;
return this;
}
/**
* Sets the value of 'Access-Control-Allow-Origin' header.
*
* @param allowedOrigins
* The value of 'Access-Control-Allow-Origin' header.
* @return Itself for chaining methods calls.
*/
public CorsFilter setAllowedOrigins(Set<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
return this;
}
/**
* Sets the value of 'Access-Control-Expose-Headers' response header.
*
* @param exposedHeaders
* The value of 'Access-Control-Expose-Headers' response header.
* @return Itself for chaining methods calls.
*/
public CorsFilter setExposedHeaders(Set<String> exposedHeaders) {
this.exposedHeaders = exposedHeaders;
return this;
}
/**
* Sets the value of skipResourceForCorsOptions field.
*
* @param skipResourceForCorsOptions
* True if the filter does not call the server resource for
* OPTIONS method of CORS request.
* @return Itself for chaining methods calls.
*/
public CorsFilter setSkippingResourceForCorsOptions(boolean skipResourceForCorsOptions) {
this.skippingResourceForCorsOptions = skipResourceForCorsOptions;
return this;
}
}