package org.frameworkset.web.socket.sockjs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.frameworkset.util.ObjectUtils;
import org.frameworkset.util.annotations.HttpMethod;
import com.frameworkset.util.StringUtil;
public class CorsConfiguration {
/**
* Wildcard representing <em>all</em> origins, methods, or headers.
*/
public static final String ALL = "*";
private List<String> allowedOrigins;
private List<String> allowedMethods;
private List<String> allowedHeaders;
private List<String> exposedHeaders;
private Boolean allowCredentials;
private Long maxAge;
/**
* Construct a new, empty {@code CorsConfiguration} instance.
*/
public CorsConfiguration() {
}
/**
* Construct a new {@code CorsConfiguration} instance by copying all
* values from the supplied {@code CorsConfiguration}.
*/
public CorsConfiguration(CorsConfiguration other) {
this.allowedOrigins = other.allowedOrigins;
this.allowedMethods = other.allowedMethods;
this.allowedHeaders = other.allowedHeaders;
this.exposedHeaders = other.exposedHeaders;
this.allowCredentials = other.allowCredentials;
this.maxAge = other.maxAge;
}
/**
* Combine the supplied {@code CorsConfiguration} with this one.
* <p>Properties of this configuration are overridden by any non-null
* properties of the supplied one.
* @return the combined {@code CorsConfiguration} or {@code this}
* configuration if the supplied configuration is {@code null}
*/
public CorsConfiguration combine(CorsConfiguration other) {
if (other == null) {
return this;
}
CorsConfiguration config = new CorsConfiguration(this);
config.setAllowedOrigins(combine(this.getAllowedOrigins(), other.getAllowedOrigins()));
config.setAllowedMethods(combine(this.getAllowedMethods(), other.getAllowedMethods()));
config.setAllowedHeaders(combine(this.getAllowedHeaders(), other.getAllowedHeaders()));
config.setExposedHeaders(combine(this.getExposedHeaders(), other.getExposedHeaders()));
Boolean allowCredentials = other.getAllowCredentials();
if (allowCredentials != null) {
config.setAllowCredentials(allowCredentials);
}
Long maxAge = other.getMaxAge();
if (maxAge != null) {
config.setMaxAge(maxAge);
}
return config;
}
private List<String> combine(List<String> source, List<String> other) {
if (other == null || other.contains(ALL)) {
return source;
}
if (source == null || source.contains(ALL)) {
return other;
}
List<String> combined = new ArrayList<String>(source);
combined.addAll(other);
return combined;
}
/**
* Set the origins to allow, e.g. {@code "http://domain1.com"}.
* <p>The special value {@code "*"} allows all domains.
* <p>By default this is not set.
*/
public void setAllowedOrigins(List<String> allowedOrigins) {
this.allowedOrigins = (allowedOrigins != null ? new ArrayList<String>(allowedOrigins) : null);
}
/**
* Return the configured origins to allow, possibly {@code null}.
* @see #addAllowedOrigin(String)
* @see #setAllowedOrigins(List)
*/
public List<String> getAllowedOrigins() {
return this.allowedOrigins;
}
/**
* Add an origin to allow.
*/
public void addAllowedOrigin(String origin) {
if (this.allowedOrigins == null) {
this.allowedOrigins = new ArrayList<String>();
}
this.allowedOrigins.add(origin);
}
/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"},
* {@code "PUT"}, etc.
* <p>The special value {@code "*"} allows all methods.
* <p>If not set, only {@code "GET"} is allowed.
* <p>By default this is not set.
*/
public void setAllowedMethods(List<String> allowedMethods) {
this.allowedMethods = (allowedMethods != null ? new ArrayList<String>(allowedMethods) : null);
}
/**
* Return the allowed HTTP methods, possibly {@code null} in which case
* only {@code "GET"} is allowed.
* @see #addAllowedMethod(HttpMethod)
* @see #addAllowedMethod(String)
* @see #setAllowedMethods(List)
*/
public List<String> getAllowedMethods() {
return this.allowedMethods;
}
/**
* Add an HTTP method to allow.
*/
public void addAllowedMethod(HttpMethod method) {
if (method != null) {
addAllowedMethod(method.name());
}
}
/**
* Add an HTTP method to allow.
*/
public void addAllowedMethod(String method) {
if (StringUtil.hasText(method)) {
if (this.allowedMethods == null) {
this.allowedMethods = new ArrayList<String>();
}
this.allowedMethods.add(method);
}
}
/**
* Set the list of headers that a pre-flight request can list as allowed
* for use during an actual request.
* <p>The special value {@code "*"} allows actual requests to send any
* header.
* <p>A header name is not required to be listed if it is one of:
* {@code Cache-Control}, {@code Content-Language}, {@code Expires},
* {@code Last-Modified}, or {@code Pragma}.
* <p>By default this is not set.
*/
public void setAllowedHeaders(List<String> allowedHeaders) {
this.allowedHeaders = (allowedHeaders != null ? new ArrayList<String>(allowedHeaders) : null);
}
/**
* Return the allowed actual request headers, possibly {@code null}.
* @see #addAllowedHeader(String)
* @see #setAllowedHeaders(List)
*/
public List<String> getAllowedHeaders() {
return this.allowedHeaders;
}
/**
* Add an actual request header to allow.
*/
public void addAllowedHeader(String allowedHeader) {
if (this.allowedHeaders == null) {
this.allowedHeaders = new ArrayList<String>();
}
this.allowedHeaders.add(allowedHeader);
}
/**
* Set the list of response headers other than simple headers (i.e.
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}) that an
* actual response might have and can be exposed.
* <p>Note that {@code "*"} is not a valid exposed header value.
* <p>By default this is not set.
*/
public void setExposedHeaders(List<String> exposedHeaders) {
if (exposedHeaders != null && exposedHeaders.contains(ALL)) {
throw new IllegalArgumentException("'*' is not a valid exposed header value");
}
this.exposedHeaders = (exposedHeaders == null ? null : new ArrayList<String>(exposedHeaders));
}
/**
* Return the configured response headers to expose, possibly {@code null}.
* @see #addExposedHeader(String)
* @see #setExposedHeaders(List)
*/
public List<String> getExposedHeaders() {
return this.exposedHeaders;
}
/**
* Add a response header to expose.
* <p>Note that {@code "*"} is not a valid exposed header value.
*/
public void addExposedHeader(String exposedHeader) {
if (ALL.equals(exposedHeader)) {
throw new IllegalArgumentException("'*' is not a valid exposed header value");
}
if (this.exposedHeaders == null) {
this.exposedHeaders = new ArrayList<String>();
}
this.exposedHeaders.add(exposedHeader);
}
/**
* Whether user credentials are supported.
* <p>By default this is not set (i.e. user credentials are not supported).
*/
public void setAllowCredentials(Boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}
/**
* Return the configured {@code allowCredentials} flag, possibly {@code null}.
* @see #setAllowCredentials(Boolean)
*/
public Boolean getAllowCredentials() {
return this.allowCredentials;
}
/**
* Configure how long, in seconds, the response from a pre-flight request
* can be cached by clients.
* <p>By default this is not set.
*/
public void setMaxAge(Long maxAge) {
this.maxAge = maxAge;
}
/**
* Return the configured {@code maxAge} value, possibly {@code null}.
* @see #setMaxAge(Long)
*/
public Long getMaxAge() {
return this.maxAge;
}
/**
* Check the origin of the request against the configured allowed origins.
* @param requestOrigin the origin to check
* @return the origin to use for the response, possibly {@code null} which
* means the request origin is not allowed
*/
public String checkOrigin(String requestOrigin) {
if (!StringUtil.hasText(requestOrigin)) {
return null;
}
if (ObjectUtils.isEmpty(this.allowedOrigins)) {
return null;
}
if (this.allowedOrigins.contains(ALL)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
}
else {
return requestOrigin;
}
}
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
}
}
return null;
}
/**
* Check the HTTP request method (or the method from the
* {@code Access-Control-Request-Method} header on a pre-flight request)
* against the configured allowed methods.
* @param requestMethod the HTTP request method to check
* @return the list of HTTP methods to list in the response of a pre-flight
* request, or {@code null} if the supplied {@code requestMethod} is not allowed
*/
public List<HttpMethod> checkHttpMethod(HttpMethod requestMethod) {
if (requestMethod == null) {
return null;
}
List<String> allowedMethods =
(this.allowedMethods != null ? this.allowedMethods : new ArrayList<String>());
if (allowedMethods.contains(ALL)) {
return Collections.singletonList(requestMethod);
}
if (allowedMethods.isEmpty()) {
allowedMethods.add(HttpMethod.GET.name());
}
List<HttpMethod> result = new ArrayList<HttpMethod>(allowedMethods.size());
boolean allowed = false;
for (String method : allowedMethods) {
if (requestMethod.matches(method)) {
allowed = true;
}
HttpMethod resolved = HttpMethod.resolve(method);
if (resolved != null) {
result.add(resolved);
}
}
return (allowed ? result : null);
}
/**
* Check the supplied request headers (or the headers listed in the
* {@code Access-Control-Request-Headers} of a pre-flight request) against
* the configured allowed headers.
* @param requestHeaders the request headers to check
* @return the list of allowed headers to list in the response of a pre-flight
* request, or {@code null} if none of the supplied request headers is allowed
*/
public List<String> checkHeaders(List<String> requestHeaders) {
if (requestHeaders == null) {
return null;
}
if (requestHeaders.isEmpty()) {
return Collections.emptyList();
}
if (ObjectUtils.isEmpty(this.allowedHeaders)) {
return null;
}
boolean allowAnyHeader = this.allowedHeaders.contains(ALL);
List<String> result = new ArrayList<String>();
for (String requestHeader : requestHeaders) {
if (StringUtil.hasText(requestHeader)) {
requestHeader = requestHeader.trim();
for (String allowedHeader : this.allowedHeaders) {
if (allowAnyHeader || requestHeader.equalsIgnoreCase(allowedHeader)) {
result.add(requestHeader);
break;
}
}
}
}
return (result.isEmpty() ? null : result);
}
}