Index: test/org/apache/catalina/filters/TesterFilterChain.java
===================================================================
--- test/org/apache/catalina/filters/TesterFilterChain.java (revision 0)
+++ test/org/apache/catalina/filters/TesterFilterChain.java (revision 0)
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * A mock {@link FilterChain}.
+ */
+public class TesterFilterChain implements FilterChain {
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException {
+ // NoOp
+ }
+
+}
\ No newline at end of file
Property changes on: test/org/apache/catalina/filters/TesterFilterChain.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: test/org/apache/catalina/filters/TesterServletContext.java
===================================================================
--- test/org/apache/catalina/filters/TesterServletContext.java (revision 0)
+++ test/org/apache/catalina/filters/TesterServletContext.java (revision 0)
@@ -0,0 +1,316 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRegistration.Dynamic;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+
+/**
+ * A Mock {@link ServletContext}.
+ *
+ * @author mosoni
+ *
+ */
+public class TesterServletContext implements ServletContext {
+
+ @Override
+ public String getContextPath() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public ServletContext getContext(String uripath) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getMajorVersion() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getMinorVersion() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getMimeType(String file) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Set getResourcePaths(String path) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public URL getResource(String path) throws MalformedURLException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public InputStream getResourceAsStream(String path) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public RequestDispatcher getNamedDispatcher(String name) {
+
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Servlet getServlet(String name) throws ServletException {
+
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getServlets() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getServletNames() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void log(String msg) {
+ // NOOP
+ }
+
+ @Override
+ public void log(Exception exception, String msg) {
+ // NOOP
+ }
+
+ @Override
+ public void log(String message, Throwable throwable) {
+ // NOOP
+ }
+
+ @Override
+ public String getRealPath(String path) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getServerInfo() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Object getAttribute(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setAttribute(String name, Object object) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getServletContextName() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getEffectiveMajorVersion() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getEffectiveMinorVersion() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean setInitParameter(String name, String value) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Dynamic addServlet(String servletName, String className) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Dynamic addServlet(String servletName, Servlet servlet) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Dynamic addServlet(String servletName,
+ Class extends Servlet> servletClass) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public T createServlet(Class c)
+ throws ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public ServletRegistration getServletRegistration(String servletName) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Map getServletRegistrations() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public javax.servlet.FilterRegistration.Dynamic addFilter(
+ String filterName, String className) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public javax.servlet.FilterRegistration.Dynamic addFilter(
+ String filterName, Filter filter) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public javax.servlet.FilterRegistration.Dynamic addFilter(
+ String filterName, Class extends Filter> filterClass) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public T createFilter(Class c)
+ throws ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public FilterRegistration getFilterRegistration(String filterName) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Map getFilterRegistrations() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public SessionCookieConfig getSessionCookieConfig() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setSessionTrackingModes(
+ Set sessionTrackingModes) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Set getDefaultSessionTrackingModes() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Set getEffectiveSessionTrackingModes() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void addListener(String className) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void addListener(T t) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void addListener(Class extends EventListener> listenerClass) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public T createListener(Class c)
+ throws ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public JspConfigDescriptor getJspConfigDescriptor() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void declareRoles(String... roleNames) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getVirtualServerName() {
+ throw new RuntimeException("Not implemented");
+ }
+
+}
\ No newline at end of file
Property changes on: test/org/apache/catalina/filters/TesterServletContext.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: test/org/apache/catalina/filters/TesterHttpServletRequest.java
===================================================================
--- test/org/apache/catalina/filters/TesterHttpServletRequest.java (revision 0)
+++ test/org/apache/catalina/filters/TesterHttpServletRequest.java (revision 0)
@@ -0,0 +1,444 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
+
+/**
+ * A mock {@link HttpServletRequest}.
+ */
+public class TesterHttpServletRequest implements HttpServletRequest {
+
+ private final Map attributes = new HashMap();
+ private final Map> headers = new HashMap>();
+ private String method;
+ private String contentType;
+
+ @Override
+ public Object getAttribute(String name) {
+ return attributes.get(name);
+ }
+
+ @Override
+ public Enumeration getAttributeNames() {
+ return Collections.enumeration(attributes.keySet());
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setCharacterEncoding(String env)
+ throws UnsupportedEncodingException {
+ // NO-OP.
+ }
+
+ @Override
+ public int getContentLength() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ /**
+ * Method to set content type for test.
+ *
+ * @param contentType
+ * The type of content.
+ */
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ @Override
+ public ServletInputStream getInputStream() throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getParameter(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getParameterNames() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Map getParameterMap() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getProtocol() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getScheme() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getServerName() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getServerPort() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public BufferedReader getReader() throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getRemoteHost() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setAttribute(String name, Object o) {
+ attributes.put(name, o);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ attributes.remove(name);
+ }
+
+ @Override
+ public Locale getLocale() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getLocales() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isSecure() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public RequestDispatcher getRequestDispatcher(String path) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getRealPath(String path) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getRemotePort() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getLocalName() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getLocalAddr() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getLocalPort() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getAuthType() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Cookie[] getCookies() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public long getDateHeader(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getHeader(String name) {
+ List list = headers.get(name);
+ if (list != null) {
+ return CORSFilter.join(new HashSet(list), ",");
+ }
+ return null;
+ }
+
+ /**
+ * Method to set header name and value for test.
+ *
+ * @param name
+ * Name of header.
+ * @param value
+ * Value of header.
+ */
+ public void setHeader(String name, String value) {
+ List values = new ArrayList();
+ values.add(value);
+ headers.put(name, values);
+ }
+
+ @Override
+ public Enumeration getHeaders(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Enumeration getHeaderNames() {
+ return Collections.enumeration(headers.keySet());
+ }
+
+ @Override
+ public int getIntHeader(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getMethod() {
+ return method;
+ }
+
+ /**
+ * Method to set HTTP method type, for test.
+ *
+ * @param method
+ * The type of HTTP method.
+ */
+ public void setMethod(String method) {
+ this.method = method;
+ }
+
+ @Override
+ public String getPathInfo() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getPathTranslated() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getContextPath() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getQueryString() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getRemoteUser() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isUserInRole(String role) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Principal getUserPrincipal() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getRequestedSessionId() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getRequestURI() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public StringBuffer getRequestURL() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getServletPath() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public HttpSession getSession(boolean create) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public HttpSession getSession() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isRequestedSessionIdValid() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromCookie() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromURL() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isRequestedSessionIdFromUrl() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public AsyncContext startAsync() throws IllegalStateException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public AsyncContext startAsync(ServletRequest servletRequest,
+ ServletResponse servletResponse) throws IllegalStateException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isAsyncStarted() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean isAsyncSupported() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public AsyncContext getAsyncContext() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public DispatcherType getDispatcherType() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String changeSessionId() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean authenticate(HttpServletResponse response)
+ throws IOException, ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void login(String username, String password) throws ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void logout() throws ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Collection getParts() throws IOException, ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Part getPart(String name) throws IOException, ServletException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public T upgrade(
+ Class httpUpgradeHandlerClass) throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+}
\ No newline at end of file
Property changes on: test/org/apache/catalina/filters/TesterHttpServletRequest.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: test/org/apache/catalina/filters/TesterFilterConfigs.java
===================================================================
--- test/org/apache/catalina/filters/TesterFilterConfigs.java (revision 0)
+++ test/org/apache/catalina/filters/TesterFilterConfigs.java (revision 0)
@@ -0,0 +1,355 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.util.Enumeration;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+
+/**
+ * A collection of mock {@link FilterConfig}.
+ */
+public class TesterFilterConfigs {
+ /**
+ * A HTTPS origin.
+ */
+ public static final String HTTPS_WWW_APACHE_ORG = "https://www.apache.org";
+ /**
+ * A HTTP origin.
+ */
+ public static final String HTTP_TOMCAT_APACHE_ORG = "http://tomcat.apache.org";
+ /**
+ * A sample header to test exposed headers.
+ */
+ public static final String EXPOSED_HEADERS = "X-CUSTOM-HEADER";
+ /**
+ * Any origin
+ */
+ public static final String ANY_ORIGIN = "*";
+
+ /**
+ * A mock {@link ServletContext} object.
+ */
+ public static final TesterServletContext mockServletContext = new TesterServletContext();
+
+ /**
+ * Returns a {@link FilterConfig} object, with default configuration.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getDefaultFilterConfig() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
+ final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with support of PUT method
+ * and credentials.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getFilterConfigAnyOriginAndSupportsCredentials() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS
+ + ",PUT";
+ final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = "true";
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with support of PUT method
+ * and credentials disabled..
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getFilterConfigAnyOriginAndSupportsCredentialsDisabled() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS
+ + ",PUT";
+ final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = "false";
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with support of PUT method
+ * and any origin disabled, that is configured using specific origins.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getFilterConfigSpecificOriginAndSupportsCredentialsDisabled() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS
+ + ",PUT";
+ final String allowedOrigins = HTTP_TOMCAT_APACHE_ORG + ","
+ + HTTPS_WWW_APACHE_ORG;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = "false";
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with exposed headers.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getFilterConfigWithExposedHeaders() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
+ final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
+ final String exposedHeaders = EXPOSED_HEADERS;
+ final String supportCredentials = CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with support of PUT method,
+ * a HTTPS origin
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getSecureFilterConfig() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS
+ + ",PUT";
+ final String allowedOrigins = HTTPS_WWW_APACHE_ORG;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = "true";
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = "true";
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A {@link FilterConfig} with all null attributes, i.e. will
+ * use default config.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getNullFilterConfig() {
+ return generateFilterConfig(null, null, null, null, null, null, null,
+ null);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with support of PUT method,
+ * a HTTPS origin and a HTTP origin.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getSpecificOriginFilterConfig() {
+ final String allowedOrigins = HTTPS_WWW_APACHE_ORG + ","
+ + HTTP_TOMCAT_APACHE_ORG;
+
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS
+ + ",PUT";
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with support of PUT method,
+ * a HTTPS origin, a HTTP origin, and a negative max age header, which
+ * indicates browsers to not cache pre-flight response.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getSpecificOriginFilterConfigNegativeMaxAge() {
+ final String allowedOrigins = HTTPS_WWW_APACHE_ORG + ","
+ + HTTP_TOMCAT_APACHE_ORG;
+
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS
+ + ",PUT";
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
+ final String preflightMaxAge = "-1";
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, with an invalid pre-flight
+ * max age value. It should be a integer.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getFilterConfigInvalidMaxPreflightAge() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
+ final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
+ final String preflightMaxAge = "abc";
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A {@link FilterConfig} with all properties as empty strings.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getEmptyFilterConfig() {
+ final String allowedHttpHeaders = "";
+ final String allowedHttpMethods = "";
+ final String allowedOrigins = "";
+ final String exposedHeaders = "";
+ final String supportCredentials = "";
+ final String preflightMaxAge = "";
+ final String loggingEnabled = "";
+ final String decorateRequest = "";
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ /**
+ * A variation of default {@link FilterConfig}, that disables adding
+ * attributes to HttpServletRequest.
+ *
+ * @return A {@link FilterConfig} object.
+ */
+ public static FilterConfig getFilterConfigDecorateRequestDisabled() {
+ final String allowedHttpHeaders = CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
+ final String allowedHttpMethods = CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
+ final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
+ final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
+ final String supportCredentials = CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
+ final String preflightMaxAge = CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
+ final String loggingEnabled = CORSFilter.DEFAULT_LOGGING_ENABLED;
+ final String decorateRequest = "false";
+
+ return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
+ allowedOrigins, exposedHeaders, supportCredentials,
+ preflightMaxAge, loggingEnabled, decorateRequest);
+ }
+
+ private static FilterConfig generateFilterConfig(
+ final String allowedHttpHeaders, final String allowedHttpMethods,
+ final String allowedOrigins, final String exposedHeaders,
+ final String supportCredentials, final String preflightMaxAge,
+ final String loggingEnabled, final String decorateRequest) {
+ FilterConfig filterConfig = new FilterConfig() {
+
+ @Override
+ public String getFilterName() {
+ return "cors-filter";
+ }
+
+ @Override
+ public ServletContext getServletContext() {
+ return mockServletContext;
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ if (CORSFilter.PARAM_CORS_ALLOWED_HEADERS
+ .equalsIgnoreCase(name)) {
+ return allowedHttpHeaders;
+ } else if (CORSFilter.PARAM_CORS_ALLOWED_METHODS
+ .equalsIgnoreCase(name)) {
+ return allowedHttpMethods;
+ } else if (CORSFilter.PARAM_CORS_ALLOWED_ORIGINS
+ .equalsIgnoreCase(name)) {
+ return allowedOrigins;
+ } else if (CORSFilter.PARAM_CORS_EXPOSED_HEADERS
+ .equalsIgnoreCase(name)) {
+ return exposedHeaders;
+ } else if (CORSFilter.PARAM_CORS_SUPPORT_CREDENTIALS
+ .equalsIgnoreCase(name)) {
+ return supportCredentials;
+ } else if (CORSFilter.PARAM_CORS_PREFLIGHT_MAXAGE
+ .equalsIgnoreCase(name)) {
+ return preflightMaxAge;
+ } else if (CORSFilter.PARAM_CORS_LOGGING_ENABLED
+ .equalsIgnoreCase(name)) {
+ return loggingEnabled;
+ } else if (CORSFilter.PARAM_CORS_REQUEST_DECORATE
+ .equalsIgnoreCase(name)) {
+ return decorateRequest;
+ }
+ return null;
+ }
+
+ @Override
+ public Enumeration getInitParameterNames() {
+ return null;
+ }
+ };
+
+ return filterConfig;
+ }
+}
\ No newline at end of file
Property changes on: test/org/apache/catalina/filters/TesterFilterConfigs.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: test/org/apache/catalina/filters/TesterHttpServletResponse.java
===================================================================
--- test/org/apache/catalina/filters/TesterHttpServletResponse.java (revision 0)
+++ test/org/apache/catalina/filters/TesterHttpServletResponse.java (revision 0)
@@ -0,0 +1,238 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A mock {@link HttpServletResponse}.
+ */
+public class TesterHttpServletResponse implements HttpServletResponse {
+ List headerNames = new ArrayList();
+ List headerValues = new ArrayList();
+ PrintWriter pw;
+ int status;
+
+ @Override
+ public String getCharacterEncoding() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getContentType() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+ if (pw == null) {
+ pw = new PrintWriter(new StringWriter());
+ }
+ return pw;
+ }
+
+ @Override
+ public void setCharacterEncoding(String charset) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setContentLength(int len) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setContentType(String type) {
+ // NO-OP
+ }
+
+ @Override
+ public void setBufferSize(int size) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public int getBufferSize() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void resetBuffer() {
+ // NO-OP
+ }
+
+ @Override
+ public boolean isCommitted() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void reset() {
+ // NO-OP
+ }
+
+ @Override
+ public void setLocale(Locale loc) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Locale getLocale() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void addCookie(Cookie cookie) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public boolean containsHeader(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String encodeURL(String url) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String encodeRedirectURL(String url) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String encodeUrl(String url) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String encodeRedirectUrl(String url) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ // NO-OP
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ // NO-OP
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setDateHeader(String name, long date) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void addDateHeader(String name, long date) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public String getHeader(String name) {
+ int index = headerNames.indexOf(name);
+ if (index != -1) {
+ return headerValues.get(index);
+ }
+ return null;
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ int index = headerNames.indexOf(name);
+ if (index != -1) {
+ headerValues.set(index, value);
+ } else {
+ headerNames.add(name);
+ headerValues.add(value);
+ }
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ headerNames.add(name);
+ headerValues.add(value);
+ }
+
+ @Override
+ public void setIntHeader(String name, int value) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void addIntHeader(String name, int value) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public void setStatus(int sc) {
+ this.status = sc;
+ }
+
+ @Override
+ public int getStatus() {
+ return this.status;
+ }
+
+ @Override
+ public void setStatus(int sc, String sm) {
+ // NO-OP
+ }
+
+ @Override
+ public void setContentLengthLong(long length) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Collection getHeaders(String name) {
+ throw new RuntimeException("Not implemented");
+ }
+
+ @Override
+ public Collection getHeaderNames() {
+ throw new RuntimeException("Not implemented");
+ }
+
+}
\ No newline at end of file
Property changes on: test/org/apache/catalina/filters/TesterHttpServletResponse.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: test/org/apache/catalina/filters/TestCORSFilter.java
===================================================================
--- test/org/apache/catalina/filters/TestCORSFilter.java (revision 0)
+++ test/org/apache/catalina/filters/TestCORSFilter.java (revision 0)
@@ -0,0 +1,1562 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link CORSFilter}.
+ */
+public class TestCORSFilter {
+ private final FilterChain filterChain = new TesterFilterChain();
+
+ /**
+ * Tests if a GET request is treated as simple request.
+ *
+ * @See http://www.w3.org/TR/cors/#simple-method
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimpleGET() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("GET");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "https://www.apache.org"));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Tests if a POST request is treated as simple request.
+ *
+ * @See http://www.w3.org/TR/cors/#simple-method
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimplePOST() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setContentType("text/plain");
+ request.setMethod("POST");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "https://www.apache.org"));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Tests if a HEAD request is treated as simple request.
+ *
+ * @See http://www.w3.org/TR/cors/#simple-method
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimpleHEAD() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("HEAD");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "https://www.apache.org"));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Test the presence of specific origin in response, when '*' is not used.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimpleSpecificHeader() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("POST");
+ request.setContentType("text/plain");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Tests the prsence of the origin (and not '*') in the response, when
+ * supports credentials is enabled alongwith any origin, '*'.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimpleAnyOriginAndSupportsCredentials()
+ throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("GET");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigAnyOriginAndSupportsCredentials());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS)
+ .equals("true"));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Tests the presence of the origin (and not '*') in the response, when
+ * supports credentials is enabled alongwith any origin, '*'.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimpleAnyOriginAndSupportsCredentialsDisabled()
+ throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("GET");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigAnyOriginAndSupportsCredentialsDisabled());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.ANY_ORIGIN));
+ Assert.assertNull(response
+ .getHeader(CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Tests the presence of exposed headers in response, if configured.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterSimpleWithExposedHeaders() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("POST");
+ request.setContentType("text/plain");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter
+ .init(TesterFilterConfigs.getFilterConfigWithExposedHeaders());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "https://www.apache.org"));
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS)
+ .equals(TesterFilterConfigs.EXPOSED_HEADERS));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Checks if an OPTIONS request is processed as pre-flight.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterPreflight() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
+ "Content-Type"));
+ }
+
+ /**
+ * Checks if an OPTIONS request is processed as pre-flight where any origin
+ * is enabled.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterPreflightAnyOrigin() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
+ "Content-Type"));
+ }
+
+ /**
+ * Checks if an OPTIONS request is processed as pre-flight.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterPreflightInvalidOrigin() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://www.example.com");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertEquals(response.getStatus(),
+ HttpServletResponse.SC_FORBIDDEN);
+ }
+
+ /**
+ * Tests the case when a negative max-age header is provided. In that case
+ * the browser should not cache the response.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterPreflightNegativeMaxAge() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getSpecificOriginFilterConfigNegativeMaxAge());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertNull(response
+ .getHeader(CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
+ "Content-Type"));
+ }
+
+ /**
+ * Tests a preflight request with credentials enabled.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterPreflightWithCredentials() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSecureFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS)
+ .equals("true"));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
+ "Content-Type"));
+ }
+
+ /**
+ * Tests a preflight request, when specific origin is enabled and
+ * credentials are disabled.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterPreflightWithoutCredentialsAndSpecificOrigin()
+ throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigSpecificOriginAndSupportsCredentialsDisabled());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertNull(response
+ .getHeader(CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
+ "Content-Type"));
+ }
+
+ /**
+ * Negative test, when a CORS request arrives, with a null origin.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterNullOrigin() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+
+ request.setMethod("POST");
+ request.setContentType("text/plain");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.NOT_CORS, requestType);
+
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.FALSE));
+ }
+
+ /**
+ * Tests a CORS request, from an origin that's not allowed.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDoFilterInvalidCORSOriginNotAllowed() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN, "www.google.com");
+ request.setMethod("POST");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Negative case, when a null request and null response is used.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test(expected = ServletException.class)
+ public void testDoFilterNullRequestNullResponse() throws IOException,
+ ServletException {
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(null, null, filterChain);
+ }
+
+ /**
+ * Negative case, when a null request is used.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test(expected = ServletException.class)
+ public void testDoFilterNullRequestResponse() throws IOException,
+ ServletException {
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(null, response, filterChain);
+ }
+
+ /**
+ * Negative case, when a null response is used.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test(expected = ServletException.class)
+ public void testDoFilterRequestNullResponse() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, null, filterChain);
+ }
+
+ /**
+ * Tests filter init for defaults.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testInitDefaultFilterConfig() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("GET");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(null);
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "https://www.apache.org"));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.TRUE));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
+ CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
+ }
+
+ /**
+ * Tests filter init when an invalid filter config is provided with invalid
+ * max age.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test(expected = ServletException.class)
+ public void testInitInvalidFilterConfig() throws IOException,
+ ServletException {
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigInvalidMaxPreflightAge());
+ // If we don't get an exception at this point, then all mocked objects
+ // worked as expected.
+ }
+
+ /**
+ * Tests if a non-simple request is given to simple request handler.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNotSimple() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.handleSimpleCORS(request, response, filterChain);
+ }
+
+ /**
+ * When a non-preflight request is given to a pre-flight requets handler.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNotPreflight() throws IOException, ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("GET");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.handlePreflightCORS(request, response, filterChain);
+ }
+
+ /**
+ * Tests when a null request and a null response is provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testDecorateCORSPropertiesNullRequestNullCORSRequestType() {
+ CORSFilter.decorateCORSProperties(null, null);
+ }
+
+ /**
+ * Tests when a null request is provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testDecorateCORSPropertiesNullRequestValidCORSRequestType() {
+ CORSFilter.decorateCORSProperties(null,
+ CORSFilter.CORSRequestType.SIMPLE);
+ }
+
+ /**
+ * Tests when a null response is provided.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testDecorateCORSPropertiesValidRequestNullRequestType() {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ CORSFilter.decorateCORSProperties(request, null);
+ }
+
+ /**
+ * Test a normal non cross-origin request.
+ */
+ @Test
+ public void testDecorateCORSPropertiesCORSRequestTypeNotCORS() {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ CORSFilter.decorateCORSProperties(request,
+ CORSFilter.CORSRequestType.NOT_CORS);
+ Assert.assertTrue(request.getAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST).equals(
+ Boolean.FALSE));
+ }
+
+ /**
+ * Tests an invalid request type.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testDecorateCORSPropertiesCORSRequestTypeInvalidCORS() {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ CORSFilter.decorateCORSProperties(request,
+ CORSFilter.CORSRequestType.INVALID_CORS);
+ Assert.assertNull(request
+ .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
+ }
+
+ /**
+ * Tests a simple request type when any origin is enabled.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckSimpleRequestTypeAnyOrigin() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN, "http://www.w3.org");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.SIMPLE, requestType);
+ }
+
+ /**
+ * Happy path test, when a valid CORS Simple request arrives.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckSimpleRequestType() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.SIMPLE, requestType);
+ }
+
+ /**
+ * Happy path test, when a valid CORS Simple request arrives.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckActualRequestType() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setMethod("PUT");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.ACTUAL, requestType);
+ }
+
+ /**
+ * Happy path test, when a valid CORS Simple request arrives.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckActualRequestTypeMethodPOSTNotSimpleHeaders()
+ throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setMethod("POST");
+ request.setContentType("application/json");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.ACTUAL, requestType);
+ }
+
+ /**
+ * Happy path test, when a valid CORS Pre-flight request arrives.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckPreFlightRequestType() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Content-Type");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.PRE_FLIGHT, requestType);
+ }
+
+ /**
+ * when a valid CORS Pre-flight request arrives, with no
+ * Access-Control-Request-Method
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeNoACRM() throws ServletException,
+ IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.ACTUAL, requestType);
+ }
+
+ /**
+ * when a valid CORS Pre-flight request arrives, with empty
+ * Access-Control-Request-Method
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeEmptyACRM()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * Happy path test, when a valid CORS Pre-flight request arrives.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeNoHeaders()
+ throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.PRE_FLIGHT, requestType);
+ }
+
+ /**
+ * Section 6.2.3
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeInvalidRequestMethod()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
+ "POLITE");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Section Section 6.2.5
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeUnsupportedRequestMethod()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
+ "TRACE");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Section Section 6.2.6
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeUnsupportedRequestHeaders()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "X-ANSWER");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSecureFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Section Section 6.2.7
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeAnyOriginNoWithCredentials()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
+ "Origin");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigAnyOriginAndSupportsCredentialsDisabled());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "*"));
+ Assert.assertNull(response
+ .getHeader(CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
+ }
+
+ /**
+ * Test when a preflight request is made from an origin that's not allowed.
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeOriginNotAllowed()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN, "www.ebay.com");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSecureFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Happy path test, when a valid CORS Pre-flight request arrives.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckPreFlightRequestTypeEmptyHeaders()
+ throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
+ request.setHeader(
+ CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "");
+ request.setMethod("OPTIONS");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.PRE_FLIGHT, requestType);
+ }
+
+ /**
+ * Negative test, when a CORS request arrives, with an empty origin.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckNotCORSRequestTypeEmptyOrigin()
+ throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN, "");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * Tests for failure, when a different domain is used, that's not in the
+ * allowed list of origins.
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckInvalidOrigin() throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN, "www.example.com");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Tests for failure, when a different sub-domain is used, that's not in the
+ * allowed list of origins.
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckInvalidOriginNotAllowedSubdomain()
+ throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://commons.apache.org");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * PUT is not an allowed request method.
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckInvalidRequestMethod() throws ServletException,
+ IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://tomcat.apache.org");
+ request.setMethod("PUT");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * When requestMethod is null
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckNullRequestMethod() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://tomcat.apache.org");
+ request.setMethod(null);
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * "http://tomcat.apache.org" is an allowed origin and
+ * "https://tomcat.apache.org" is not, because scheme doesn't match
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckForSchemeVariance() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "https://tomcat.apache.org");
+ request.setMethod("POST");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * "http://tomcat.apache.org" is an allowed origin and
+ * "http://tomcat.apache.org:8080" is not, because ports doesn't match
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ @Test
+ public void testCheckForPortVariance() throws ServletException, IOException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://tomcat.apache.org:8080");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
+ corsFilter.doFilter(request, response, filterChain);
+ Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
+ response.getStatus());
+ }
+
+ /**
+ * Tests for failure, when an invalid {@link HttpServletRequest} is
+ * encountered.
+ *
+ * @throws ServletException
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testCheckRequestTypeNull() throws ServletException {
+ HttpServletRequest request = null;
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.checkRequestType(request);
+ }
+
+ /**
+ * Tests a normal join of multiple elements, with comma separator.
+ */
+ @Test
+ public void testJoin() {
+ Set elements = new LinkedHashSet();
+ String separator = ",";
+ elements.add("world");
+ elements.add("peace");
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue("world,peace".equals(join));
+ }
+
+ /**
+ * Tests join with only 1 element.
+ */
+ @Test
+ public void testJoinSingleElement() {
+ Set elements = new LinkedHashSet();
+ String separator = ",";
+ elements.add("world");
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue("world".equals(join));
+ }
+
+ /**
+ * Tests join when a null separator is provided, the default comma is used.
+ */
+ @Test
+ public void testJoinSepNull() {
+ Set elements = new LinkedHashSet();
+ String separator = null;
+ elements.add("world");
+ elements.add("peace");
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue("world,peace".equals(join));
+ }
+
+ /**
+ * Tests join when elements collection is null.
+ */
+ @Test
+ public void testJoinElementsNull() {
+ Set elements = null;
+ String separator = ",";
+ String join = CORSFilter.join(elements, separator);
+
+ Assert.assertNull(join);
+ }
+
+ /**
+ * Tests join when atleast one null elements is there.
+ */
+ @Test
+ public void testJoinOneNullElement() {
+ Set elements = new LinkedHashSet();
+ String separator = ",";
+ elements.add(null);
+ elements.add("peace");
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue(",peace".equals(join));
+ }
+
+ /**
+ * Tests join when null elements are there.
+ */
+ @Test
+ public void testJoinAllNullElements() {
+ Set elements = new LinkedHashSet();
+ String separator = ",";
+ elements.add(null);
+ elements.add(null);
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue("".equals(join));
+ }
+
+ /**
+ * Tests join, for empty strings.
+ */
+ @Test
+ public void testJoinAllEmptyElements() {
+ Set elements = new LinkedHashSet();
+ String separator = ",";
+ elements.add("");
+ elements.add("");
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue("".equals(join));
+ }
+
+ /**
+ * Tests join, with a pipe separator.
+ */
+ @Test
+ public void testJoinPipeSeparator() {
+ Set elements = new LinkedHashSet();
+ String separator = "|";
+ elements.add("world");
+ elements.add("peace");
+ String join = CORSFilter.join(elements, separator);
+ Assert.assertTrue("world|peace".equals(join));
+ }
+
+ /**
+ * Tests default filter config.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testWithFilterConfig() throws ServletException {
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6);
+ Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4);
+ Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
+ Assert.assertTrue(corsFilter.isAnyOriginAllowed());
+ Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
+ Assert.assertTrue(corsFilter.isSupportsCredentials());
+ Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800);
+ Assert.assertTrue(!corsFilter.isLoggingEnabled());
+ }
+
+ /**
+ * Tests an invalid non-number max age.
+ *
+ * @throws ServletException
+ */
+ @Test(expected = ServletException.class)
+ public void testWithFilterConfigInvalidPreflightAge()
+ throws ServletException {
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigInvalidMaxPreflightAge());
+ }
+
+ /**
+ * Tests empty config object.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testWithStringParserEmpty() throws ServletException {
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getEmptyFilterConfig());
+ Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 0);
+ Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 0);
+ Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
+ Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
+ Assert.assertFalse(corsFilter.isSupportsCredentials());
+ Assert.assertTrue(corsFilter.getPreflightMaxAge() == 0);
+ Assert.assertTrue(!corsFilter.isLoggingEnabled());
+ }
+
+ /**
+ * If an init param is null, it's default value will be used.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testWithStringParserNull() throws ServletException {
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getNullFilterConfig());
+ Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6);
+ Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4);
+ Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
+ Assert.assertTrue(corsFilter.isAnyOriginAllowed());
+ Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
+ Assert.assertTrue(corsFilter.isSupportsCredentials());
+ Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800);
+ Assert.assertTrue(!corsFilter.isLoggingEnabled());
+ }
+
+ /**
+ * A valid origin.
+ */
+ @Test
+ public void testValidOrigin() {
+ Assert.assertTrue(CORSFilter.isValidOrigin("http://www.w3.org"));
+ }
+
+ /**
+ * Invalid origin, \r\n
+ */
+ @Test
+ public void testInValidOriginCRLF() {
+ Assert.assertFalse(CORSFilter.isValidOrigin("http://www.w3.org\r\n"));
+ }
+
+ /**
+ * Invalid origin, encoded chars
+ */
+ @Test
+ public void testInValidOriginEncodedCRLF1() {
+ Assert.assertFalse(CORSFilter.isValidOrigin("http://www.w3.org%0d%0a"));
+ }
+
+ /**
+ * Invalid origin, encoded chars in caps.
+ */
+ @Test
+ public void testInValidOriginEncodedCRLF2() {
+ Assert.assertFalse(CORSFilter.isValidOrigin("http://www.w3.org%0D%0A"));
+ }
+
+ /**
+ * Invalid origin, double encoded chars.
+ */
+ @Test
+ public void testInValidOriginEncodedCRLF3() {
+ Assert.assertFalse(CORSFilter
+ .isValidOrigin("http://www.w3.org%0%0d%0ad%0%0d%0aa"));
+ }
+
+ /**
+ * Test for CRLF, an invalid request. \r\n characters.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckInvalidCRLF1() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://www.w3.org\r\n");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * Test for CRLF, an invalid request.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckInvalidCRLF2() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://www.w3.org\r");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * Test for CRLF, an invalid request. encoded chars
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckInvalidCRLF3() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://www.w3.org%0d%0a");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * Test for CRLF, an invalid request. encoded chars in caps.
+ *
+ * @throws ServletException
+ */
+ @Test
+ public void testCheckInvalidCRLF4() throws ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ "http://www.w3.org%0D%0A");
+ request.setMethod("GET");
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
+ CORSFilter.CORSRequestType requestType = corsFilter
+ .checkRequestType(request);
+ Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
+ requestType);
+ }
+
+ /**
+ * Request should not be decorated.
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ @Test
+ public void testDecorateRequestDisabled() throws IOException,
+ ServletException {
+ TesterHttpServletRequest request = new TesterHttpServletRequest();
+ request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
+ TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
+ request.setMethod("GET");
+ TesterHttpServletResponse response = new TesterHttpServletResponse();
+
+ CORSFilter corsFilter = new CORSFilter();
+ corsFilter.init(TesterFilterConfigs
+ .getFilterConfigDecorateRequestDisabled());
+ corsFilter.doFilter(request, response, filterChain);
+
+ Assert.assertTrue(response.getHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
+ "https://www.apache.org"));
+ Assert.assertNull(request
+ .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
+ Assert.assertNull(request
+ .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN));
+ Assert.assertNull(request
+ .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS));
+ Assert.assertNull(request
+ .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE));
+ }
+
+ /**
+ * Not sure how to test doing nothing. Sigh!
+ */
+ @Test
+ public void testDestroy() {
+ // Nothing to test.
+ // NO-OP
+ }
+}
\ No newline at end of file
Property changes on: test/org/apache/catalina/filters/TestCORSFilter.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: java/org/apache/catalina/filters/CORSFilter.java
===================================================================
--- java/org/apache/catalina/filters/CORSFilter.java (revision 0)
+++ java/org/apache/catalina/filters/CORSFilter.java (revision 0)
@@ -0,0 +1,1155 @@
+/**
+ * Copyright 2012-2013 eBay Software Foundation, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.catalina.filters;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+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;
+
+/**
+ *
+ * A {@link Filter} that enable client-side cross-origin requests by
+ * implementing W3C's CORS (Cross-Origin Resource
+ * Sharing) specification for resources. Each {@link HttpServletRequest}
+ * request is inspected as per specification, and appropriate response headers
+ * are added to {@link HttpServletResponse}.
+ *
+ *
+ *
+ * By default, it also sets following request attributes, that helps to
+ * determine nature of request downstream.
+ *
+ *
cors.isCorsRequest: Flag to determine if request is a CORS
+ * request. Set to true if CORS request; false
+ * otherwise.
+ *
cors.request.origin: The Origin URL, i.e. the URL of the page from
+ * where the request is originated.
+ *
+ * cors.request.type: Type of request. Possible values:
+ *
+ *
SIMPLE: A request which is not preceded by a pre-flight request.
+ *
ACTUAL: A request which is preceded by a pre-flight request.
+ *
PRE_FLIGHT: A pre-flight request.
+ *
NOT_CORS: A normal same-origin request.
+ *
INVALID_CORS: A cross-origin request, which is invalid.
+ *
+ *
+ *
cors.request.headers: Request headers sent as
+ * 'Access-Control-Request-Headers' header, for pre-flight request.
+ *
+ *
+ *
+ * @author Mohit Soni
+ * @see CORS specification
+ *
+ */
+public final class CORSFilter implements Filter {
+ // ----------------------------------------------------- Instance variables
+ /**
+ * Holds filter configuration.
+ */
+ private FilterConfig filterConfig;
+
+ /**
+ * A {@link Collection} of origins consisting of zero or more origins that
+ * are allowed access to the resource.
+ */
+ private final Collection allowedOrigins;
+
+ /**
+ * Determines if any origin is allowed to make request.
+ */
+ private boolean anyOriginAllowed;
+
+ /**
+ * A {@link Collection} of methods consisting of zero or more methods that
+ * are supported by the resource.
+ */
+ private final Collection allowedHttpMethods;
+
+ /**
+ * A {@link Collection} of headers consisting of zero or more header field
+ * names that are supported by the resource.
+ */
+ private final Collection allowedHttpHeaders;
+
+ /**
+ * A {@link Collection} of exposed headers consisting of zero or more header
+ * field names of headers other than the simple response headers that the
+ * resource might use and can be exposed.
+ */
+ private final Collection exposedHeaders;
+
+ /**
+ * A supports credentials flag that indicates whether the resource supports
+ * user credentials in the request. It is true when the resource does and
+ * false otherwise.
+ */
+ private boolean supportsCredentials;
+
+ /**
+ * Indicates (in seconds) how long the results of a pre-flight request can
+ * be cached in a pre-flight result cache.
+ */
+ private long preflightMaxAge;
+
+ /**
+ * Controls access log logging.
+ */
+ private boolean loggingEnabled;
+
+ /**
+ * Determines if the request should be decorated or not.
+ */
+ private boolean decorateRequest;
+
+ // --------------------------------------------------------- Constructor(s)
+ /**
+ * Initializes configuration {@link Collection} objects.
+ */
+ public CORSFilter() {
+ this.allowedOrigins = new HashSet();
+ this.allowedHttpMethods = new HashSet();
+ this.allowedHttpHeaders = new HashSet();
+ this.exposedHeaders = new HashSet();
+ }
+
+ // --------------------------------------------------------- Public methods
+ @Override
+ public void doFilter(final ServletRequest servletRequest,
+ final ServletResponse servletResponse, final FilterChain filterChain)
+ throws IOException, ServletException {
+ if (!(servletRequest instanceof HttpServletRequest)
+ || !(servletResponse instanceof HttpServletResponse)) {
+ String message = "CORS doesn't support non-HTTP request or response.";
+ throw new ServletException(message);
+ }
+
+ // Safe to downcast at this point.
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+ // Determines the CORS request type.
+ CORSFilter.CORSRequestType requestType = checkRequestType(request);
+
+ // Adds CORS specific attributes to request.
+ if (decorateRequest) {
+ CORSFilter.decorateCORSProperties(request, requestType);
+ }
+ switch (requestType) {
+ case SIMPLE:
+ // Handles a Simple CORS request.
+ this.handleSimpleCORS(request, response, filterChain);
+ break;
+ case ACTUAL:
+ // Handles an Actual CORS request.
+ this.handleSimpleCORS(request, response, filterChain);
+ break;
+ case PRE_FLIGHT:
+ // Handles a Pre-flight CORS request.
+ this.handlePreflightCORS(request, response, filterChain);
+ break;
+ case NOT_CORS:
+ // Handles a Normal request that is not a cross-origin request.
+ this.handleNonCORS(request, response, filterChain);
+ break;
+ default:
+ // Handles a CORS request that violates specification.
+ this.handleInvalidCORS(request, response, filterChain);
+ break;
+ }
+ }
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ // Initialize defaults
+ parseAndStore(DEFAULT_ALLOWED_ORIGINS, DEFAULT_ALLOWED_HTTP_METHODS,
+ DEFAULT_ALLOWED_HTTP_HEADERS, DEFAULT_EXPOSED_HEADERS,
+ DEFAULT_SUPPORTS_CREDENTIALS, DEFAULT_PREFLIGHT_MAXAGE,
+ DEFAULT_LOGGING_ENABLED, DEFAULT_DECORATE_REQUEST);
+
+ this.filterConfig = filterConfig;
+ this.loggingEnabled = false;
+
+ if (filterConfig != null) {
+ String configAllowedOrigins = filterConfig
+ .getInitParameter(PARAM_CORS_ALLOWED_ORIGINS);
+ String configAllowedHttpMethods = filterConfig
+ .getInitParameter(PARAM_CORS_ALLOWED_METHODS);
+ String configAllowedHttpHeaders = filterConfig
+ .getInitParameter(PARAM_CORS_ALLOWED_HEADERS);
+ String configExposedHeaders = filterConfig
+ .getInitParameter(PARAM_CORS_EXPOSED_HEADERS);
+ String configSupportsCredentials = filterConfig
+ .getInitParameter(PARAM_CORS_SUPPORT_CREDENTIALS);
+ String configPreflightMaxAge = filterConfig
+ .getInitParameter(PARAM_CORS_PREFLIGHT_MAXAGE);
+ String configLoggingEnabled = filterConfig
+ .getInitParameter(PARAM_CORS_LOGGING_ENABLED);
+ String configDecorateRequest = filterConfig
+ .getInitParameter(PARAM_CORS_REQUEST_DECORATE);
+
+ parseAndStore(configAllowedOrigins, configAllowedHttpMethods,
+ configAllowedHttpHeaders, configExposedHeaders,
+ configSupportsCredentials, configPreflightMaxAge,
+ configLoggingEnabled, configDecorateRequest);
+ }
+ }
+
+ // --------------------------------------------------------------- Handlers
+ /**
+ * Handles a CORS request of type {@link CORSRequestType}.SIMPLE.
+ *
+ * @param request
+ * The {@link HttpServletRequest} object.
+ * @param response
+ * The {@link HttpServletResponse} object.
+ * @param filterChain
+ * The {@link FilterChain} object.
+ * @throws IOException
+ * @throws ServletException
+ * @see Simple
+ * Cross-Origin Request, Actual Request, and Redirects
+ */
+ public void handleSimpleCORS(final HttpServletRequest request,
+ final HttpServletResponse response, final FilterChain filterChain)
+ throws IOException, ServletException {
+ CORSFilter.CORSRequestType requestType = checkRequestType(request);
+ if (!(requestType == CORSFilter.CORSRequestType.SIMPLE || requestType == CORSFilter.CORSRequestType.ACTUAL)) {
+ String message = "Expects a HttpServletRequest object of type "
+ + CORSFilter.CORSRequestType.SIMPLE + " or "
+ + CORSFilter.CORSRequestType.ACTUAL;
+ throw new IllegalArgumentException(message);
+ }
+
+ final String origin = request
+ .getHeader(CORSFilter.REQUEST_HEADER_ORIGIN);
+ final String method = request.getMethod();
+
+ // Section 6.1.2
+ if (!isOriginAllowed(origin)) {
+ handleInvalidCORS(request, response, filterChain);
+ return;
+ }
+
+ if (!allowedHttpMethods.contains(method)) {
+ handleInvalidCORS(request, response, filterChain);
+ return;
+ }
+
+ // Section 6.1.3
+ // Add a single Access-Control-Allow-Origin header.
+ if (anyOriginAllowed && !supportsCredentials) {
+ // If resource doesn't support credentials and if any origin is
+ // allowed
+ // to make CORS request, return header with '*'.
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*");
+ } else {
+ // If the resource supports credentials add a single
+ // Access-Control-Allow-Origin header, with the value of the Origin
+ // header as value.
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+ origin);
+ }
+ // Section 6.1.3
+ // If the resource supports credentials, add a single
+ // Access-Control-Allow-Credentials header with the case-sensitive
+ // string "true" as value.
+ if (supportsCredentials) {
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
+ "true");
+ }
+
+ // Section 6.1.4
+ // If the list of exposed headers is not empty add one or more
+ // Access-Control-Expose-Headers headers, with as values the header
+ // field names given in the list of exposed headers.
+ if ((exposedHeaders != null) && (exposedHeaders.size() > 0)) {
+ String exposedHeadersString = join(exposedHeaders, ",");
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
+ exposedHeadersString);
+ }
+
+ // Forward the request down the filter chain.
+ filterChain.doFilter(request, response);
+ }
+
+ /**
+ * Handles CORS pre-flight request.
+ *
+ * @param request
+ * The {@link HttpServletRequest} object.
+ * @param response
+ * The {@link HttpServletResponse} object.
+ * @param filterChain
+ * The {@link FilterChain} object.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void handlePreflightCORS(final HttpServletRequest request,
+ final HttpServletResponse response, final FilterChain filterChain)
+ throws IOException, ServletException {
+ CORSRequestType requestType = checkRequestType(request);
+ if (requestType != CORSRequestType.PRE_FLIGHT) {
+ throw new IllegalArgumentException(
+ "Expects a HttpServletRequest object of type "
+ + CORSRequestType.PRE_FLIGHT.name().toLowerCase());
+ }
+
+ final String origin = request
+ .getHeader(CORSFilter.REQUEST_HEADER_ORIGIN);
+
+ // Section 6.2.2
+ if (!isOriginAllowed(origin)) {
+ handleInvalidCORS(request, response, filterChain);
+ return;
+ }
+
+ // Section 6.2.3
+ String accessControlRequestMethod = request
+ .getHeader(CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
+ if (accessControlRequestMethod == null
+ || (!HTTP_METHODS.contains(accessControlRequestMethod.trim()))) {
+ handleInvalidCORS(request, response, filterChain);
+ return;
+ } else {
+ accessControlRequestMethod = accessControlRequestMethod.trim();
+ }
+
+ // Section 6.2.4
+ String accessControlRequestHeadersHeader = request
+ .getHeader(CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
+ List accessControlRequestHeaders = new LinkedList();
+ if (accessControlRequestHeadersHeader != null
+ && !accessControlRequestHeadersHeader.trim().isEmpty()) {
+ String[] headers = accessControlRequestHeadersHeader.trim().split(
+ ",");
+ for (String header : headers) {
+ accessControlRequestHeaders.add(header.trim().toLowerCase());
+ }
+ }
+
+ // Section 6.2.5
+ if (!allowedHttpMethods.contains(accessControlRequestMethod)) {
+ handleInvalidCORS(request, response, filterChain);
+ return;
+ }
+
+ // Section 6.2.6
+ if (!accessControlRequestHeaders.isEmpty()) {
+ for (String header : accessControlRequestHeaders) {
+ if (!allowedHttpHeaders.contains(header)) {
+ handleInvalidCORS(request, response, filterChain);
+ return;
+ }
+ }
+ }
+
+ // Section 6.2.7
+ if (supportsCredentials) {
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+ origin);
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
+ "true");
+ } else {
+ if (anyOriginAllowed) {
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+ "*");
+ } else {
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+ origin);
+ }
+ }
+
+ // Section 6.2.8
+ if (preflightMaxAge > 0) {
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE,
+ String.valueOf(preflightMaxAge));
+ }
+
+ // Section 6.2.9
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
+ accessControlRequestMethod);
+
+ // Section 6.2.10
+ if ((allowedHttpHeaders != null) && (!allowedHttpHeaders.isEmpty())) {
+ response.addHeader(
+ CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
+ join(allowedHttpHeaders, ","));
+ }
+
+ // Do not forward the request down the filter chain.
+ }
+
+ /**
+ * Handles a request, that's not a CORS request, but is a valid request i.e.
+ * it is not a cross-origin request. This implementation, just forwards the
+ * request down the filter chain.
+ *
+ * @param request
+ * The {@link HttpServletRequest} object.
+ * @param response
+ * The {@link HttpServletResponse} object.
+ * @param filterChain
+ * The {@link FilterChain} object.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void handleNonCORS(final HttpServletRequest request,
+ final HttpServletResponse response, final FilterChain filterChain)
+ throws IOException, ServletException {
+ // Let request pass.
+ filterChain.doFilter(request, response);
+ }
+
+ /**
+ * Handles a CORS request that violates specification.
+ *
+ * @param request
+ * The {@link HttpServletRequest} object.
+ * @param response
+ * The {@link HttpServletResponse} object.
+ * @param filterChain
+ * The {@link FilterChain} object.
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void handleInvalidCORS(final HttpServletRequest request,
+ final HttpServletResponse response, final FilterChain filterChain) {
+ String origin = request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN);
+ String method = request.getMethod();
+ String accessControlRequestHeaders = request
+ .getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
+
+ String message = "Invalid CORS request; Origin=" + origin + ";Method="
+ + method;
+ if (accessControlRequestHeaders != null) {
+ message = message + ";Access-Control-Request-Headers="
+ + accessControlRequestHeaders;
+ }
+ response.setContentType("text/plain");
+ response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ response.resetBuffer();
+
+ log(message);
+ }
+
+ @Override
+ public void destroy() {
+ // NOOP
+ }
+
+ // -------------------------------------------------------- Utility methods
+ /**
+ * Decorates the {@link HttpServletRequest}, with CORS attributes.
+ *
+ *
cors.isCorsRequest: Flag to determine if request is a CORS
+ * request. Set to true if CORS request; false
+ * otherwise.
+ *
cors.request.origin: The Origin URL.
+ *
cors.request.type: Type of request. Values:
+ * simple or preflight or not_cors or
+ * invalid_cors
+ *
cors.request.headers: Request headers sent as
+ * 'Access-Control-Request-Headers' header, for pre-flight request.
+ *
+ *
+ * @param request
+ * The {@link HttpServletRequest} object.
+ * @param corsRequestType
+ * The {@link CORSRequestType} object.
+ */
+ public static void decorateCORSProperties(final HttpServletRequest request,
+ final CORSRequestType corsRequestType) {
+ if (request == null) {
+ throw new IllegalArgumentException(
+ "HttpServletRequest object is null");
+ }
+
+ if (corsRequestType == null) {
+ throw new IllegalArgumentException("CORSRequestType object is null");
+ }
+
+ switch (corsRequestType) {
+ case SIMPLE:
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+ Boolean.TRUE);
+ request.setAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
+ request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN));
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
+ corsRequestType.name().toLowerCase());
+ break;
+ case ACTUAL:
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+ Boolean.TRUE);
+ request.setAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
+ request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN));
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
+ corsRequestType.name().toLowerCase());
+ break;
+ case PRE_FLIGHT:
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+ Boolean.TRUE);
+ request.setAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
+ request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN));
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
+ corsRequestType.name().toLowerCase());
+ String headers = request
+ .getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
+ if (headers == null) {
+ headers = "";
+ }
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS, headers);
+ break;
+ case NOT_CORS:
+ request.setAttribute(
+ CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+ Boolean.FALSE);
+ break;
+ default:
+ // Don't set any attributes
+ break;
+ }
+ }
+
+ /**
+ * Joins elements of {@link Set} into a string, where each element is
+ * separated by the provided separator.
+ *
+ * @param elements
+ * The {@link Set} containing elements to join together.
+ * @param joinSeparator
+ * The character to be used for separating elements.
+ * @return The joined {@link String}; null if elements
+ * {@link Set} is null.
+ */
+ public static String join(final Collection elements,
+ final String joinSeparator) {
+ String separator = ",";
+ if (elements == null) {
+ return null;
+ }
+ if (joinSeparator != null) {
+ separator = joinSeparator;
+ }
+ StringBuilder buffer = new StringBuilder();
+ boolean isFirst = true;
+ for (String element : elements) {
+ if (!isFirst) {
+ buffer.append(separator);
+ } else {
+ isFirst = false;
+ }
+
+ if (element != null) {
+ buffer.append(element);
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Determines the request type.
+ *
+ * @param request
+ * @return {@link CORSRequestType} The type of request.
+ */
+ public CORSRequestType checkRequestType(final HttpServletRequest request) {
+ CORSRequestType requestType = CORSRequestType.INVALID_CORS;
+ if (request == null) {
+ throw new IllegalArgumentException(
+ "HttpServletRequest object is null");
+ }
+ String originHeader = request.getHeader(REQUEST_HEADER_ORIGIN);
+ // Section 6.1.1 and Section 6.2.1
+ if (originHeader != null) {
+ if (originHeader.isEmpty()) {
+ requestType = CORSRequestType.INVALID_CORS;
+ } else if (!isValidOrigin(originHeader)) {
+ requestType = CORSRequestType.INVALID_CORS;
+ } else {
+ String method = request.getMethod();
+ if (method != null && HTTP_METHODS.contains(method)) {
+ if ("OPTIONS".equals(method)) {
+ String accessControlRequestMethodHeader = request
+ .getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
+ if (accessControlRequestMethodHeader != null
+ && !accessControlRequestMethodHeader.isEmpty()) {
+ requestType = CORSRequestType.PRE_FLIGHT;
+ } else if (accessControlRequestMethodHeader != null
+ && accessControlRequestMethodHeader.isEmpty()) {
+ requestType = CORSRequestType.INVALID_CORS;
+ } else {
+ requestType = CORSRequestType.ACTUAL;
+ }
+ } else if ("GET".equals(method) || "HEAD".equals(method)) {
+ requestType = CORSRequestType.SIMPLE;
+ } else if ("POST".equals(method)) {
+ String contentType = request.getContentType();
+ if (contentType != null) {
+ contentType = contentType.toLowerCase().trim();
+ if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES
+ .contains(contentType)) {
+ requestType = CORSRequestType.SIMPLE;
+ } else {
+ requestType = CORSRequestType.ACTUAL;
+ }
+ }
+ } else if (COMPLEX_HTTP_METHODS.contains(method)) {
+ requestType = CORSRequestType.ACTUAL;
+ }
+ }
+ }
+ } else {
+ requestType = CORSRequestType.NOT_CORS;
+ }
+
+ return requestType;
+ }
+
+ /**
+ * Checks if the Origin is allowed to make a CORS request.
+ *
+ * @param origin
+ * The Origin.
+ * @return true if origin is allowed; false
+ * otherwise.
+ */
+ private boolean isOriginAllowed(final String origin) {
+ if (anyOriginAllowed) {
+ return true;
+ }
+
+ // If 'Origin' header is a case-sensitive match of any of allowed
+ // origins, then return true, else return false.
+ return allowedOrigins.contains(origin);
+ }
+
+ private void log(String message) {
+ if (loggingEnabled) {
+ filterConfig.getServletContext().log(message);
+ }
+ }
+
+ /**
+ * Parses each param-value and populates configuration variables. If a param
+ * is provided, it overrides the default.
+ *
+ * @param allowedOrigins
+ * A {@link String} of comma separated origins.
+ * @param allowedHttpMethods
+ * A {@link String} of comma separated HTTP methods.
+ * @param allowedHttpHeaders
+ * A {@link String} of comma separated HTTP headers.
+ * @param exposedHeaders
+ * A {@link String} of comma separated headers that needs to be
+ * exposed.
+ * @param supportsCredentials
+ * "true" if support credentials needs to be enabled.
+ * @param preflightMaxAge
+ * The amount of seconds the user agent is allowed to cache the
+ * result of the pre-flight request.
+ * @param loggingEnabled
+ * Flag to control logging to access log.
+ * @throws ServletException
+ */
+ private void parseAndStore(final String allowedOrigins,
+ final String allowedHttpMethods, final String allowedHttpHeaders,
+ final String exposedHeaders, final String supportsCredentials,
+ final String preflightMaxAge, final String loggingEnabled,
+ final String decorateRequest) throws ServletException {
+ if (allowedOrigins != null) {
+ if (allowedOrigins.trim().equals("*")) {
+ this.anyOriginAllowed = true;
+ } else {
+ this.anyOriginAllowed = false;
+ Set setAllowedOrigins = parseStringToSet(allowedOrigins);
+ this.allowedOrigins.clear();
+ this.allowedOrigins.addAll(setAllowedOrigins);
+ }
+ }
+
+ if (allowedHttpMethods != null) {
+ Set setAllowedHttpMethods = parseStringToSet(allowedHttpMethods);
+ this.allowedHttpMethods.clear();
+ this.allowedHttpMethods.addAll(setAllowedHttpMethods);
+ }
+
+ if (allowedHttpHeaders != null) {
+ Set setAllowedHttpHeaders = parseStringToSet(allowedHttpHeaders);
+ Set lowerCaseHeaders = new HashSet();
+ for (String header : setAllowedHttpHeaders) {
+ String lowerCase = header.toLowerCase();
+ lowerCaseHeaders.add(lowerCase);
+ }
+ this.allowedHttpHeaders.clear();
+ this.allowedHttpHeaders.addAll(lowerCaseHeaders);
+ }
+
+ if (exposedHeaders != null) {
+ Set setExposedHeaders = parseStringToSet(exposedHeaders);
+ this.exposedHeaders.clear();
+ this.exposedHeaders.addAll(setExposedHeaders);
+ }
+
+ if (supportsCredentials != null) {
+ // For any value other then 'true' this will be false.
+ this.supportsCredentials = Boolean
+ .parseBoolean(supportsCredentials);
+ }
+
+ if (preflightMaxAge != null) {
+ try {
+ if (!preflightMaxAge.isEmpty()) {
+ this.preflightMaxAge = Long.parseLong(preflightMaxAge);
+ } else {
+ this.preflightMaxAge = 0L;
+ }
+ } catch (NumberFormatException e) {
+ throw new ServletException("Unable to parse preflightMaxAge", e);
+ }
+ }
+
+ if (loggingEnabled != null) {
+ // For any value other then 'true' this will be false.
+ this.loggingEnabled = Boolean.parseBoolean(loggingEnabled);
+ }
+
+ if (decorateRequest != null) {
+ // For any value other then 'true' this will be false.
+ this.decorateRequest = Boolean.parseBoolean(decorateRequest);
+ }
+ }
+
+ /**
+ * Takes a comma separated list and returns a Set.
+ *
+ * @param data
+ * A comma separated list of strings.
+ * @return Set
+ */
+ private Set parseStringToSet(final String data) {
+ String[] splits;
+
+ if (data != null && data.length() > 0) {
+ splits = data.split(",");
+ } else {
+ splits = new String[] {};
+ }
+
+ Set set = new HashSet();
+ if (splits.length > 0) {
+ for (String split : splits) {
+ set.add(split.trim());
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Checks if a given origin is valid or not. Criteria:
+ *
+ *
If an encoded character is present in origin, it's not valid.
+ *
Origin should be a valid {@link URI}
+ *
+ *
+ * @param origin
+ * @see RFC952
+ * @return true, if origin is a valid URI; false, if either it contains CRLF
+ * characters, or is not a valid URI or is missing scheme.
+ */
+ public static boolean isValidOrigin(String origin) {
+ // Checks for encoded characters. Helps prevent CRLF injection.
+ if (origin.contains("%")) {
+ return false;
+ }
+
+ URI originURI;
+
+ try {
+ originURI = new URI(origin);
+ } catch (URISyntaxException e) {
+ return false;
+ }
+ // If scheme for URI is null, return false. Return true otherwise.
+ return originURI.getScheme() != null;
+
+ }
+
+ // -------------------------------------------------------------- Accessors
+ /**
+ * Determines if logging is enabled or not.
+ *
+ * @return true if it's enabled; false otherwise.
+ */
+ public boolean isLoggingEnabled() {
+ return loggingEnabled;
+ }
+
+ /**
+ * Determines if any origin is allowed to make CORS request.
+ *
+ * @return true if it's enabled; false otherwise.
+ */
+ public boolean isAnyOriginAllowed() {
+ return anyOriginAllowed;
+ }
+
+ /**
+ * Returns a {@link Collection} of headers that should be exposed by
+ * browser.
+ *
+ * @return A {@link Collection} of {@link String}.
+ */
+ public Collection getExposedHeaders() {
+ return exposedHeaders;
+ }
+
+ /**
+ * Determines is supports credentials is enabled
+ *
+ * @return true if supported, false otherwise.
+ */
+ public boolean isSupportsCredentials() {
+ return supportsCredentials;
+ }
+
+ /**
+ * Returns the preflight response cache time in seconds.
+ *
+ * @return Time to cache in seconds.
+ */
+ public long getPreflightMaxAge() {
+ return preflightMaxAge;
+ }
+
+ /**
+ * Returns the {@link Set} of allowed origins that are allowed to make
+ * requests.
+ *
+ * @return {@link Set} of {@link String}
+ */
+ public Collection getAllowedOrigins() {
+ return allowedOrigins;
+ }
+
+ /**
+ * Returns a {@link Set} of HTTP methods that are allowed to make requests.
+ *
+ * @return {@link Set} of {@link String}
+ */
+ public Collection getAllowedHttpMethods() {
+ return allowedHttpMethods;
+ }
+
+ /**
+ * Returns a {@link Set} of headers support by resource.
+ *
+ * @return {@link Set} of {@link String}
+ */
+ public Collection getAllowedHttpHeaders() {
+ return allowedHttpHeaders;
+ }
+
+ // -------------------------------------------------- CORS Response Headers
+ /**
+ * The Access-Control-Allow-Origin header indicates whether a resource can
+ * be shared based by returning the value of the Origin request header in
+ * the response.
+ */
+ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
+
+ /**
+ * The Access-Control-Allow-Credentials header indicates whether the
+ * response to request can be exposed when the omit credentials flag is
+ * unset. When part of the response to a preflight request it indicates that
+ * the actual request can include user credentials.
+ */
+ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
+
+ /**
+ * The Access-Control-Expose-Headers header indicates which headers are safe
+ * to expose to the API of a CORS API specification
+ */
+ public static final String RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
+
+ /**
+ * The Access-Control-Max-Age header indicates how long the results of a
+ * preflight request can be cached in a preflight result cache.
+ */
+ public static final String RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
+
+ /**
+ * The Access-Control-Allow-Methods header indicates, as part of the
+ * response to a preflight request, which methods can be used during the
+ * actual request.
+ */
+ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
+
+ /**
+ * The Access-Control-Allow-Headers header indicates, as part of the
+ * response to a preflight request, which header field names can be used
+ * during the actual request.
+ */
+ public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
+
+ // -------------------------------------------------- CORS Request Headers
+ /**
+ * The Origin header indicates where the cross-origin request or preflight
+ * request originates from.
+ */
+ public static final String REQUEST_HEADER_ORIGIN = "Origin";
+
+ /**
+ * The Access-Control-Request-Method header indicates which method will be
+ * used in the actual request as part of the preflight request.
+ */
+ public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
+
+ /**
+ * The Access-Control-Request-Headers header indicates which headers will be
+ * used in the actual request as part of the preflight request.
+ */
+ public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
+
+ // ----------------------------------------------------- Request attributes
+ /**
+ * The prefix to a CORS request attribute.
+ */
+ public static final String HTTP_REQUEST_ATTRIBUTE_PREFIX = "cors.";
+
+ /**
+ * Attribute that contains the origin of the request.
+ */
+ public static final String HTTP_REQUEST_ATTRIBUTE_ORIGIN = HTTP_REQUEST_ATTRIBUTE_PREFIX
+ + "request.origin";
+
+ /**
+ * Boolean value, suggesting if the request is a CORS request or not.
+ */
+ public static final String HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST = HTTP_REQUEST_ATTRIBUTE_PREFIX
+ + "isCorsRequest";
+
+ /**
+ * Type of CORS request, of type {@link CORSRequestType}.
+ */
+ public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE = HTTP_REQUEST_ATTRIBUTE_PREFIX
+ + "request.type";
+
+ /**
+ * Request headers sent as 'Access-Control-Request-Headers' header, for
+ * pre-flight request.
+ */
+ public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS = HTTP_REQUEST_ATTRIBUTE_PREFIX
+ + "request.headers";
+
+ // -------------------------------------------------------------- Constants
+ /**
+ * Enumerates varies types of CORS requests. Also, provides utility methods
+ * to determine the request type.
+ */
+ public static enum CORSRequestType {
+ /**
+ * A simple HTTP request, i.e. it shouldn't be pre-flighted.
+ */
+ SIMPLE,
+ /**
+ * A HTTP request that needs to be pre-flighted.
+ */
+ ACTUAL,
+ /**
+ * A pre-flight CORS request, to get meta information, before a
+ * non-simple HTTP request is sent.
+ */
+ PRE_FLIGHT,
+ /**
+ * Not a CORS request, but a normal request.
+ */
+ NOT_CORS,
+ /**
+ * An invalid CORS request, i.e. it qualifies to be a CORS request, but
+ * fails to be a valid one.
+ */
+ INVALID_CORS
+ }
+
+ /**
+ * {@link Collection} of HTTP methods. Case sensitive.
+ *
+ * @see RFC2616
+ */
+ public static final Collection HTTP_METHODS = new HashSet(
+ Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
+ "TRACE", "CONNECT"));
+ /**
+ * {@link Collection} of non-simple HTTP methods. Case sensitive.
+ */
+ public static final Collection COMPLEX_HTTP_METHODS = new HashSet(
+ Arrays.asList("PUT", "DELETE", "TRACE", "CONNECT"));
+ /**
+ * {@link Collection} of Simple HTTP methods. Case sensitive.
+ *
+ * @see Terminology
+ */
+ public static final Collection SIMPLE_HTTP_METHODS = new HashSet(
+ Arrays.asList("GET", "POST", "HEAD"));
+
+ /**
+ * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
+ *
+ * @see Terminology
+ */
+ public static final Collection SIMPLE_HTTP_REQUEST_HEADERS = new HashSet(
+ Arrays.asList("Accept", "Accept-Language", "Content-Language"));
+
+ /**
+ * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
+ *
+ * @see Terminology
+ */
+ public static final Collection SIMPLE_HTTP_RESPONSE_HEADERS = new HashSet(
+ Arrays.asList("Cache-Control", "Content-Language", "Content-Type",
+ "Expires", "Last-Modified", "Pragma"));
+
+ /**
+ * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
+ *
+ * @see Terminology
+ */
+ public static final Collection SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES = new HashSet(
+ Arrays.asList("application/x-www-form-urlencoded",
+ "multipart/form-data", "text/plain"));
+
+ // ------------------------------------------------ Configuration Defaults
+ /**
+ * By default, all origins are allowed to make requests.
+ */
+ public static final String DEFAULT_ALLOWED_ORIGINS = "*";
+
+ /**
+ * By default, following methods are supported: GET, POST, HEAD and OPTIONS.
+ */
+ public static final String DEFAULT_ALLOWED_HTTP_METHODS = "GET,POST,HEAD,OPTIONS";
+
+ /**
+ * By default, time duration to cache pre-flight response is 30 mins.
+ */
+ public static final String DEFAULT_PREFLIGHT_MAXAGE = "1800";
+
+ /**
+ * By default, support credentials is turned on.
+ */
+ public static final String DEFAULT_SUPPORTS_CREDENTIALS = "true";
+
+ /**
+ * By default, following headers are supported:
+ * Origin,Accept,X-Requested-With, Content-Type,
+ * Access-Control-Request-Method, and Access-Control-Request-Headers.
+ */
+ public static final String DEFAULT_ALLOWED_HTTP_HEADERS = "Origin,Accept,X-Requested-With,Content-Type,"
+ + "Access-Control-Request-Method,Access-Control-Request-Headers";
+
+ /**
+ * By default, none of the headers are exposed in response.
+ */
+ public static final String DEFAULT_EXPOSED_HEADERS = "";
+
+ /**
+ * By default, access log logging is turned off
+ */
+ public static final String DEFAULT_LOGGING_ENABLED = "false";
+
+ /**
+ * By default, request is decorated with CORS attributes.
+ */
+ public static final String DEFAULT_DECORATE_REQUEST = "true";
+
+ // ----------------------------------------Filter Config Init param-name(s)
+ /**
+ * Key to retrieve allowed origins from {@link FilterConfig}.
+ */
+ public static final String PARAM_CORS_ALLOWED_ORIGINS = "cors.allowed.origins";
+
+ /**
+ * Key to retrieve support credentials from {@link FilterConfig}.
+ */
+ public static final String PARAM_CORS_SUPPORT_CREDENTIALS = "cors.support.credentials";
+
+ /**
+ * Key to retrieve exposed headers from {@link FilterConfig}.
+ */
+ public static final String PARAM_CORS_EXPOSED_HEADERS = "cors.exposed.headers";
+
+ /**
+ * Key to retrieve allowed headers from {@link FilterConfig}.
+ */
+ public static final String PARAM_CORS_ALLOWED_HEADERS = "cors.allowed.headers";
+
+ /**
+ * Key to retrieve allowed methods from {@link FilterConfig}.
+ */
+ public static final String PARAM_CORS_ALLOWED_METHODS = "cors.allowed.methods";
+
+ /**
+ * Key to retrieve preflight max age from {@link FilterConfig}.
+ */
+ public static final String PARAM_CORS_PREFLIGHT_MAXAGE = "cors.preflight.maxage";
+
+ /**
+ * Key to retrieve access log logging flag.
+ */
+ public static final String PARAM_CORS_LOGGING_ENABLED = "cors.logging.enabled";
+
+ /**
+ * Key to determine if request should be decorated.
+ */
+ public static final String PARAM_CORS_REQUEST_DECORATE = "cors.request.decorate";
+}
\ No newline at end of file
Property changes on: java/org/apache/catalina/filters/CORSFilter.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: webapps/docs/config/filter.xml
===================================================================
--- webapps/docs/config/filter.xml (revision 1488477)
+++ webapps/docs/config/filter.xml (working copy)
@@ -1309,6 +1309,118 @@
+
+
+
This filter is an implementation of W3C's CORS (Cross-Origin Resource Sharing) specification, which is a mechanism that enables cross-origin requests.
+
The filter works by adding required Access-Control-* headers to HttpServletResponse object. The filter also protects against HTTP response splitting. If request is invalid, or is not permitted, then request is rejected with HTTP status code 403 (Forbidden). A flowchart that demonstrate request processing by this filter is available here.
+
The minimal configuration required to use this filter is:
+
+
+
+
The filter class name for the CORS Filter is org.apache.catalina.filters.CORSFilter.
+
+
+
The CORS Filter supports following initialisation parameters:
+
+
+
A list of origins that are allowed to access the resource. A '*' can be specified to enable access to resource from any origin. Otherwise, a whitelist of comma separated origins can be provided. Ex: http://www.w3.org, https://www.apache.org. Defaults:* (Any origin is allowed to access the resource).
+
+
+
A comma separated list of HTTP methods that can be used to access the resource, using cross-origin requests. These are the methods which will also be included as part of 'Access-Control-Allow-Methods' header in a pre-flight response. Ex: GET,POST. Defaults:GET,POST,HEAD,OPTIONS
+
+
+
A comma separated list of request headers that can be used when making an actual request. These header will also be returned as part of 'Access-Control-Allow-Headers' header in a pre-flight response. Ex: Origin,Accept. Defaults:Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers
+
+
+
A comma separated list of headers other than the simple response headers that browsers are allowed to access. These are the headers which will also be included as part of 'Access-Control-Expose-Headers' header in the pre-flight response. Ex: X-CUSTOM-HEADER-PING,X-CUSTOM-HEADER-PONG. Default: None. Non-simple headers are not exposed by default.
+
+
+
The amount of seconds, browser is allowed to cache the result of the pre-flight request. This will be included as part of 'Access-Control-Max-Age' header in the pre-flight response. A negative value will prevent CORS Filter from adding this response header from pre-flight response. Defaults:1800
+
+
+
A flag that indicates whether the resource supports user credentials. This flag is exposed as part of 'Access-Control-Allow-Credentials' header in a pre-flight response. It helps browser determine whether or not an actual request can be made using credentials. Defaults:true
+
+
+
A flag to control logging to container logs. Defaults:false
+
+
+
A flag to control if CORS specific attributes should be added to HttpServletRequest object or not. Defaults:true
+
+
+
Here's an example of a more advanced configuration, that overrides defaults:
+
+
+
+
CORS Filter adds information about a request, in the HttpServletRequest object, for consumption downstream. Following attributes are set, if cors.request.decorate initialisation parameter is true:
+
+
cors.isCorsRequest: Flag to determine if a request is a CORS request.
+
cors.request.origin: The Origin URL, i.e. the URL of the page from where the request is originated.
+
cors.request.type: Type of CORS request. Possible values:
+
+
SIMPLE: A request which is not preceded by a pre-flight request.
+
ACTUAL: A request which is preceded by a pre-flight request.
+
PRE_FLIGHT: A pre-flight request.
+
NOT_CORS: A normal same-origin request.
+
INVALID_CORS: A cross-origin request, which is invalid.
+
+
+
cors.request.headers: Request headers sent as 'Access-Control-Request-Headers' header, for a pre-flight request.