11/*
2- * Copyright 2013, 2014 EnergyOS.org
2+ * Copyright 2013 BrandsEye.com (http://www.brandseye.com)
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1616
1717package org .energyos .espi .thirdparty .web .filter ;
1818
19- import org .springframework .stereotype .Component ;
20- import org .springframework .web .filter .OncePerRequestFilter ;
21-
22- import javax .servlet .FilterChain ;
23- import javax .servlet .ServletException ;
19+ import javax .servlet .*;
2420import javax .servlet .http .HttpServletRequest ;
2521import javax .servlet .http .HttpServletResponse ;
2622import java .io .IOException ;
23+ import java .util .Enumeration ;
24+ import java .util .LinkedHashMap ;
25+ import java .util .Map ;
26+ import java .util .regex .Pattern ;
27+
28+ import org .springframework .stereotype .Component ;
29+
30+ /**
31+ * Adds CORS headers to requests to enable cross-domain access.
32+ */
2733
2834@ Component
29- public class CORSFilter extends OncePerRequestFilter {
30- @ Override
31- protected void doFilterInternal (HttpServletRequest request , HttpServletResponse response , FilterChain filterChain ) throws ServletException , IOException {
32-
33- if (logger .isDebugEnabled ()) {
34- logger .debug ("Request Method is '" + request .getMethod () + "'" );
35+ public class CORSFilter implements Filter {
36+
37+ private final Map <String , String > optionsHeaders = new LinkedHashMap <String , String >();
38+
39+ private Pattern allowOriginRegex ;
40+ private String allowOrigin ;
41+ private String exposeHeaders ;
42+
43+ public void init (FilterConfig cfg ) throws ServletException {
44+ String regex = cfg .getInitParameter ("allow.origin.regex" );
45+ if (regex != null ) {
46+ allowOriginRegex = Pattern .compile (regex , Pattern .CASE_INSENSITIVE );
47+ } else {
48+ optionsHeaders .put ("Access-Control-Allow-Origin" , "*" );
3549 }
36-
37- // Only add Access-Control header fields if request is OPTIONS
38- if (request .getMethod ().equals ("OPTIONS" )) {
39- response .addHeader ("Access-Control-Allow-Origin" , "*" );
40- response .addHeader ("Access-Control-Allow-Methods" , "GET, POST, PUT, DELETE" );
41- response .addHeader ("Access-Control-Allow-Headers" , "Content-Type, Authorization" );
42- response .addHeader ("Access-Control-Max-Age" , "1800" );
50+
51+ optionsHeaders .put ("Access-Control-Allow-Headers" , "origin, authorization, accept, content-type" );
52+ optionsHeaders .put ("Access-Control-Allow-Methods" , "GET, POST, PUT, DELETE, OPTIONS" );
53+ optionsHeaders .put ("Access-Control-Max-Age" , "1800" );
54+ for (Enumeration <String > i = cfg .getInitParameterNames (); i .hasMoreElements (); ) {
55+ String name = i .nextElement ();
56+ if (name .startsWith ("header:" )) {
57+ optionsHeaders .put (name .substring (7 ), cfg .getInitParameter (name ));
58+ }
4359 }
4460
61+ //maintained for backward compatibility on how to set allowOrigin if not
62+ //using a regex
63+ allowOrigin = optionsHeaders .get ("Access-Control-Allow-Origin" );
64+ //since all methods now go through checkOrigin() to apply the Access-Control-Allow-Origin
65+ //header, and that header should have a single value of the requesting Origin since
66+ //Access-Control-Allow-Credentials is always true, we remove it from the options headers
67+ optionsHeaders .remove ("Access-Control-Allow-Origin" );
68+
69+ exposeHeaders = cfg .getInitParameter ("expose.headers" );
70+ }
71+
72+ public void doFilter (ServletRequest request , ServletResponse response , FilterChain filterChain )
73+ throws IOException , ServletException {
74+ if (request instanceof HttpServletRequest && response instanceof HttpServletResponse ) {
75+ HttpServletRequest req = (HttpServletRequest )request ;
76+ HttpServletResponse resp = (HttpServletResponse )response ;
77+ if ("OPTIONS" .equals (req .getMethod ())) {
78+ if (checkOrigin (req , resp )) {
79+ for (Map .Entry <String , String > e : optionsHeaders .entrySet ()) {
80+ resp .addHeader (e .getKey (), e .getValue ());
81+ }
82+
83+ // We need to return here since we don't want the chain to further process
84+ // a preflight request since this can lead to unexpected processing of the preflighted
85+ // request or a 405 - method not allowed in Grails 2.3
86+ return ;
87+
88+ }
89+ } else if (checkOrigin (req , resp )) {
90+ if (exposeHeaders != null ) {
91+ resp .addHeader ("Access-Control-Expose-Headers" , exposeHeaders );
92+ }
93+ }
94+ }
4595 filterChain .doFilter (request , response );
4696 }
47- }
97+
98+ private boolean checkOrigin (HttpServletRequest req , HttpServletResponse resp ) {
99+ String origin = req .getHeader ("Origin" );
100+ if (origin == null ) {
101+ //no origin; per W3C specification, terminate further processing for both pre-flight and actual requests
102+ return false ;
103+ }
104+
105+ boolean matches = false ;
106+ //check if using regex to match origin
107+ if (allowOriginRegex != null ) {
108+ matches = allowOriginRegex .matcher (origin ).matches ();
109+ } else if (allowOrigin != null ) {
110+ matches = allowOrigin .equals ("*" ) || allowOrigin .equals (origin );
111+ }
112+
113+ if (matches ) {
114+ resp .addHeader ("Access-Control-Allow-Origin" , origin );
115+ resp .addHeader ("Access-Control-Allow-Credentials" , "true" );
116+ return true ;
117+ } else {
118+ return false ;
119+ }
120+ }
121+
122+ public void destroy () {
123+ }
124+ }
0 commit comments