2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package org.apache.catalina.filters;
19 import java.io.IOException;
21 import java.net.URISyntaxException;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.LinkedList;
26 import java.util.List;
29 import javax.servlet.Filter;
30 import javax.servlet.FilterChain;
31 import javax.servlet.FilterConfig;
32 import javax.servlet.ServletException;
33 import javax.servlet.ServletRequest;
34 import javax.servlet.ServletResponse;
35 import javax.servlet.http.HttpServletRequest;
36 import javax.servlet.http.HttpServletResponse;
38 import org.apache.catalina.filters.Constants;
39 import org.apache.juli.logging.Log;
40 import org.apache.juli.logging.LogFactory;
41 import org.apache.tomcat.util.res.StringManager;
45 * A {@link Filter} that enable client-side cross-origin requests by
46 * implementing W3C's CORS (<b>C</b>ross-<b>O</b>rigin <b>R</b>esource
47 * <b>S</b>haring) specification for resources. Each {@link HttpServletRequest}
48 * request is inspected as per specification, and appropriate response headers
49 * are added to {@link HttpServletResponse}.
53 * By default, it also sets following request attributes, that help to
54 * determine the nature of the request downstream.
56 * <li><b>cors.isCorsRequest:</b> Flag to determine if the request is a CORS
57 * request. Set to <code>true</code> if a CORS request; <code>false</code>
59 * <li><b>cors.request.origin:</b> The Origin URL, i.e. the URL of the page from
60 * where the request is originated.</li>
62 * <b>cors.request.type:</b> Type of request. Possible values:
64 * <li>SIMPLE: A request which is not preceded by a pre-flight request.</li>
65 * <li>ACTUAL: A request which is preceded by a pre-flight request.</li>
66 * <li>PRE_FLIGHT: A pre-flight request.</li>
67 * <li>NOT_CORS: A normal same-origin request.</li>
68 * <li>INVALID_CORS: A cross-origin request which is invalid.</li>
71 * <li><b>cors.request.headers:</b> Request headers sent as
72 * 'Access-Control-Request-Headers' header, for pre-flight request.</li>
76 * @see <a href="http://www.w3.org/TR/cors/">CORS specification</a>
79 public final class CorsFilter implements Filter {
81 private static final Log log = LogFactory.getLog(CorsFilter.class);
83 private static final StringManager sm =
84 StringManager.getManager(Constants.Package);
88 * A {@link Collection} of origins consisting of zero or more origins that
89 * are allowed access to the resource.
91 private final Collection<String> allowedOrigins;
94 * Determines if any origin is allowed to make request.
96 private boolean anyOriginAllowed;
99 * A {@link Collection} of methods consisting of zero or more methods that
100 * are supported by the resource.
102 private final Collection<String> allowedHttpMethods;
105 * A {@link Collection} of headers consisting of zero or more header field
106 * names that are supported by the resource.
108 private final Collection<String> allowedHttpHeaders;
111 * A {@link Collection} of exposed headers consisting of zero or more header
112 * field names of headers other than the simple response headers that the
113 * resource might use and can be exposed.
115 private final Collection<String> exposedHeaders;
118 * A supports credentials flag that indicates whether the resource supports
119 * user credentials in the request. It is true when the resource does and
122 private boolean supportsCredentials;
125 * Indicates (in seconds) how long the results of a pre-flight request can
126 * be cached in a pre-flight result cache.
128 private long preflightMaxAge;
131 * Determines if the request should be decorated or not.
133 private boolean decorateRequest;
136 public CorsFilter() {
137 this.allowedOrigins = new HashSet<String>();
138 this.allowedHttpMethods = new HashSet<String>();
139 this.allowedHttpHeaders = new HashSet<String>();
140 this.exposedHeaders = new HashSet<String>();
145 public void doFilter(final ServletRequest servletRequest,
146 final ServletResponse servletResponse, final FilterChain filterChain)
147 throws IOException, ServletException {
148 if (!(servletRequest instanceof HttpServletRequest) ||
149 !(servletResponse instanceof HttpServletResponse)) {
150 throw new ServletException(sm.getString("corsFilter.onlyHttp"));
153 // Safe to downcast at this point.
154 HttpServletRequest request = (HttpServletRequest) servletRequest;
155 HttpServletResponse response = (HttpServletResponse) servletResponse;
157 // Determines the CORS request type.
158 CorsFilter.CORSRequestType requestType = checkRequestType(request);
160 // Adds CORS specific attributes to request.
161 if (decorateRequest) {
162 CorsFilter.decorateCORSProperties(request, requestType);
164 switch (requestType) {
166 // Handles a Simple CORS request.
167 this.handleSimpleCORS(request, response, filterChain);
170 // Handles an Actual CORS request.
171 this.handleSimpleCORS(request, response, filterChain);
174 // Handles a Pre-flight CORS request.
175 this.handlePreflightCORS(request, response, filterChain);
178 // Handles a Normal request that is not a cross-origin request.
179 this.handleNonCORS(request, response, filterChain);
182 // Handles a CORS request that violates specification.
183 this.handleInvalidCORS(request, response, filterChain);
190 public void init(final FilterConfig filterConfig) throws ServletException {
191 // Initialize defaults
192 parseAndStore(DEFAULT_ALLOWED_ORIGINS, DEFAULT_ALLOWED_HTTP_METHODS,
193 DEFAULT_ALLOWED_HTTP_HEADERS, DEFAULT_EXPOSED_HEADERS,
194 DEFAULT_SUPPORTS_CREDENTIALS, DEFAULT_PREFLIGHT_MAXAGE,
195 DEFAULT_DECORATE_REQUEST);
197 if (filterConfig != null) {
198 String configAllowedOrigins = filterConfig
199 .getInitParameter(PARAM_CORS_ALLOWED_ORIGINS);
200 String configAllowedHttpMethods = filterConfig
201 .getInitParameter(PARAM_CORS_ALLOWED_METHODS);
202 String configAllowedHttpHeaders = filterConfig
203 .getInitParameter(PARAM_CORS_ALLOWED_HEADERS);
204 String configExposedHeaders = filterConfig
205 .getInitParameter(PARAM_CORS_EXPOSED_HEADERS);
206 String configSupportsCredentials = filterConfig
207 .getInitParameter(PARAM_CORS_SUPPORT_CREDENTIALS);
208 String configPreflightMaxAge = filterConfig
209 .getInitParameter(PARAM_CORS_PREFLIGHT_MAXAGE);
210 String configDecorateRequest = filterConfig
211 .getInitParameter(PARAM_CORS_REQUEST_DECORATE);
213 parseAndStore(configAllowedOrigins, configAllowedHttpMethods,
214 configAllowedHttpHeaders, configExposedHeaders,
215 configSupportsCredentials, configPreflightMaxAge,
216 configDecorateRequest);
222 * Handles a CORS request of type {@link CORSRequestType}.SIMPLE.
225 * The {@link HttpServletRequest} object.
227 * The {@link HttpServletResponse} object.
229 * The {@link FilterChain} object.
230 * @throws IOException
231 * @throws ServletException
232 * @see <a href="http://www.w3.org/TR/cors/#resource-requests">Simple
233 * Cross-Origin Request, Actual Request, and Redirects</a>
235 protected void handleSimpleCORS(final HttpServletRequest request,
236 final HttpServletResponse response, final FilterChain filterChain)
237 throws IOException, ServletException {
239 CorsFilter.CORSRequestType requestType = checkRequestType(request);
240 if (!(requestType == CorsFilter.CORSRequestType.SIMPLE ||
241 requestType == CorsFilter.CORSRequestType.ACTUAL)) {
242 throw new IllegalArgumentException(
243 sm.getString("corsFilter.wrongType2",
244 CorsFilter.CORSRequestType.SIMPLE,
245 CorsFilter.CORSRequestType.ACTUAL));
248 final String origin = request
249 .getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
250 final String method = request.getMethod();
253 if (!isOriginAllowed(origin)) {
254 handleInvalidCORS(request, response, filterChain);
258 if (!allowedHttpMethods.contains(method)) {
259 handleInvalidCORS(request, response, filterChain);
264 // Add a single Access-Control-Allow-Origin header.
265 if (anyOriginAllowed && !supportsCredentials) {
266 // If resource doesn't support credentials and if any origin is
268 // to make CORS request, return header with '*'.
270 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
273 // If the resource supports credentials add a single
274 // Access-Control-Allow-Origin header, with the value of the Origin
277 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
282 // If the resource supports credentials, add a single
283 // Access-Control-Allow-Credentials header with the case-sensitive
284 // string "true" as value.
285 if (supportsCredentials) {
287 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
292 // If the list of exposed headers is not empty add one or more
293 // Access-Control-Expose-Headers headers, with as values the header
294 // field names given in the list of exposed headers.
295 if ((exposedHeaders != null) && (exposedHeaders.size() > 0)) {
296 String exposedHeadersString = join(exposedHeaders, ",");
298 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
299 exposedHeadersString);
302 // Forward the request down the filter chain.
303 filterChain.doFilter(request, response);
308 * Handles CORS pre-flight request.
311 * The {@link HttpServletRequest} object.
313 * The {@link HttpServletResponse} object.
315 * The {@link FilterChain} object.
316 * @throws IOException
317 * @throws ServletException
319 protected void handlePreflightCORS(final HttpServletRequest request,
320 final HttpServletResponse response, final FilterChain filterChain)
321 throws IOException, ServletException {
323 CORSRequestType requestType = checkRequestType(request);
324 if (requestType != CORSRequestType.PRE_FLIGHT) {
325 throw new IllegalArgumentException(
326 sm.getString("corsFilter.wrongType1",
327 CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
330 final String origin = request
331 .getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
334 if (!isOriginAllowed(origin)) {
335 handleInvalidCORS(request, response, filterChain);
340 String accessControlRequestMethod = request.getHeader(
341 CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
342 if (accessControlRequestMethod == null ||
343 !HTTP_METHODS.contains(accessControlRequestMethod.trim())) {
344 handleInvalidCORS(request, response, filterChain);
347 accessControlRequestMethod = accessControlRequestMethod.trim();
351 String accessControlRequestHeadersHeader = request.getHeader(
352 CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
353 List<String> accessControlRequestHeaders = new LinkedList<String>();
354 if (accessControlRequestHeadersHeader != null &&
355 !accessControlRequestHeadersHeader.trim().isEmpty()) {
356 String[] headers = accessControlRequestHeadersHeader.trim().split(
358 for (String header : headers) {
359 accessControlRequestHeaders.add(header.trim().toLowerCase());
364 if (!allowedHttpMethods.contains(accessControlRequestMethod)) {
365 handleInvalidCORS(request, response, filterChain);
370 if (!accessControlRequestHeaders.isEmpty()) {
371 for (String header : accessControlRequestHeaders) {
372 if (!allowedHttpHeaders.contains(header)) {
373 handleInvalidCORS(request, response, filterChain);
380 if (supportsCredentials) {
382 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
385 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
388 if (anyOriginAllowed) {
390 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
394 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
400 if (preflightMaxAge > 0) {
402 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE,
403 String.valueOf(preflightMaxAge));
408 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
409 accessControlRequestMethod);
412 if ((allowedHttpHeaders != null) && (!allowedHttpHeaders.isEmpty())) {
414 CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
415 join(allowedHttpHeaders, ","));
418 // Do not forward the request down the filter chain.
423 * Handles a request, that's not a CORS request, but is a valid request i.e.
424 * it is not a cross-origin request. This implementation, just forwards the
425 * request down the filter chain.
428 * The {@link HttpServletRequest} object.
430 * The {@link HttpServletResponse} object.
432 * The {@link FilterChain} object.
433 * @throws IOException
434 * @throws ServletException
436 private void handleNonCORS(final HttpServletRequest request,
437 final HttpServletResponse response, final FilterChain filterChain)
438 throws IOException, ServletException {
440 filterChain.doFilter(request, response);
445 * Handles a CORS request that violates specification.
448 * The {@link HttpServletRequest} object.
450 * The {@link HttpServletResponse} object.
452 * The {@link FilterChain} object.
454 private void handleInvalidCORS(final HttpServletRequest request,
455 final HttpServletResponse response, final FilterChain filterChain) {
456 String origin = request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
457 String method = request.getMethod();
458 String accessControlRequestHeaders = request.getHeader(
459 REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
461 response.setContentType("text/plain");
462 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
463 response.resetBuffer();
465 if (log.isDebugEnabled()) {
466 // Debug so no need for i18n
467 StringBuilder message =
468 new StringBuilder("Invalid CORS request; Origin=");
469 message.append(origin);
470 message.append(";Method=");
471 message.append(method);
472 if (accessControlRequestHeaders != null) {
473 message.append(";Access-Control-Request-Headers=");
474 message.append(accessControlRequestHeaders);
476 log.debug(message.toString());
482 public void destroy() {
488 * Decorates the {@link HttpServletRequest}, with CORS attributes.
490 * <li><b>cors.isCorsRequest:</b> Flag to determine if request is a CORS
491 * request. Set to <code>true</code> if CORS request; <code>false</code>
493 * <li><b>cors.request.origin:</b> The Origin URL.</li>
494 * <li><b>cors.request.type:</b> Type of request. Values:
495 * <code>simple</code> or <code>preflight</code> or <code>not_cors</code> or
496 * <code>invalid_cors</code></li>
497 * <li><b>cors.request.headers:</b> Request headers sent as
498 * 'Access-Control-Request-Headers' header, for pre-flight request.</li>
502 * The {@link HttpServletRequest} object.
503 * @param corsRequestType
504 * The {@link CORSRequestType} object.
506 protected static void decorateCORSProperties(
507 final HttpServletRequest request,
508 final CORSRequestType corsRequestType) {
509 if (request == null) {
510 throw new IllegalArgumentException(
511 sm.getString("corsFilter.nullRequest"));
514 if (corsRequestType == null) {
515 throw new IllegalArgumentException(
516 sm.getString("corsFilter.nullRequestType"));
519 switch (corsRequestType) {
521 request.setAttribute(
522 CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
524 request.setAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
525 request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN));
526 request.setAttribute(
527 CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
528 corsRequestType.name().toLowerCase());
531 request.setAttribute(
532 CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
534 request.setAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
535 request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN));
536 request.setAttribute(
537 CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
538 corsRequestType.name().toLowerCase());
541 request.setAttribute(
542 CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
544 request.setAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
545 request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN));
546 request.setAttribute(
547 CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
548 corsRequestType.name().toLowerCase());
549 String headers = request.getHeader(
550 REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
551 if (headers == null) {
554 request.setAttribute(
555 CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS, headers);
558 request.setAttribute(
559 CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
563 // Don't set any attributes
570 * Joins elements of {@link Set} into a string, where each element is
571 * separated by the provided separator.
574 * The {@link Set} containing elements to join together.
575 * @param joinSeparator
576 * The character to be used for separating elements.
577 * @return The joined {@link String}; <code>null</code> if elements
578 * {@link Set} is null.
580 protected static String join(final Collection<String> elements,
581 final String joinSeparator) {
582 String separator = ",";
583 if (elements == null) {
586 if (joinSeparator != null) {
587 separator = joinSeparator;
589 StringBuilder buffer = new StringBuilder();
590 boolean isFirst = true;
591 for (String element : elements) {
593 buffer.append(separator);
598 if (element != null) {
599 buffer.append(element);
603 return buffer.toString();
608 * Determines the request type.
612 protected CORSRequestType checkRequestType(final HttpServletRequest request) {
613 CORSRequestType requestType = CORSRequestType.INVALID_CORS;
614 if (request == null) {
615 throw new IllegalArgumentException(
616 sm.getString("corsFilter.nullRequest"));
618 String originHeader = request.getHeader(REQUEST_HEADER_ORIGIN);
619 // Section 6.1.1 and Section 6.2.1
620 if (originHeader != null) {
621 if (originHeader.isEmpty()) {
622 requestType = CORSRequestType.INVALID_CORS;
623 } else if (!isValidOrigin(originHeader)) {
624 requestType = CORSRequestType.INVALID_CORS;
626 String method = request.getMethod();
627 if (method != null && HTTP_METHODS.contains(method)) {
628 if ("OPTIONS".equals(method)) {
629 String accessControlRequestMethodHeader =
631 REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
632 if (accessControlRequestMethodHeader != null &&
633 !accessControlRequestMethodHeader.isEmpty()) {
634 requestType = CORSRequestType.PRE_FLIGHT;
635 } else if (accessControlRequestMethodHeader != null &&
636 accessControlRequestMethodHeader.isEmpty()) {
637 requestType = CORSRequestType.INVALID_CORS;
639 requestType = CORSRequestType.ACTUAL;
641 } else if ("GET".equals(method) || "HEAD".equals(method)) {
642 requestType = CORSRequestType.SIMPLE;
643 } else if ("POST".equals(method)) {
644 String contentType = request.getContentType();
645 if (contentType != null) {
646 contentType = contentType.toLowerCase().trim();
647 if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES
648 .contains(contentType)) {
649 requestType = CORSRequestType.SIMPLE;
651 requestType = CORSRequestType.ACTUAL;
654 } else if (COMPLEX_HTTP_METHODS.contains(method)) {
655 requestType = CORSRequestType.ACTUAL;
660 requestType = CORSRequestType.NOT_CORS;
668 * Checks if the Origin is allowed to make a CORS request.
672 * @return <code>true</code> if origin is allowed; <code>false</code>
675 private boolean isOriginAllowed(final String origin) {
676 if (anyOriginAllowed) {
680 // If 'Origin' header is a case-sensitive match of any of allowed
681 // origins, then return true, else return false.
682 return allowedOrigins.contains(origin);
687 * Parses each param-value and populates configuration variables. If a param
688 * is provided, it overrides the default.
690 * @param allowedOrigins
691 * A {@link String} of comma separated origins.
692 * @param allowedHttpMethods
693 * A {@link String} of comma separated HTTP methods.
694 * @param allowedHttpHeaders
695 * A {@link String} of comma separated HTTP headers.
696 * @param exposedHeaders
697 * A {@link String} of comma separated headers that needs to be
699 * @param supportsCredentials
700 * "true" if support credentials needs to be enabled.
701 * @param preflightMaxAge
702 * The amount of seconds the user agent is allowed to cache the
703 * result of the pre-flight request.
704 * @throws ServletException
706 private void parseAndStore(final String allowedOrigins,
707 final String allowedHttpMethods, final String allowedHttpHeaders,
708 final String exposedHeaders, final String supportsCredentials,
709 final String preflightMaxAge, final String decorateRequest)
710 throws ServletException {
711 if (allowedOrigins != null) {
712 if (allowedOrigins.trim().equals("*")) {
713 this.anyOriginAllowed = true;
715 this.anyOriginAllowed = false;
716 Set<String> setAllowedOrigins =
717 parseStringToSet(allowedOrigins);
718 this.allowedOrigins.clear();
719 this.allowedOrigins.addAll(setAllowedOrigins);
723 if (allowedHttpMethods != null) {
724 Set<String> setAllowedHttpMethods =
725 parseStringToSet(allowedHttpMethods);
726 this.allowedHttpMethods.clear();
727 this.allowedHttpMethods.addAll(setAllowedHttpMethods);
730 if (allowedHttpHeaders != null) {
731 Set<String> setAllowedHttpHeaders =
732 parseStringToSet(allowedHttpHeaders);
733 Set<String> lowerCaseHeaders = new HashSet<String>();
734 for (String header : setAllowedHttpHeaders) {
735 String lowerCase = header.toLowerCase();
736 lowerCaseHeaders.add(lowerCase);
738 this.allowedHttpHeaders.clear();
739 this.allowedHttpHeaders.addAll(lowerCaseHeaders);
742 if (exposedHeaders != null) {
743 Set<String> setExposedHeaders = parseStringToSet(exposedHeaders);
744 this.exposedHeaders.clear();
745 this.exposedHeaders.addAll(setExposedHeaders);
748 if (supportsCredentials != null) {
749 // For any value other then 'true' this will be false.
750 this.supportsCredentials = Boolean
751 .parseBoolean(supportsCredentials);
754 if (preflightMaxAge != null) {
756 if (!preflightMaxAge.isEmpty()) {
757 this.preflightMaxAge = Long.parseLong(preflightMaxAge);
759 this.preflightMaxAge = 0L;
761 } catch (NumberFormatException e) {
762 throw new ServletException(
763 sm.getString("corsFilter.invalidPreflightMaxAge"), e);
767 if (decorateRequest != null) {
768 // For any value other then 'true' this will be false.
769 this.decorateRequest = Boolean.parseBoolean(decorateRequest);
774 * Takes a comma separated list and returns a Set<String>.
777 * A comma separated list of strings.
778 * @return Set<String>
780 private Set<String> parseStringToSet(final String data) {
783 if (data != null && data.length() > 0) {
784 splits = data.split(",");
786 splits = new String[] {};
789 Set<String> set = new HashSet<String>();
790 if (splits.length > 0) {
791 for (String split : splits) {
792 set.add(split.trim());
801 * Checks if a given origin is valid or not. Criteria:
803 * <li>If an encoded character is present in origin, it's not valid.</li>
804 * <li>Origin should be a valid {@link URI}</li>
808 * @see <a href="http://tools.ietf.org/html/rfc952">RFC952</a>
810 protected static boolean isValidOrigin(String origin) {
811 // Checks for encoded characters. Helps prevent CRLF injection.
812 if (origin.contains("%")) {
819 originURI = new URI(origin);
820 } catch (URISyntaxException e) {
823 // If scheme for URI is null, return false. Return true otherwise.
824 return originURI.getScheme() != null;
830 * Determines if any origin is allowed to make CORS request.
832 * @return <code>true</code> if it's enabled; false otherwise.
834 public boolean isAnyOriginAllowed() {
835 return anyOriginAllowed;
840 * Returns a {@link Set} of headers that should be exposed by browser.
842 public Collection<String> getExposedHeaders() {
843 return exposedHeaders;
848 * Determines is supports credentials is enabled.
850 public boolean isSupportsCredentials() {
851 return supportsCredentials;
856 * Returns the preflight response cache time in seconds.
858 * @return Time to cache in seconds.
860 public long getPreflightMaxAge() {
861 return preflightMaxAge;
866 * Returns the {@link Set} of allowed origins that are allowed to make
869 * @return {@link Set}
871 public Collection<String> getAllowedOrigins() {
872 return allowedOrigins;
877 * Returns a {@link Set} of HTTP methods that are allowed to make requests.
879 * @return {@link Set}
881 public Collection<String> getAllowedHttpMethods() {
882 return allowedHttpMethods;
887 * Returns a {@link Set} of headers support by resource.
889 * @return {@link Set}
891 public Collection<String> getAllowedHttpHeaders() {
892 return allowedHttpHeaders;
896 // -------------------------------------------------- CORS Response Headers
898 * The Access-Control-Allow-Origin header indicates whether a resource can
899 * be shared based by returning the value of the Origin request header in
902 public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN =
903 "Access-Control-Allow-Origin";
906 * The Access-Control-Allow-Credentials header indicates whether the
907 * response to request can be exposed when the omit credentials flag is
908 * unset. When part of the response to a preflight request it indicates that
909 * the actual request can include user credentials.
911 public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS =
912 "Access-Control-Allow-Credentials";
915 * The Access-Control-Expose-Headers header indicates which headers are safe
916 * to expose to the API of a CORS API specification
918 public static final String RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS =
919 "Access-Control-Expose-Headers";
922 * The Access-Control-Max-Age header indicates how long the results of a
923 * preflight request can be cached in a preflight result cache.
925 public static final String RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE =
926 "Access-Control-Max-Age";
929 * The Access-Control-Allow-Methods header indicates, as part of the
930 * response to a preflight request, which methods can be used during the
933 public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS =
934 "Access-Control-Allow-Methods";
937 * The Access-Control-Allow-Headers header indicates, as part of the
938 * response to a preflight request, which header field names can be used
939 * during the actual request.
941 public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS =
942 "Access-Control-Allow-Headers";
944 // -------------------------------------------------- CORS Request Headers
946 * The Origin header indicates where the cross-origin request or preflight
947 * request originates from.
949 public static final String REQUEST_HEADER_ORIGIN = "Origin";
952 * The Access-Control-Request-Method header indicates which method will be
953 * used in the actual request as part of the preflight request.
955 public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD =
956 "Access-Control-Request-Method";
959 * The Access-Control-Request-Headers header indicates which headers will be
960 * used in the actual request as part of the preflight request.
962 public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS =
963 "Access-Control-Request-Headers";
965 // ----------------------------------------------------- Request attributes
967 * The prefix to a CORS request attribute.
969 public static final String HTTP_REQUEST_ATTRIBUTE_PREFIX = "cors.";
972 * Attribute that contains the origin of the request.
974 public static final String HTTP_REQUEST_ATTRIBUTE_ORIGIN =
975 HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.origin";
978 * Boolean value, suggesting if the request is a CORS request or not.
980 public static final String HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST =
981 HTTP_REQUEST_ATTRIBUTE_PREFIX + "isCorsRequest";
984 * Type of CORS request, of type {@link CORSRequestType}.
986 public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE =
987 HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.type";
990 * Request headers sent as 'Access-Control-Request-Headers' header, for
991 * pre-flight request.
993 public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS =
994 HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.headers";
996 // -------------------------------------------------------------- Constants
998 * Enumerates varies types of CORS requests. Also, provides utility methods
999 * to determine the request type.
1001 protected static enum CORSRequestType {
1003 * A simple HTTP request, i.e. it shouldn't be pre-flighted.
1007 * A HTTP request that needs to be pre-flighted.
1011 * A pre-flight CORS request, to get meta information, before a
1012 * non-simple HTTP request is sent.
1016 * Not a CORS request, but a normal request.
1020 * An invalid CORS request, i.e. it qualifies to be a CORS request, but
1021 * fails to be a valid one.
1027 * {@link Collection} of HTTP methods. Case sensitive.
1029 * @see <a href="http://tools.ietf.org/html/rfc2616#section-5.1.1"
1030 * >http://tools.ietf.org/html/rfc2616#section-5.1.1</a>
1033 public static final Collection<String> HTTP_METHODS =
1034 new HashSet<String>(Arrays.asList("OPTIONS", "GET", "HEAD", "POST",
1035 "PUT", "DELETE", "TRACE", "CONNECT"));
1037 * {@link Collection} of non-simple HTTP methods. Case sensitive.
1039 public static final Collection<String> COMPLEX_HTTP_METHODS =
1040 new HashSet<String>(Arrays.asList("PUT", "DELETE", "TRACE",
1043 * {@link Collection} of Simple HTTP methods. Case sensitive.
1045 * @see <a href="http://www.w3.org/TR/cors/#terminology"
1046 * >http://www.w3.org/TR/cors/#terminology</a>
1048 public static final Collection<String> SIMPLE_HTTP_METHODS =
1049 new HashSet<String>(Arrays.asList("GET", "POST", "HEAD"));
1052 * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
1054 * @see <a href="http://www.w3.org/TR/cors/#terminology"
1055 * >http://www.w3.org/TR/cors/#terminology</a>
1057 public static final Collection<String> SIMPLE_HTTP_REQUEST_HEADERS =
1058 new HashSet<String>(Arrays.asList("Accept", "Accept-Language",
1059 "Content-Language"));
1062 * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
1064 * @see <a href="http://www.w3.org/TR/cors/#terminology"
1065 * >http://www.w3.org/TR/cors/#terminology</a>
1067 public static final Collection<String> SIMPLE_HTTP_RESPONSE_HEADERS =
1068 new HashSet<String>(Arrays.asList("Cache-Control",
1069 "Content-Language", "Content-Type", "Expires",
1070 "Last-Modified", "Pragma"));
1073 * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
1075 * @see <a href="http://www.w3.org/TR/cors/#terminology"
1076 * >http://www.w3.org/TR/cors/#terminology</a>
1078 public static final Collection<String> SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES =
1079 new HashSet<String>(Arrays.asList(
1080 "application/x-www-form-urlencoded",
1081 "multipart/form-data", "text/plain"));
1083 // ------------------------------------------------ Configuration Defaults
1085 * By default, all origins are allowed to make requests.
1087 public static final String DEFAULT_ALLOWED_ORIGINS = "*";
1090 * By default, following methods are supported: GET, POST, HEAD and OPTIONS.
1092 public static final String DEFAULT_ALLOWED_HTTP_METHODS =
1093 "GET,POST,HEAD,OPTIONS";
1096 * By default, time duration to cache pre-flight response is 30 mins.
1098 public static final String DEFAULT_PREFLIGHT_MAXAGE = "1800";
1101 * By default, support credentials is turned on.
1103 public static final String DEFAULT_SUPPORTS_CREDENTIALS = "true";
1106 * By default, following headers are supported:
1107 * Origin,Accept,X-Requested-With, Content-Type,
1108 * Access-Control-Request-Method, and Access-Control-Request-Headers.
1110 public static final String DEFAULT_ALLOWED_HTTP_HEADERS =
1111 "Origin,Accept,X-Requested-With,Content-Type," +
1112 "Access-Control-Request-Method,Access-Control-Request-Headers";
1115 * By default, none of the headers are exposed in response.
1117 public static final String DEFAULT_EXPOSED_HEADERS = "";
1120 * By default, request is decorated with CORS attributes.
1122 public static final String DEFAULT_DECORATE_REQUEST = "true";
1124 // ----------------------------------------Filter Config Init param-name(s)
1126 * Key to retrieve allowed origins from {@link FilterConfig}.
1128 public static final String PARAM_CORS_ALLOWED_ORIGINS =
1129 "cors.allowed.origins";
1132 * Key to retrieve support credentials from {@link FilterConfig}.
1134 public static final String PARAM_CORS_SUPPORT_CREDENTIALS =
1135 "cors.support.credentials";
1138 * Key to retrieve exposed headers from {@link FilterConfig}.
1140 public static final String PARAM_CORS_EXPOSED_HEADERS =
1141 "cors.exposed.headers";
1144 * Key to retrieve allowed headers from {@link FilterConfig}.
1146 public static final String PARAM_CORS_ALLOWED_HEADERS =
1147 "cors.allowed.headers";
1150 * Key to retrieve allowed methods from {@link FilterConfig}.
1152 public static final String PARAM_CORS_ALLOWED_METHODS =
1153 "cors.allowed.methods";
1156 * Key to retrieve preflight max age from {@link FilterConfig}.
1158 public static final String PARAM_CORS_PREFLIGHT_MAXAGE =
1159 "cors.preflight.maxage";
1162 * Key to determine if request should be decorated.
1164 public static final String PARAM_CORS_REQUEST_DECORATE =
1165 "cors.request.decorate";