Skip to content
This repository was archived by the owner on Jul 1, 2025. It is now read-only.

Commit a5bdfee

Browse files
committed
1) Enhance CORSFilter to incorporate grails-cors routine
2) Rename LongToStringFilter to StringToLongFilter to better represent what the filter does
1 parent d5f4957 commit a5bdfee

File tree

4 files changed

+104
-27
lines changed

4 files changed

+104
-27
lines changed
Lines changed: 97 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -16,32 +16,109 @@
1616

1717
package 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.*;
2420
import javax.servlet.http.HttpServletRequest;
2521
import javax.servlet.http.HttpServletResponse;
2622
import 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+
}

src/main/webapp/WEB-INF/web.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
</filter>
4545

4646
<filter>
47-
<filter-name>LongToStringFilter</filter-name>
48-
<filter-class>org.energyos.espi.common.utils.LongToStringFilter</filter-class>
47+
<filter-name>StringToLongFilter</filter-name>
48+
<filter-class>org.energyos.espi.common.utils.StringToLongFilter</filter-class>
4949
</filter>
5050

5151
<filter-mapping>
@@ -54,7 +54,7 @@
5454
</filter-mapping>
5555

5656
<filter-mapping>
57-
<filter-name>LongToStringFilter</filter-name>
57+
<filter-name>StringToLongFilter</filter-name>
5858
<url-pattern>/*</url-pattern>
5959
</filter-mapping>
6060

src/test/java/org/energyos/espi/thirdparty/integration/web/filters/CORSFilterTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ public void setup() {
4141
@Test
4242
public void optionsRequest_hasCorrectFilters() throws Exception {
4343
mockMvc.perform(options("/"))
44-
.andExpect(header().string("Access-Control-Allow-Origin", is("*")))
45-
.andExpect(header().string("Access-Control-Allow-Methods", is("GET, POST, PUT, DELETE")))
46-
.andExpect(header().string("Access-Control-Allow-Headers", is("Content-Type, Authorization")))
44+
.andExpect(header().string("Access-Control-Allow-Methods", is("GET, POST, PUT, DELETE, OPTIONS")))
45+
.andExpect(header().string("Access-Control-Allow-Headers", is("origin, authorization, accept, content-type")))
46+
.andExpect(header().string("Access-Control-Allow-Credentials", is("true")))
4747
.andExpect(header().string("Access-Control-Max-Age", is("1800")));
4848
}
4949
}

src/test/java/org/energyos/espi/thirdparty/web/filter/CORSFilterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void testDoFilterInternal() throws Exception {
1818
MockHttpServletRequest request = new MockHttpServletRequest();
1919
MockHttpServletResponse response = new MockHttpServletResponse();
2020

21-
corsFilter.doFilterInternal(request, response, filterChain);
21+
corsFilter.doFilter(request, response, filterChain);
2222

2323
verify(filterChain).doFilter(request, response);
2424
}

0 commit comments

Comments
 (0)