View | Details | Raw Unified | Return to bug 55046
Collapse All | Expand All

(-)test/org/apache/catalina/filters/MockFilterChain.java (+17 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.io.IOException;
4
5
import javax.servlet.FilterChain;
6
import javax.servlet.ServletException;
7
import javax.servlet.ServletRequest;
8
import javax.servlet.ServletResponse;
9
10
public class MockFilterChain implements FilterChain {
11
12
    public void doFilter(ServletRequest request, ServletResponse response)
13
            throws IOException, ServletException {
14
        // NoOp
15
    }
16
17
}
0
  + native
18
  + native
(-)test/org/apache/catalina/filters/MockServletContext.java (+279 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.io.InputStream;
4
import java.net.MalformedURLException;
5
import java.net.URL;
6
import java.util.Enumeration;
7
import java.util.EventListener;
8
import java.util.Map;
9
import java.util.Set;
10
11
import javax.servlet.Filter;
12
import javax.servlet.FilterRegistration;
13
import javax.servlet.RequestDispatcher;
14
import javax.servlet.Servlet;
15
import javax.servlet.ServletContext;
16
import javax.servlet.ServletException;
17
import javax.servlet.ServletRegistration;
18
import javax.servlet.ServletRegistration.Dynamic;
19
import javax.servlet.SessionCookieConfig;
20
import javax.servlet.SessionTrackingMode;
21
import javax.servlet.descriptor.JspConfigDescriptor;
22
23
public class MockServletContext implements ServletContext {
24
25
    public String getContextPath() {
26
        throw new RuntimeException("Not implemented");
27
    }
28
29
    public ServletContext getContext(String uripath) {
30
        throw new RuntimeException("Not implemented");
31
    }
32
33
    public int getMajorVersion() {
34
        throw new RuntimeException("Not implemented");
35
    }
36
37
    public int getMinorVersion() {
38
        throw new RuntimeException("Not implemented");
39
    }
40
41
    public String getMimeType(String file) {
42
        throw new RuntimeException("Not implemented");
43
    }
44
45
    public Set getResourcePaths(String path) {
46
        throw new RuntimeException("Not implemented");
47
    }
48
49
    public URL getResource(String path) throws MalformedURLException {
50
        throw new RuntimeException("Not implemented");
51
    }
52
53
    public InputStream getResourceAsStream(String path) {
54
        throw new RuntimeException("Not implemented");
55
    }
56
57
    public RequestDispatcher getRequestDispatcher(String path) {
58
59
        throw new RuntimeException("Not implemented");
60
    }
61
62
    public RequestDispatcher getNamedDispatcher(String name) {
63
64
        throw new RuntimeException("Not implemented");
65
    }
66
67
    public Servlet getServlet(String name) throws ServletException {
68
69
        throw new RuntimeException("Not implemented");
70
    }
71
72
    public Enumeration getServlets() {
73
74
        throw new RuntimeException("Not implemented");
75
    }
76
77
    public Enumeration getServletNames() {
78
79
        throw new RuntimeException("Not implemented");
80
    }
81
82
    public void log(String msg) {
83
        // NOOP
84
    }
85
86
    public void log(Exception exception, String msg) {
87
        // NOOP
88
    }
89
90
    public void log(String message, Throwable throwable) {
91
        // NOOP
92
    }
93
94
    public String getRealPath(String path) {
95
96
        throw new RuntimeException("Not implemented");
97
    }
98
99
    public String getServerInfo() {
100
101
        throw new RuntimeException("Not implemented");
102
    }
103
104
    public String getInitParameter(String name) {
105
106
        throw new RuntimeException("Not implemented");
107
    }
108
109
    public Enumeration getInitParameterNames() {
110
111
        throw new RuntimeException("Not implemented");
112
    }
113
114
    public Object getAttribute(String name) {
115
116
        throw new RuntimeException("Not implemented");
117
    }
118
119
    public Enumeration getAttributeNames() {
120
121
        throw new RuntimeException("Not implemented");
122
    }
123
124
    public void setAttribute(String name, Object object) {
125
        throw new RuntimeException("Not implemented");
126
    }
127
128
    public void removeAttribute(String name) {
129
        throw new RuntimeException("Not implemented");
130
    }
131
132
    public String getServletContextName() {
133
        throw new RuntimeException("Not implemented");
134
    }
135
136
    @Override
137
    public int getEffectiveMajorVersion() {
138
        throw new RuntimeException("Not implemented");
139
    }
140
141
    @Override
142
    public int getEffectiveMinorVersion() {
143
        throw new RuntimeException("Not implemented");
144
    }
145
146
    @Override
147
    public boolean setInitParameter(String name, String value) {
148
        throw new RuntimeException("Not implemented");
149
    }
150
151
    @Override
152
    public Dynamic addServlet(String servletName, String className) {
153
        throw new RuntimeException("Not implemented");
154
    }
155
156
    @Override
157
    public Dynamic addServlet(String servletName, Servlet servlet) {
158
        throw new RuntimeException("Not implemented");
159
    }
160
161
    @Override
162
    public Dynamic addServlet(String servletName,
163
            Class<? extends Servlet> servletClass) {
164
        throw new RuntimeException("Not implemented");
165
    }
166
167
    @Override
168
    public <T extends Servlet> T createServlet(Class<T> c)
169
            throws ServletException {
170
        throw new RuntimeException("Not implemented");
171
    }
172
173
    @Override
174
    public ServletRegistration getServletRegistration(String servletName) {
175
        throw new RuntimeException("Not implemented");
176
    }
177
178
    @Override
179
    public Map<String, ? extends ServletRegistration> getServletRegistrations() {
180
        throw new RuntimeException("Not implemented");
181
    }
182
183
    @Override
184
    public javax.servlet.FilterRegistration.Dynamic addFilter(
185
            String filterName, String className) {
186
        throw new RuntimeException("Not implemented");
187
    }
188
189
    @Override
190
    public javax.servlet.FilterRegistration.Dynamic addFilter(
191
            String filterName, Filter filter) {
192
        throw new RuntimeException("Not implemented");
193
    }
194
195
    @Override
196
    public javax.servlet.FilterRegistration.Dynamic addFilter(
197
            String filterName, Class<? extends Filter> filterClass) {
198
        throw new RuntimeException("Not implemented");
199
    }
200
201
    @Override
202
    public <T extends Filter> T createFilter(Class<T> c)
203
            throws ServletException {
204
        throw new RuntimeException("Not implemented");
205
    }
206
207
    @Override
208
    public FilterRegistration getFilterRegistration(String filterName) {
209
        throw new RuntimeException("Not implemented");
210
    }
211
212
    @Override
213
    public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
214
        throw new RuntimeException("Not implemented");
215
    }
216
217
    @Override
218
    public SessionCookieConfig getSessionCookieConfig() {
219
        throw new RuntimeException("Not implemented");
220
    }
221
222
    @Override
223
    public void setSessionTrackingModes(
224
            Set<SessionTrackingMode> sessionTrackingModes) {
225
        throw new RuntimeException("Not implemented");
226
    }
227
228
    @Override
229
    public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
230
        throw new RuntimeException("Not implemented");
231
    }
232
233
    @Override
234
    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
235
        throw new RuntimeException("Not implemented");
236
    }
237
238
    @Override
239
    public void addListener(String className) {
240
        throw new RuntimeException("Not implemented");
241
    }
242
243
    @Override
244
    public <T extends EventListener> void addListener(T t) {
245
        throw new RuntimeException("Not implemented");
246
    }
247
248
    @Override
249
    public void addListener(Class<? extends EventListener> listenerClass) {
250
        throw new RuntimeException("Not implemented");
251
    }
252
253
    @Override
254
    public <T extends EventListener> T createListener(Class<T> c)
255
            throws ServletException {
256
        throw new RuntimeException("Not implemented");
257
    }
258
259
    @Override
260
    public JspConfigDescriptor getJspConfigDescriptor() {
261
        throw new RuntimeException("Not implemented");
262
    }
263
264
    @Override
265
    public ClassLoader getClassLoader() {
266
        throw new RuntimeException("Not implemented");
267
    }
268
269
    @Override
270
    public void declareRoles(String... roleNames) {
271
        throw new RuntimeException("Not implemented");
272
    }
273
274
    @Override
275
    public String getVirtualServerName() {
276
        throw new RuntimeException("Not implemented");
277
    }
278
279
}
0
  + native
280
  + native
(-)test/org/apache/catalina/filters/MockHttpServletRequest.java (+397 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.io.BufferedReader;
4
import java.io.IOException;
5
import java.io.UnsupportedEncodingException;
6
import java.security.Principal;
7
import java.util.ArrayList;
8
import java.util.Collection;
9
import java.util.Collections;
10
import java.util.Enumeration;
11
import java.util.HashMap;
12
import java.util.HashSet;
13
import java.util.List;
14
import java.util.Locale;
15
import java.util.Map;
16
17
import javax.servlet.AsyncContext;
18
import javax.servlet.DispatcherType;
19
import javax.servlet.RequestDispatcher;
20
import javax.servlet.ServletContext;
21
import javax.servlet.ServletException;
22
import javax.servlet.ServletInputStream;
23
import javax.servlet.ServletRequest;
24
import javax.servlet.ServletResponse;
25
import javax.servlet.http.Cookie;
26
import javax.servlet.http.HttpServletRequest;
27
import javax.servlet.http.HttpServletResponse;
28
import javax.servlet.http.HttpSession;
29
import javax.servlet.http.HttpUpgradeHandler;
30
import javax.servlet.http.Part;
31
32
public class MockHttpServletRequest implements HttpServletRequest {
33
34
    private Map<String, Object> attributes = new HashMap<String, Object>();
35
    private Map<String, List<String>> headers = new HashMap<String, List<String>>();
36
    private String method;
37
    private String contentType;
38
39
    public Object getAttribute(String name) {
40
        return attributes.get(name);
41
    }
42
43
    public Enumeration getAttributeNames() {
44
        return Collections.enumeration(attributes.keySet());
45
    }
46
47
    public String getCharacterEncoding() {
48
49
        throw new RuntimeException("Not implemented");
50
    }
51
52
    public void setCharacterEncoding(String env)
53
            throws UnsupportedEncodingException {
54
55
    }
56
57
    public int getContentLength() {
58
59
        throw new RuntimeException("Not implemented");
60
    }
61
62
    public String getContentType() {
63
        return this.contentType;
64
    }
65
66
    public void setContentType(String contentType) {
67
        this.contentType = contentType;
68
    }
69
70
    public ServletInputStream getInputStream() throws IOException {
71
72
        throw new RuntimeException("Not implemented");
73
    }
74
75
    public String getParameter(String name) {
76
77
        throw new RuntimeException("Not implemented");
78
    }
79
80
    public Enumeration getParameterNames() {
81
82
        throw new RuntimeException("Not implemented");
83
    }
84
85
    public String[] getParameterValues(String name) {
86
87
        throw new RuntimeException("Not implemented");
88
    }
89
90
    public Map getParameterMap() {
91
92
        throw new RuntimeException("Not implemented");
93
    }
94
95
    public String getProtocol() {
96
97
        throw new RuntimeException("Not implemented");
98
    }
99
100
    public String getScheme() {
101
102
        throw new RuntimeException("Not implemented");
103
    }
104
105
    public String getServerName() {
106
107
        throw new RuntimeException("Not implemented");
108
    }
109
110
    public int getServerPort() {
111
112
        throw new RuntimeException("Not implemented");
113
    }
114
115
    public BufferedReader getReader() throws IOException {
116
117
        throw new RuntimeException("Not implemented");
118
    }
119
120
    public String getRemoteAddr() {
121
122
        throw new RuntimeException("Not implemented");
123
    }
124
125
    public String getRemoteHost() {
126
127
        throw new RuntimeException("Not implemented");
128
    }
129
130
    public void setAttribute(String name, Object o) {
131
        attributes.put(name, o);
132
    }
133
134
    public void removeAttribute(String name) {
135
        attributes.remove(name);
136
    }
137
138
    public Locale getLocale() {
139
140
        throw new RuntimeException("Not implemented");
141
    }
142
143
    public Enumeration getLocales() {
144
145
        throw new RuntimeException("Not implemented");
146
    }
147
148
    public boolean isSecure() {
149
150
        throw new RuntimeException("Not implemented");
151
    }
152
153
    public RequestDispatcher getRequestDispatcher(String path) {
154
155
        throw new RuntimeException("Not implemented");
156
    }
157
158
    public String getRealPath(String path) {
159
160
        throw new RuntimeException("Not implemented");
161
    }
162
163
    public int getRemotePort() {
164
165
        throw new RuntimeException("Not implemented");
166
    }
167
168
    public String getLocalName() {
169
170
        throw new RuntimeException("Not implemented");
171
    }
172
173
    public String getLocalAddr() {
174
175
        throw new RuntimeException("Not implemented");
176
    }
177
178
    public int getLocalPort() {
179
180
        throw new RuntimeException("Not implemented");
181
    }
182
183
    public String getAuthType() {
184
185
        throw new RuntimeException("Not implemented");
186
    }
187
188
    public Cookie[] getCookies() {
189
190
        throw new RuntimeException("Not implemented");
191
    }
192
193
    public long getDateHeader(String name) {
194
195
        throw new RuntimeException("Not implemented");
196
    }
197
198
    public String getHeader(String name) {
199
        List<String> list = headers.get(name);
200
        if (list != null) {
201
            return CORSFilter.join(new HashSet<String>(list), ",");
202
        }
203
        return null;
204
    }
205
206
    public void setHeader(String name, String value) {
207
        List<String> values = new ArrayList<String>();
208
        values.add(value);
209
        headers.put(name, values);
210
    }
211
212
    public Enumeration getHeaders(String name) {
213
214
        throw new RuntimeException("Not implemented");
215
    }
216
217
    public Enumeration getHeaderNames() {
218
        return Collections.enumeration(headers.keySet());
219
    }
220
221
    public int getIntHeader(String name) {
222
223
        throw new RuntimeException("Not implemented");
224
    }
225
226
    public String getMethod() {
227
        return method;
228
    }
229
230
    public void setMethod(String method) {
231
        this.method = method;
232
    }
233
234
    public String getPathInfo() {
235
236
        throw new RuntimeException("Not implemented");
237
    }
238
239
    public String getPathTranslated() {
240
241
        throw new RuntimeException("Not implemented");
242
    }
243
244
    public String getContextPath() {
245
246
        throw new RuntimeException("Not implemented");
247
    }
248
249
    public String getQueryString() {
250
251
        throw new RuntimeException("Not implemented");
252
    }
253
254
    public String getRemoteUser() {
255
256
        throw new RuntimeException("Not implemented");
257
    }
258
259
    public boolean isUserInRole(String role) {
260
261
        throw new RuntimeException("Not implemented");
262
    }
263
264
    public Principal getUserPrincipal() {
265
266
        throw new RuntimeException("Not implemented");
267
    }
268
269
    public String getRequestedSessionId() {
270
271
        throw new RuntimeException("Not implemented");
272
    }
273
274
    public String getRequestURI() {
275
276
        throw new RuntimeException("Not implemented");
277
    }
278
279
    public StringBuffer getRequestURL() {
280
281
        throw new RuntimeException("Not implemented");
282
    }
283
284
    public String getServletPath() {
285
286
        throw new RuntimeException("Not implemented");
287
    }
288
289
    public HttpSession getSession(boolean create) {
290
291
        throw new RuntimeException("Not implemented");
292
    }
293
294
    public HttpSession getSession() {
295
296
        throw new RuntimeException("Not implemented");
297
    }
298
299
    public boolean isRequestedSessionIdValid() {
300
301
        throw new RuntimeException("Not implemented");
302
    }
303
304
    public boolean isRequestedSessionIdFromCookie() {
305
306
        throw new RuntimeException("Not implemented");
307
    }
308
309
    public boolean isRequestedSessionIdFromURL() {
310
311
        throw new RuntimeException("Not implemented");
312
    }
313
314
    public boolean isRequestedSessionIdFromUrl() {
315
316
        throw new RuntimeException("Not implemented");
317
    }
318
319
    @Override
320
    public long getContentLengthLong() {
321
        throw new RuntimeException("Not implemented");
322
    }
323
324
    @Override
325
    public ServletContext getServletContext() {
326
        throw new RuntimeException("Not implemented");
327
    }
328
329
    @Override
330
    public AsyncContext startAsync() throws IllegalStateException {
331
        throw new RuntimeException("Not implemented");
332
    }
333
334
    @Override
335
    public AsyncContext startAsync(ServletRequest servletRequest,
336
            ServletResponse servletResponse) throws IllegalStateException {
337
        throw new RuntimeException("Not implemented");
338
    }
339
340
    @Override
341
    public boolean isAsyncStarted() {
342
        throw new RuntimeException("Not implemented");
343
    }
344
345
    @Override
346
    public boolean isAsyncSupported() {
347
        throw new RuntimeException("Not implemented");
348
    }
349
350
    @Override
351
    public AsyncContext getAsyncContext() {
352
        throw new RuntimeException("Not implemented");
353
    }
354
355
    @Override
356
    public DispatcherType getDispatcherType() {
357
        throw new RuntimeException("Not implemented");
358
    }
359
360
    @Override
361
    public String changeSessionId() {
362
        throw new RuntimeException("Not implemented");
363
    }
364
365
    @Override
366
    public boolean authenticate(HttpServletResponse response)
367
            throws IOException, ServletException {
368
        throw new RuntimeException("Not implemented");
369
    }
370
371
    @Override
372
    public void login(String username, String password) throws ServletException {
373
        throw new RuntimeException("Not implemented");
374
    }
375
376
    @Override
377
    public void logout() throws ServletException {
378
        throw new RuntimeException("Not implemented");
379
    }
380
381
    @Override
382
    public Collection<Part> getParts() throws IOException, ServletException {
383
        throw new RuntimeException("Not implemented");
384
    }
385
386
    @Override
387
    public Part getPart(String name) throws IOException, ServletException {
388
        throw new RuntimeException("Not implemented");
389
    }
390
391
    @Override
392
    public <T extends HttpUpgradeHandler> T upgrade(
393
            Class<T> httpUpgradeHandlerClass) throws IOException {
394
        throw new RuntimeException("Not implemented");
395
    }
396
397
}
0
  + native
398
  + native
(-)test/org/apache/catalina/filters/MockFilterConfigs.java (+293 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.util.Enumeration;
4
5
import javax.servlet.FilterConfig;
6
import javax.servlet.ServletContext;
7
8
public class MockFilterConfigs {
9
    public static final String HTTPS_WWW_APACHE_ORG = "https://www.apache.org";
10
    public static final String HTTP_TOMCAT_APACHE_ORG =
11
            "http://tomcat.apache.org";
12
    public static final String EXPOSED_HEADERS = "X-CUSTOM-HEADER";
13
    /**
14
     * Any origin
15
     */
16
    public static final String ANY_ORIGIN = "*";
17
18
    public static final MockServletContext mockServletContext =
19
            new MockServletContext();
20
21
    public static FilterConfig getDefaultFilterConfig() {
22
        final String allowedHttpHeaders =
23
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
24
        final String allowedHttpMethods =
25
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
26
        final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
27
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
28
        final String supportCredentials =
29
                CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
30
        final String preflightMaxAge =
31
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
32
        final String loggingEnabled =
33
                CORSFilter.DEFAULT_LOGGING_ENABLED;
34
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
35
36
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
37
                allowedOrigins, exposedHeaders, supportCredentials,
38
                preflightMaxAge, loggingEnabled, decorateRequest);
39
    }
40
41
    public static FilterConfig getFilterConfigAnyOriginAndSupportsCredentials() {
42
        final String allowedHttpHeaders =
43
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
44
        final String allowedHttpMethods =
45
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT";
46
        final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
47
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
48
        final String supportCredentials = "true";
49
        final String preflightMaxAge =
50
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
51
        final String loggingEnabled =
52
                CORSFilter.DEFAULT_LOGGING_ENABLED;
53
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
54
55
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
56
                allowedOrigins, exposedHeaders, supportCredentials,
57
                preflightMaxAge, loggingEnabled, decorateRequest);
58
    }
59
60
    public static FilterConfig
61
            getFilterConfigAnyOriginAndSupportsCredentialsDisabled() {
62
        final String allowedHttpHeaders =
63
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
64
        final String allowedHttpMethods =
65
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT";
66
        final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
67
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
68
        final String supportCredentials = "false";
69
        final String preflightMaxAge =
70
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
71
        final String loggingEnabled =
72
                CORSFilter.DEFAULT_LOGGING_ENABLED;
73
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
74
75
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
76
                allowedOrigins, exposedHeaders, supportCredentials,
77
                preflightMaxAge, loggingEnabled, decorateRequest);
78
    }
79
80
    public static FilterConfig
81
            getFilterConfigSpecificOriginAndSupportsCredentialsDisabled() {
82
        final String allowedHttpHeaders =
83
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
84
        final String allowedHttpMethods =
85
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT";
86
        final String allowedOrigins =
87
                HTTP_TOMCAT_APACHE_ORG + "," + HTTPS_WWW_APACHE_ORG;
88
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
89
        final String supportCredentials = "false";
90
        final String preflightMaxAge =
91
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
92
        final String loggingEnabled =
93
                CORSFilter.DEFAULT_LOGGING_ENABLED;
94
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
95
96
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
97
                allowedOrigins, exposedHeaders, supportCredentials,
98
                preflightMaxAge, loggingEnabled, decorateRequest);
99
    }
100
101
    public static FilterConfig getFilterConfigWithExposedHeaders() {
102
        final String allowedHttpHeaders =
103
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
104
        final String allowedHttpMethods =
105
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
106
        final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
107
        final String exposedHeaders = EXPOSED_HEADERS;
108
        final String supportCredentials =
109
                CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
110
        final String preflightMaxAge =
111
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
112
        final String loggingEnabled =
113
                CORSFilter.DEFAULT_LOGGING_ENABLED;
114
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
115
116
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
117
                allowedOrigins, exposedHeaders, supportCredentials,
118
                preflightMaxAge, loggingEnabled, decorateRequest);
119
    }
120
121
    public static FilterConfig getSecureFilterConfig() {
122
        final String allowedHttpHeaders =
123
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
124
        final String allowedHttpMethods =
125
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT";
126
        final String allowedOrigins = HTTPS_WWW_APACHE_ORG;
127
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
128
        final String supportCredentials = "true";
129
        final String preflightMaxAge =
130
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
131
        final String loggingEnabled = "true";
132
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
133
134
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
135
                allowedOrigins, exposedHeaders, supportCredentials,
136
                preflightMaxAge, loggingEnabled, decorateRequest);
137
    }
138
139
    public static FilterConfig getNullFilterConfig() {
140
        return generateFilterConfig(null, null, null, null, null, null, null,
141
                null);
142
    }
143
144
    public static FilterConfig getSpecificOriginFilterConfig() {
145
        final String allowedOrigins =
146
                HTTPS_WWW_APACHE_ORG + "," + HTTP_TOMCAT_APACHE_ORG;
147
148
        final String allowedHttpHeaders =
149
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
150
        final String allowedHttpMethods =
151
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT";
152
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
153
        final String supportCredentials =
154
                CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
155
        final String preflightMaxAge =
156
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
157
        final String loggingEnabled =
158
                CORSFilter.DEFAULT_LOGGING_ENABLED;
159
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
160
161
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
162
                allowedOrigins, exposedHeaders, supportCredentials,
163
                preflightMaxAge, loggingEnabled, decorateRequest);
164
    }
165
166
    public static FilterConfig getSpecificOriginFilterConfigNegativeMaxAge() {
167
        final String allowedOrigins =
168
                HTTPS_WWW_APACHE_ORG + "," + HTTP_TOMCAT_APACHE_ORG;
169
170
        final String allowedHttpHeaders =
171
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
172
        final String allowedHttpMethods =
173
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS + ",PUT";
174
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
175
        final String supportCredentials =
176
                CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
177
        final String preflightMaxAge = "-1";
178
        final String loggingEnabled =
179
                CORSFilter.DEFAULT_LOGGING_ENABLED;
180
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
181
182
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
183
                allowedOrigins, exposedHeaders, supportCredentials,
184
                preflightMaxAge, loggingEnabled, decorateRequest);
185
    }
186
187
    public static FilterConfig getFilterConfigInvalidMaxPreflightAge() {
188
        final String allowedHttpHeaders =
189
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
190
        final String allowedHttpMethods =
191
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
192
        final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
193
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
194
        final String supportCredentials =
195
                CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
196
        final String preflightMaxAge = "abc";
197
        final String loggingEnabled =
198
                CORSFilter.DEFAULT_LOGGING_ENABLED;
199
        final String decorateRequest = CORSFilter.DEFAULT_DECORATE_REQUEST;
200
201
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
202
                allowedOrigins, exposedHeaders, supportCredentials,
203
                preflightMaxAge, loggingEnabled, decorateRequest);
204
    }
205
206
    public static FilterConfig getEmptyFilterConfig() {
207
        final String allowedHttpHeaders = "";
208
        final String allowedHttpMethods = "";
209
        final String allowedOrigins = "";
210
        final String exposedHeaders = "";
211
        final String supportCredentials = "";
212
        final String preflightMaxAge = "";
213
        final String loggingEnabled = "";
214
        final String decorateRequest = "";
215
216
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
217
                allowedOrigins, exposedHeaders, supportCredentials,
218
                preflightMaxAge, loggingEnabled, decorateRequest);
219
    }
220
221
    public static FilterConfig getFilterConfigDecorateRequestDisabled() {
222
        final String allowedHttpHeaders =
223
                CORSFilter.DEFAULT_ALLOWED_HTTP_HEADERS;
224
        final String allowedHttpMethods =
225
                CORSFilter.DEFAULT_ALLOWED_HTTP_METHODS;
226
        final String allowedOrigins = CORSFilter.DEFAULT_ALLOWED_ORIGINS;
227
        final String exposedHeaders = CORSFilter.DEFAULT_EXPOSED_HEADERS;
228
        final String supportCredentials =
229
                CORSFilter.DEFAULT_SUPPORTS_CREDENTIALS;
230
        final String preflightMaxAge =
231
                CORSFilter.DEFAULT_PREFLIGHT_MAXAGE;
232
        final String loggingEnabled =
233
                CORSFilter.DEFAULT_LOGGING_ENABLED;
234
        final String decorateRequest = "false";
235
236
        return generateFilterConfig(allowedHttpHeaders, allowedHttpMethods,
237
                allowedOrigins, exposedHeaders, supportCredentials,
238
                preflightMaxAge, loggingEnabled, decorateRequest);
239
    }
240
241
    private static FilterConfig generateFilterConfig(
242
            final String allowedHttpHeaders, final String allowedHttpMethods,
243
            final String allowedOrigins, final String exposedHeaders,
244
            final String supportCredentials, final String preflightMaxAge,
245
            final String loggingEnabled, final String decorateRequest) {
246
        FilterConfig filterConfig = new FilterConfig() {
247
248
            public String getFilterName() {
249
                return "cors-filter";
250
            }
251
252
            public ServletContext getServletContext() {
253
                return mockServletContext;
254
            }
255
256
            public String getInitParameter(String name) {
257
                if (CORSFilter.PARAM_CORS_ALLOWED_HEADERS
258
                        .equalsIgnoreCase(name)) {
259
                    return allowedHttpHeaders;
260
                } else if (CORSFilter.PARAM_CORS_ALLOWED_METHODS
261
                        .equalsIgnoreCase(name)) {
262
                    return allowedHttpMethods;
263
                } else if (CORSFilter.PARAM_CORS_ALLOWED_ORIGINS
264
                        .equalsIgnoreCase(name)) {
265
                    return allowedOrigins;
266
                } else if (CORSFilter.PARAM_CORS_EXPOSED_HEADERS
267
                        .equalsIgnoreCase(name)) {
268
                    return exposedHeaders;
269
                } else if (CORSFilter.PARAM_CORS_SUPPORT_CREDENTIALS
270
                        .equalsIgnoreCase(name)) {
271
                    return supportCredentials;
272
                } else if (CORSFilter.PARAM_CORS_PREFLIGHT_MAXAGE
273
                        .equalsIgnoreCase(name)) {
274
                    return preflightMaxAge;
275
                } else if (CORSFilter.PARAM_CORS_LOGGING_ENABLED
276
                        .equalsIgnoreCase(name)) {
277
                    return loggingEnabled;
278
                } else if (CORSFilter.PARAM_CORS_REQUEST_DECORATE
279
                        .equalsIgnoreCase(name)) {
280
                    return decorateRequest;
281
                }
282
                return null;
283
            }
284
285
            @SuppressWarnings("rawtypes")
286
            public Enumeration getInitParameterNames() {
287
                return null;
288
            }
289
        };
290
291
        return filterConfig;
292
    }
293
}
0
  + native
294
  + native
(-)test/org/apache/catalina/filters/TestCORSFilter.java (+1402 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.io.IOException;
4
import java.util.LinkedHashSet;
5
import java.util.Set;
6
7
import javax.servlet.FilterChain;
8
import javax.servlet.ServletException;
9
import javax.servlet.http.HttpServletRequest;
10
import javax.servlet.http.HttpServletResponse;
11
12
import org.junit.Assert;
13
import org.junit.Test;
14
15
public class TestCORSFilter {
16
    private FilterChain filterChain = new MockFilterChain();
17
18
    /**
19
     * Tests if a GET request is treated as simple request.
20
     * 
21
     * @See http://www.w3.org/TR/cors/#simple-method
22
     * @throws IOException
23
     * @throws ServletException
24
     */
25
    @Test
26
    public void testDoFilterSimpleGET() throws IOException, ServletException {
27
        MockHttpServletRequest request = new MockHttpServletRequest();
28
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
29
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
30
        request.setMethod("GET");
31
        MockHttpServletResponse response = new MockHttpServletResponse();
32
33
        CORSFilter corsFilter = new CORSFilter();
34
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
35
        corsFilter.doFilter(request, response, filterChain);
36
37
        Assert.assertTrue(response.getHeader(
38
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
39
                "https://www.apache.org"));
40
        Assert.assertTrue((Boolean) request
41
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
42
        Assert.assertTrue(request.getAttribute(
43
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
44
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
45
        Assert.assertTrue(request.getAttribute(
46
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
47
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
48
    }
49
50
    /**
51
     * Tests if a POST request is treated as simple request.
52
     * 
53
     * @See http://www.w3.org/TR/cors/#simple-method
54
     * @throws IOException
55
     * @throws ServletException
56
     */
57
    @Test
58
    public void testDoFilterSimplePOST() throws IOException, ServletException {
59
        MockHttpServletRequest request = new MockHttpServletRequest();
60
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
61
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
62
        request.setContentType("text/plain");
63
        request.setMethod("POST");
64
        MockHttpServletResponse response = new MockHttpServletResponse();
65
66
        CORSFilter corsFilter = new CORSFilter();
67
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
68
        corsFilter.doFilter(request, response, filterChain);
69
70
        Assert.assertTrue(response.getHeader(
71
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
72
                "https://www.apache.org"));
73
        Assert.assertTrue((Boolean) request
74
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
75
        Assert.assertTrue(request.getAttribute(
76
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
77
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
78
        Assert.assertTrue(request.getAttribute(
79
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
80
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
81
    }
82
83
    /**
84
     * Tests if a HEAD request is treated as simple request.
85
     * 
86
     * @See http://www.w3.org/TR/cors/#simple-method
87
     * @throws IOException
88
     * @throws ServletException
89
     */
90
    @Test
91
    public void testDoFilterSimpleHEAD() throws IOException, ServletException {
92
        MockHttpServletRequest request = new MockHttpServletRequest();
93
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
94
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
95
        request.setMethod("HEAD");
96
        MockHttpServletResponse response = new MockHttpServletResponse();
97
98
        CORSFilter corsFilter = new CORSFilter();
99
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
100
        corsFilter.doFilter(request, response, filterChain);
101
102
        Assert.assertTrue(response.getHeader(
103
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
104
                "https://www.apache.org"));
105
        Assert.assertTrue((Boolean) request
106
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
107
        Assert.assertTrue(request.getAttribute(
108
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
109
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
110
        Assert.assertTrue(request.getAttribute(
111
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
112
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
113
    }
114
115
    /**
116
     * Test the presence of specific origin in response, when '*' is not used.
117
     * 
118
     * @throws IOException
119
     * @throws ServletException
120
     */
121
    @Test
122
    public void testDoFilterSimpleSpecificHeader() throws IOException,
123
            ServletException {
124
        MockHttpServletRequest request = new MockHttpServletRequest();
125
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
126
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
127
        request.setMethod("POST");
128
        request.setContentType("text/plain");
129
        MockHttpServletResponse response = new MockHttpServletResponse();
130
131
        CORSFilter corsFilter = new CORSFilter();
132
        corsFilter.init(MockFilterConfigs.getSpecificOriginFilterConfig());
133
        corsFilter.doFilter(request, response, filterChain);
134
135
        Assert.assertTrue(response.getHeader(
136
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
137
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
138
        Assert.assertTrue((Boolean) request
139
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
140
        Assert.assertTrue(request.getAttribute(
141
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
142
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
143
        Assert.assertTrue(request.getAttribute(
144
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
145
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
146
    }
147
148
    /**
149
     * Tests the prsence of the origin (and not '*') in the response, when
150
     * supports credentials is enabled alongwith any origin, '*'.
151
     * 
152
     * @throws IOException
153
     * @throws ServletException
154
     */
155
    @Test
156
    public void testDoFilterSimpleAnyOriginAndSupportsCredentials()
157
            throws IOException, ServletException {
158
        MockHttpServletRequest request = new MockHttpServletRequest();
159
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
160
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
161
        request.setMethod("GET");
162
        MockHttpServletResponse response = new MockHttpServletResponse();
163
164
        CORSFilter corsFilter = new CORSFilter();
165
        corsFilter.init(MockFilterConfigs
166
                .getFilterConfigAnyOriginAndSupportsCredentials());
167
        corsFilter.doFilter(request, response, filterChain);
168
169
        Assert.assertTrue(response.getHeader(
170
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
171
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
172
        Assert.assertTrue(response.getHeader(
173
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS)
174
                .equals(
175
                        "true"));
176
        Assert.assertTrue((Boolean) request
177
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
178
        Assert.assertTrue(request.getAttribute(
179
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
180
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
181
        Assert.assertTrue(request.getAttribute(
182
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
183
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
184
    }
185
186
    /**
187
     * Tests the presence of the origin (and not '*') in the response, when
188
     * supports credentials is enabled alongwith any origin, '*'.
189
     * 
190
     * @throws IOException
191
     * @throws ServletException
192
     */
193
    @Test
194
    public void testDoFilterSimpleAnyOriginAndSupportsCredentialsDisabled()
195
            throws IOException, ServletException {
196
        MockHttpServletRequest request = new MockHttpServletRequest();
197
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
198
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
199
        request.setMethod("GET");
200
        MockHttpServletResponse response = new MockHttpServletResponse();
201
202
        CORSFilter corsFilter = new CORSFilter();
203
        corsFilter.init(MockFilterConfigs
204
                .getFilterConfigAnyOriginAndSupportsCredentialsDisabled());
205
        corsFilter.doFilter(request, response, filterChain);
206
207
        Assert.assertTrue(response.getHeader(
208
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
209
                MockFilterConfigs.ANY_ORIGIN));
210
        Assert.assertNull(response.getHeader(
211
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
212
        Assert.assertTrue((Boolean) request
213
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
214
        Assert.assertTrue(request.getAttribute(
215
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
216
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
217
        Assert.assertTrue(request.getAttribute(
218
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
219
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
220
    }
221
222
    /**
223
     * Tests the presence of exposed headers in response, if configured.
224
     * 
225
     * @throws IOException
226
     * @throws ServletException
227
     */
228
    @Test
229
    public void testDoFilterSimpleWithExposedHeaders() throws IOException,
230
            ServletException {
231
        MockHttpServletRequest request = new MockHttpServletRequest();
232
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
233
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
234
        request.setMethod("POST");
235
        request.setContentType("text/plain");
236
        MockHttpServletResponse response = new MockHttpServletResponse();
237
238
        CORSFilter corsFilter = new CORSFilter();
239
        corsFilter.init(MockFilterConfigs
240
                .getFilterConfigWithExposedHeaders());
241
        corsFilter.doFilter(request, response, filterChain);
242
243
        Assert.assertTrue(response.getHeader(
244
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
245
                "https://www.apache.org"));
246
        Assert.assertTrue(response.getHeader(
247
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS)
248
                .equals(MockFilterConfigs.EXPOSED_HEADERS));
249
        Assert.assertTrue((Boolean) request
250
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
251
        Assert.assertTrue(request.getAttribute(
252
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
253
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
254
        Assert.assertTrue(request.getAttribute(
255
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
256
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
257
    }
258
259
    /**
260
     * Checks if an OPTIONS request is processed as pre-flight.
261
     * 
262
     * @throws IOException
263
     * @throws ServletException
264
     */
265
    @Test
266
    public void testDoFilterPreflight() throws IOException, ServletException {
267
        MockHttpServletRequest request = new MockHttpServletRequest();
268
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
269
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
270
        request.setHeader(
271
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
272
        request.setHeader(
273
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
274
                "Content-Type");
275
        request.setMethod("OPTIONS");
276
        MockHttpServletResponse response = new MockHttpServletResponse();
277
278
        CORSFilter corsFilter = new CORSFilter();
279
        corsFilter.init(MockFilterConfigs
280
                .getSpecificOriginFilterConfig());
281
        corsFilter.doFilter(request, response, filterChain);
282
283
        Assert.assertTrue(response.getHeader(
284
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
285
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
286
        Assert.assertTrue((Boolean) request
287
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
288
        Assert.assertTrue(request.getAttribute(
289
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
290
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
291
        Assert.assertTrue(request.getAttribute(
292
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
293
                CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
294
        Assert.assertTrue(request.getAttribute(
295
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
296
                "Content-Type"));
297
    }
298
299
    /**
300
     * Checks if an OPTIONS request is processed as pre-flight where any origin
301
     * is enabled.
302
     * 
303
     * @throws IOException
304
     * @throws ServletException
305
     */
306
    @Test
307
    public void testDoFilterPreflightAnyOrigin() throws IOException,
308
            ServletException {
309
        MockHttpServletRequest request = new MockHttpServletRequest();
310
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
311
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
312
        request.setHeader(
313
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
314
        request.setHeader(
315
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
316
                "Content-Type");
317
        request.setMethod("OPTIONS");
318
        MockHttpServletResponse response = new MockHttpServletResponse();
319
320
        CORSFilter corsFilter = new CORSFilter();
321
        corsFilter.init(MockFilterConfigs
322
                .getSpecificOriginFilterConfig());
323
        corsFilter.doFilter(request, response, filterChain);
324
325
        Assert.assertTrue(response.getHeader(
326
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
327
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
328
        Assert.assertTrue((Boolean) request
329
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
330
        Assert.assertTrue(request.getAttribute(
331
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
332
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
333
        Assert.assertTrue(request.getAttribute(
334
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
335
                CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
336
        Assert.assertTrue(request.getAttribute(
337
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
338
                "Content-Type"));
339
    }
340
341
    /**
342
     * Checks if an OPTIONS request is processed as pre-flight.
343
     * 
344
     * @throws IOException
345
     * @throws ServletException
346
     */
347
    @Test
348
    public void testDoFilterPreflightInvalidOrigin() throws IOException,
349
            ServletException {
350
        MockHttpServletRequest request = new MockHttpServletRequest();
351
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
352
                "http://www.example.com");
353
        request.setHeader(
354
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
355
        request.setHeader(
356
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
357
                "Content-Type");
358
        request.setMethod("OPTIONS");
359
        MockHttpServletResponse response = new MockHttpServletResponse();
360
361
        CORSFilter corsFilter = new CORSFilter();
362
        corsFilter.init(MockFilterConfigs
363
                .getSpecificOriginFilterConfig());
364
        corsFilter.doFilter(request, response, filterChain);
365
366
        Assert.assertEquals(response.getStatus(),
367
                HttpServletResponse.SC_FORBIDDEN);
368
    }
369
370
    @Test
371
    public void testDoFilterPreflightNegativeMaxAge() throws IOException,
372
            ServletException {
373
        MockHttpServletRequest request = new MockHttpServletRequest();
374
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
375
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
376
        request.setHeader(
377
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
378
        request.setHeader(
379
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
380
                "Content-Type");
381
        request.setMethod("OPTIONS");
382
        MockHttpServletResponse response = new MockHttpServletResponse();
383
384
        CORSFilter corsFilter = new CORSFilter();
385
        corsFilter.init(MockFilterConfigs
386
                .getSpecificOriginFilterConfigNegativeMaxAge());
387
        corsFilter.doFilter(request, response, filterChain);
388
389
        Assert.assertTrue(response.getHeader(
390
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
391
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
392
        Assert.assertNull(response.getHeader(
393
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE));
394
        Assert.assertTrue((Boolean) request
395
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
396
        Assert.assertTrue(request.getAttribute(
397
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
398
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
399
        Assert.assertTrue(request.getAttribute(
400
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
401
                CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
402
        Assert.assertTrue(request.getAttribute(
403
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
404
                "Content-Type"));
405
    }
406
407
    @Test
408
    public void testDoFilterPreflightWithCredentials() throws IOException,
409
            ServletException {
410
        MockHttpServletRequest request = new MockHttpServletRequest();
411
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
412
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
413
        request.setHeader(
414
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
415
        request.setHeader(
416
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
417
                "Content-Type");
418
        request.setMethod("OPTIONS");
419
        MockHttpServletResponse response = new MockHttpServletResponse();
420
421
        CORSFilter corsFilter = new CORSFilter();
422
        corsFilter.init(MockFilterConfigs
423
                .getSecureFilterConfig());
424
        corsFilter.doFilter(request, response, filterChain);
425
426
        Assert.assertTrue(response.getHeader(
427
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
428
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
429
        Assert.assertTrue(response.getHeader(
430
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS)
431
                .equals("true"));
432
        Assert.assertTrue((Boolean) request
433
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
434
        Assert.assertTrue(request.getAttribute(
435
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
436
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
437
        Assert.assertTrue(request.getAttribute(
438
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
439
                CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
440
        Assert.assertTrue(request.getAttribute(
441
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
442
                "Content-Type"));
443
    }
444
445
    @Test
446
    public void testDoFilterPreflightWithoutCredentialsAndSpecificOrigin()
447
            throws IOException,
448
            ServletException {
449
        MockHttpServletRequest request = new MockHttpServletRequest();
450
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
451
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
452
        request.setHeader(
453
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
454
        request.setHeader(
455
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
456
                "Content-Type");
457
        request.setMethod("OPTIONS");
458
        MockHttpServletResponse response = new MockHttpServletResponse();
459
460
        CORSFilter corsFilter = new CORSFilter();
461
        corsFilter.init(MockFilterConfigs
462
                .getFilterConfigSpecificOriginAndSupportsCredentialsDisabled());
463
        corsFilter.doFilter(request, response, filterChain);
464
465
        Assert.assertTrue(response.getHeader(
466
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
467
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
468
        Assert.assertNull(response.getHeader(
469
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
470
        Assert.assertTrue((Boolean) request
471
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
472
        Assert.assertTrue(request.getAttribute(
473
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
474
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
475
        Assert.assertTrue(request.getAttribute(
476
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
477
                CORSFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
478
        Assert.assertTrue(request.getAttribute(
479
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals(
480
                "Content-Type"));
481
    }
482
483
    /**
484
     * Negative test, when a CORS request arrives, with a null origin.
485
     */
486
    @Test
487
    public void testDoFilterNullOrigin() throws IOException, ServletException {
488
        MockHttpServletRequest request = new MockHttpServletRequest();
489
490
        request.setMethod("POST");
491
        request.setContentType("text/plain");
492
        MockHttpServletResponse response = new MockHttpServletResponse();
493
494
        CORSFilter corsFilter = new CORSFilter();
495
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
496
        CORSFilter.CORSRequestType requestType =
497
                corsFilter.checkRequestType(request);
498
        Assert.assertEquals(CORSFilter.CORSRequestType.NOT_CORS, requestType);
499
500
        corsFilter.doFilter(request, response, filterChain);
501
502
        Assert.assertFalse((Boolean) request
503
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
504
    }
505
506
    @Test
507
    public void testDoFilterInvalidCORSOriginNotAllowed() throws IOException,
508
            ServletException {
509
        MockHttpServletRequest request = new MockHttpServletRequest();
510
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
511
                "www.google.com");
512
        request.setMethod("POST");
513
        MockHttpServletResponse response = new MockHttpServletResponse();
514
515
        CORSFilter corsFilter = new CORSFilter();
516
        corsFilter.init(MockFilterConfigs.getSpecificOriginFilterConfig());
517
        corsFilter.doFilter(request, response, filterChain);
518
519
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
520
                response.getStatus());
521
    }
522
523
    @Test(expected = ServletException.class)
524
    public void testDoFilterNullRequestNullResponse() throws IOException,
525
            ServletException {
526
        CORSFilter corsFilter = new CORSFilter();
527
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
528
        corsFilter.doFilter(null, null, filterChain);
529
    }
530
531
    @Test(expected = ServletException.class)
532
    public void testDoFilterNullRequestResponse() throws IOException,
533
            ServletException {
534
        MockHttpServletResponse response = new MockHttpServletResponse();
535
        CORSFilter corsFilter = new CORSFilter();
536
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
537
        corsFilter.doFilter(null, response, filterChain);
538
    }
539
540
    @Test(expected = ServletException.class)
541
    public void testDoFilterRequestNullResponse() throws IOException,
542
            ServletException {
543
        MockHttpServletRequest request = new MockHttpServletRequest();
544
        CORSFilter corsFilter = new CORSFilter();
545
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
546
        corsFilter.doFilter(request, null, filterChain);
547
    }
548
549
    @Test
550
    public void testInitDefaultFilterConfig() throws IOException,
551
            ServletException {
552
        MockHttpServletRequest request = new MockHttpServletRequest();
553
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
554
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
555
        request.setMethod("GET");
556
        MockHttpServletResponse response = new MockHttpServletResponse();
557
558
        CORSFilter corsFilter = new CORSFilter();
559
        corsFilter.init(null);
560
        corsFilter.doFilter(request, response, filterChain);
561
562
        Assert.assertTrue(response.getHeader(
563
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
564
                "https://www.apache.org"));
565
        Assert.assertTrue((Boolean) request
566
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
567
        Assert.assertTrue(request.getAttribute(
568
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN).equals(
569
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG));
570
        Assert.assertTrue(request.getAttribute(
571
                CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE).equals(
572
                CORSFilter.CORSRequestType.SIMPLE.name().toLowerCase()));
573
    }
574
575
    @Test(expected = ServletException.class)
576
    public void testInitInvalidFilterConfig() throws IOException,
577
            ServletException {
578
        CORSFilter corsFilter = new CORSFilter();
579
        corsFilter.init(MockFilterConfigs.getFilterConfigInvalidMaxPreflightAge());
580
        // If we don't get an exception at this point, then all mocked objects
581
        // worked as expected.
582
    }
583
584
    /**
585
     * Tests if a non-simple request is given to simple request handler.
586
     * 
587
     * @throws IOException
588
     * @throws ServletException
589
     */
590
    @Test(expected = IllegalArgumentException.class)
591
    public void testNotSimple() throws IOException, ServletException {
592
        MockHttpServletRequest request = new MockHttpServletRequest();
593
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
594
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
595
        request.setHeader(
596
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
597
        request.setHeader(
598
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
599
                "Content-Type");
600
        request.setMethod("OPTIONS");
601
        MockHttpServletResponse response = new MockHttpServletResponse();
602
603
        CORSFilter corsFilter = new CORSFilter();
604
        corsFilter.init(MockFilterConfigs
605
                .getDefaultFilterConfig());
606
        corsFilter.handleSimpleCORS(request, response, filterChain);
607
    }
608
609
    /**
610
     * When a non-preflight request is given to a pre-flight requets handler.
611
     * 
612
     * @throws IOException
613
     * @throws ServletException
614
     */
615
    @Test(expected = IllegalArgumentException.class)
616
    public void testNotPreflight() throws IOException, ServletException {
617
        MockHttpServletRequest request = new MockHttpServletRequest();
618
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
619
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
620
        request.setMethod("GET");
621
        MockHttpServletResponse response = new MockHttpServletResponse();
622
623
        CORSFilter corsFilter = new CORSFilter();
624
        corsFilter.init(MockFilterConfigs.getDefaultFilterConfig());
625
        corsFilter.handlePreflightCORS(request, response, filterChain);
626
    }
627
628
    @Test(expected = IllegalArgumentException.class)
629
    public void testDecorateCORSPropertiesNullRequestNullCORSRequestType() {
630
        CORSFilter.decorateCORSProperties(null, null);
631
    }
632
633
    @Test(expected = IllegalArgumentException.class)
634
    public void testDecorateCORSPropertiesNullRequestValidCORSRequestType() {
635
        CORSFilter.decorateCORSProperties(null,
636
                CORSFilter.CORSRequestType.SIMPLE);
637
    }
638
639
    @Test(expected = IllegalArgumentException.class)
640
    public void testDecorateCORSPropertiesValidRequestNullRequestType() {
641
        MockHttpServletRequest request = new MockHttpServletRequest();
642
        CORSFilter.decorateCORSProperties(request, null);
643
    }
644
645
    @Test
646
    public void testDecorateCORSPropertiesCORSRequestTypeNotCORS() {
647
        MockHttpServletRequest request = new MockHttpServletRequest();
648
        CORSFilter.decorateCORSProperties(request,
649
                CORSFilter.CORSRequestType.NOT_CORS);
650
        Assert.assertFalse((Boolean) request
651
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
652
    }
653
654
    @Test
655
    public void testDecorateCORSPropertiesCORSRequestTypeInvalidCORS() {
656
        MockHttpServletRequest request = new MockHttpServletRequest();
657
        CORSFilter
658
                .decorateCORSProperties(request,
659
                        CORSFilter.CORSRequestType.INVALID_CORS);
660
        Assert.assertNull(request
661
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
662
    }
663
664
    @Test
665
    public void testCheckSimpleRequestTypeAnyOrigin() throws ServletException {
666
        MockHttpServletRequest request = new MockHttpServletRequest();
667
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
668
                "http://www.w3.org");
669
        request.setMethod("GET");
670
        CORSFilter corsFilter = new CORSFilter();
671
        corsFilter.init(MockFilterConfigs
672
                .getDefaultFilterConfig());
673
        CORSFilter.CORSRequestType requestType =
674
                corsFilter.checkRequestType(request);
675
        Assert.assertEquals(CORSFilter.CORSRequestType.SIMPLE, requestType);
676
    }
677
678
    /**
679
     * Happy path test, when a valid CORS Simple request arrives.
680
     * 
681
     * @throws ServletException
682
     */
683
    @Test
684
    public void testCheckSimpleRequestType() throws ServletException {
685
        MockHttpServletRequest request = new MockHttpServletRequest();
686
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
687
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
688
        request.setMethod("GET");
689
        CORSFilter corsFilter = new CORSFilter();
690
        corsFilter.init(MockFilterConfigs
691
                .getDefaultFilterConfig());
692
        CORSFilter.CORSRequestType requestType =
693
                corsFilter.checkRequestType(request);
694
        Assert.assertEquals(CORSFilter.CORSRequestType.SIMPLE, requestType);
695
    }
696
697
    /**
698
     * Happy path test, when a valid CORS Simple request arrives.
699
     * 
700
     * @throws ServletException
701
     */
702
    @Test
703
    public void testCheckActualRequestType() throws ServletException {
704
        MockHttpServletRequest request = new MockHttpServletRequest();
705
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
706
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
707
        request.setMethod("PUT");
708
        CORSFilter corsFilter = new CORSFilter();
709
        corsFilter.init(MockFilterConfigs
710
                .getDefaultFilterConfig());
711
        CORSFilter.CORSRequestType requestType =
712
                corsFilter.checkRequestType(request);
713
        Assert.assertEquals(CORSFilter.CORSRequestType.ACTUAL, requestType);
714
    }
715
716
    /**
717
     * Happy path test, when a valid CORS Simple request arrives.
718
     * 
719
     * @throws ServletException
720
     */
721
    @Test
722
    public void testCheckActualRequestTypeMethodPOSTNotSimpleHeaders()
723
            throws ServletException {
724
        MockHttpServletRequest request = new MockHttpServletRequest();
725
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
726
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
727
        request.setMethod("POST");
728
        request.setContentType("application/json");
729
        CORSFilter corsFilter = new CORSFilter();
730
        corsFilter.init(MockFilterConfigs
731
                .getDefaultFilterConfig());
732
        CORSFilter.CORSRequestType requestType =
733
                corsFilter.checkRequestType(request);
734
        Assert.assertEquals(CORSFilter.CORSRequestType.ACTUAL, requestType);
735
    }
736
737
    /**
738
     * Happy path test, when a valid CORS Pre-flight request arrives.
739
     * 
740
     * @throws ServletException
741
     */
742
    @Test
743
    public void testCheckPreFlightRequestType() throws ServletException {
744
        MockHttpServletRequest request = new MockHttpServletRequest();
745
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
746
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
747
        request.setHeader(
748
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
749
                "PUT");
750
        request.setHeader(
751
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
752
                "Content-Type");
753
        request.setMethod("OPTIONS");
754
        CORSFilter corsFilter = new CORSFilter();
755
        corsFilter.init(MockFilterConfigs
756
                .getDefaultFilterConfig());
757
        CORSFilter.CORSRequestType requestType =
758
                corsFilter.checkRequestType(request);
759
        Assert.assertEquals(CORSFilter.CORSRequestType.PRE_FLIGHT, requestType);
760
    }
761
762
    /**
763
     * when a valid CORS Pre-flight request arrives, with no
764
     * Access-Control-Request-Method
765
     * 
766
     * @throws ServletException
767
     * @throws IOException
768
     */
769
    @Test
770
    public void testCheckPreFlightRequestTypeNoACRM() throws ServletException,
771
            IOException {
772
        MockHttpServletRequest request = new MockHttpServletRequest();
773
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
774
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
775
776
        request.setMethod("OPTIONS");
777
        CORSFilter corsFilter = new CORSFilter();
778
        corsFilter.init(MockFilterConfigs
779
                .getDefaultFilterConfig());
780
        CORSFilter.CORSRequestType requestType =
781
                corsFilter.checkRequestType(request);
782
        Assert.assertEquals(CORSFilter.CORSRequestType.ACTUAL, requestType);
783
    }
784
785
    /**
786
     * when a valid CORS Pre-flight request arrives, with empty
787
     * Access-Control-Request-Method
788
     * 
789
     * @throws ServletException
790
     * @throws IOException
791
     */
792
    @Test
793
    public void testCheckPreFlightRequestTypeEmptyACRM()
794
            throws ServletException, IOException {
795
        MockHttpServletRequest request = new MockHttpServletRequest();
796
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
797
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
798
        request.setHeader(
799
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
800
                "");
801
        request.setMethod("OPTIONS");
802
        CORSFilter corsFilter = new CORSFilter();
803
        corsFilter.init(MockFilterConfigs
804
                .getDefaultFilterConfig());
805
        CORSFilter.CORSRequestType requestType =
806
                corsFilter.checkRequestType(request);
807
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
808
                requestType);
809
    }
810
811
    /**
812
     * Happy path test, when a valid CORS Pre-flight request arrives.
813
     * 
814
     * @throws ServletException
815
     */
816
    @Test
817
    public void testCheckPreFlightRequestTypeNoHeaders()
818
            throws ServletException {
819
        MockHttpServletRequest request = new MockHttpServletRequest();
820
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
821
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
822
        request.setHeader(
823
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
824
                "PUT");
825
        request.setMethod("OPTIONS");
826
        CORSFilter corsFilter = new CORSFilter();
827
        corsFilter.init(MockFilterConfigs
828
                .getDefaultFilterConfig());
829
        CORSFilter.CORSRequestType requestType =
830
                corsFilter.checkRequestType(request);
831
        Assert.assertEquals(CORSFilter.CORSRequestType.PRE_FLIGHT, requestType);
832
    }
833
834
    /**
835
     * Section 6.2.3
836
     * 
837
     * @throws ServletException
838
     * @throws IOException
839
     */
840
    @Test
841
    public void testCheckPreFlightRequestTypeInvalidRequestMethod()
842
            throws ServletException, IOException {
843
        MockHttpServletRequest request = new MockHttpServletRequest();
844
        MockHttpServletResponse response = new MockHttpServletResponse();
845
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
846
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
847
        request.setHeader(
848
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
849
                "POLITE");
850
        request.setMethod("OPTIONS");
851
        CORSFilter corsFilter = new CORSFilter();
852
        corsFilter.init(MockFilterConfigs
853
                .getDefaultFilterConfig());
854
        corsFilter.doFilter(request, response, filterChain);
855
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
856
                response.getStatus());
857
    }
858
859
    /**
860
     * Section Section 6.2.5
861
     * 
862
     * @throws ServletException
863
     * @throws IOException
864
     */
865
    @Test
866
    public void testCheckPreFlightRequestTypeUnsupportedRequestMethod()
867
            throws ServletException, IOException {
868
        MockHttpServletRequest request = new MockHttpServletRequest();
869
        MockHttpServletResponse response = new MockHttpServletResponse();
870
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
871
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
872
        request.setHeader(
873
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
874
                "TRACE");
875
        request.setMethod("OPTIONS");
876
        CORSFilter corsFilter = new CORSFilter();
877
        corsFilter.init(MockFilterConfigs
878
                .getDefaultFilterConfig());
879
        corsFilter.doFilter(request, response, filterChain);
880
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
881
                response.getStatus());
882
    }
883
884
    /**
885
     * Section Section 6.2.6
886
     * 
887
     * @throws ServletException
888
     * @throws IOException
889
     */
890
    @Test
891
    public void testCheckPreFlightRequestTypeUnsupportedRequestHeaders()
892
            throws ServletException, IOException {
893
        MockHttpServletRequest request = new MockHttpServletRequest();
894
        MockHttpServletResponse response = new MockHttpServletResponse();
895
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
896
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
897
        request.setHeader(
898
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
899
                "PUT");
900
        request.setHeader(
901
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
902
                "X-ANSWER");
903
        request.setMethod("OPTIONS");
904
        CORSFilter corsFilter = new CORSFilter();
905
        corsFilter.init(MockFilterConfigs
906
                .getSecureFilterConfig());
907
        corsFilter.doFilter(request, response, filterChain);
908
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
909
                response.getStatus());
910
    }
911
912
    /**
913
     * Section Section 6.2.7
914
     * 
915
     * @throws ServletException
916
     * @throws IOException
917
     */
918
    @Test
919
    public void testCheckPreFlightRequestTypeAnyOriginNoWithCredentials()
920
            throws ServletException, IOException {
921
        MockHttpServletRequest request = new MockHttpServletRequest();
922
        MockHttpServletResponse response = new MockHttpServletResponse();
923
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
924
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
925
        request.setHeader(
926
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
927
                "PUT");
928
        request.setHeader(
929
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
930
                "Origin");
931
        request.setMethod("OPTIONS");
932
        CORSFilter corsFilter = new CORSFilter();
933
        corsFilter.init(MockFilterConfigs
934
                .getFilterConfigAnyOriginAndSupportsCredentialsDisabled());
935
        corsFilter.doFilter(request, response, filterChain);
936
        Assert.assertTrue(response.getHeader(
937
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
938
                "*"));
939
        Assert.assertNull(response
940
                .getHeader(CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
941
    }
942
943
    @Test
944
    public void testCheckPreFlightRequestTypeOriginNotAllowed()
945
            throws ServletException, IOException {
946
        MockHttpServletRequest request = new MockHttpServletRequest();
947
        MockHttpServletResponse response = new MockHttpServletResponse();
948
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
949
                "www.ebay.com");
950
        request.setHeader(
951
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
952
                "PUT");
953
        request.setMethod("OPTIONS");
954
        CORSFilter corsFilter = new CORSFilter();
955
        corsFilter.init(MockFilterConfigs
956
                .getSecureFilterConfig());
957
        corsFilter.doFilter(request, response, filterChain);
958
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
959
                response.getStatus());
960
    }
961
962
    /**
963
     * Happy path test, when a valid CORS Pre-flight request arrives.
964
     * 
965
     * @throws ServletException
966
     */
967
    @Test
968
    public void testCheckPreFlightRequestTypeEmptyHeaders()
969
            throws ServletException {
970
        MockHttpServletRequest request = new MockHttpServletRequest();
971
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
972
                MockFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
973
        request.setHeader(
974
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
975
                "PUT");
976
        request.setHeader(
977
                CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
978
                "");
979
        request.setMethod("OPTIONS");
980
        CORSFilter corsFilter = new CORSFilter();
981
        corsFilter.init(MockFilterConfigs
982
                .getDefaultFilterConfig());
983
        CORSFilter.CORSRequestType requestType =
984
                corsFilter.checkRequestType(request);
985
        Assert.assertEquals(CORSFilter.CORSRequestType.PRE_FLIGHT, requestType);
986
    }
987
988
    /**
989
     * Negative test, when a CORS request arrives, with an empty origin.
990
     * 
991
     * @throws ServletException
992
     */
993
    @Test
994
    public void testCheckNotCORSRequestTypeEmptyOrigin()
995
            throws ServletException {
996
        MockHttpServletRequest request = new MockHttpServletRequest();
997
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
998
                "");
999
        request.setMethod("GET");
1000
        CORSFilter corsFilter = new CORSFilter();
1001
        corsFilter.init(MockFilterConfigs
1002
                .getDefaultFilterConfig());
1003
        CORSFilter.CORSRequestType requestType =
1004
                corsFilter.checkRequestType(request);
1005
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1006
                requestType);
1007
    }
1008
1009
    /**
1010
     * Tests for failure, when a different domain is used, that's not in the
1011
     * allowed list of origins.
1012
     * 
1013
     * @throws ServletException
1014
     * @throws IOException
1015
     */
1016
    @Test
1017
    public void testCheckInvalidOrigin() throws ServletException, IOException {
1018
        MockHttpServletRequest request = new MockHttpServletRequest();
1019
        MockHttpServletResponse response = new MockHttpServletResponse();
1020
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1021
                "www.example.com");
1022
        request.setMethod("GET");
1023
        CORSFilter corsFilter = new CORSFilter();
1024
        corsFilter.init(MockFilterConfigs
1025
                .getSpecificOriginFilterConfig());
1026
        corsFilter.doFilter(request, response, filterChain);
1027
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
1028
                response.getStatus());
1029
    }
1030
1031
    /**
1032
     * Tests for failure, when a different sub-domain is used, that's not in the
1033
     * allowed list of origins.
1034
     * 
1035
     * @throws ServletException
1036
     * @throws IOException
1037
     */
1038
    @Test
1039
    public void testCheckInvalidOriginNotAllowedSubdomain()
1040
            throws ServletException, IOException {
1041
        MockHttpServletRequest request = new MockHttpServletRequest();
1042
        MockHttpServletResponse response = new MockHttpServletResponse();
1043
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1044
                "http://commons.apache.org");
1045
        request.setMethod("GET");
1046
        CORSFilter corsFilter = new CORSFilter();
1047
        corsFilter.init(MockFilterConfigs
1048
                .getSpecificOriginFilterConfig());
1049
        corsFilter.doFilter(request, response, filterChain);
1050
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
1051
                response.getStatus());
1052
    }
1053
1054
    /**
1055
     * PUT is not an allowed request method.
1056
     * 
1057
     * @throws ServletException
1058
     * @throws IOException
1059
     */
1060
    @Test
1061
    public void testCheckInvalidRequestMethod() throws ServletException,
1062
            IOException {
1063
        MockHttpServletRequest request = new MockHttpServletRequest();
1064
        MockHttpServletResponse response = new MockHttpServletResponse();
1065
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1066
                "http://tomcat.apache.org");
1067
        request.setMethod("PUT");
1068
        CORSFilter corsFilter = new CORSFilter();
1069
        corsFilter.init(MockFilterConfigs
1070
                .getDefaultFilterConfig());
1071
        corsFilter.doFilter(request, response, filterChain);
1072
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
1073
                response.getStatus());
1074
    }
1075
1076
    /**
1077
     * When requestMethod is null
1078
     * 
1079
     * @throws ServletException
1080
     */
1081
    @Test
1082
    public void testCheckNullRequestMethod() throws ServletException {
1083
        MockHttpServletRequest request = new MockHttpServletRequest();
1084
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1085
                "http://tomcat.apache.org");
1086
        request.setMethod(null);
1087
        CORSFilter corsFilter = new CORSFilter();
1088
        corsFilter.init(MockFilterConfigs
1089
                .getSpecificOriginFilterConfig());
1090
        CORSFilter.CORSRequestType requestType =
1091
                corsFilter.checkRequestType(request);
1092
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1093
                requestType);
1094
    }
1095
1096
    /**
1097
     * "http://tomcat.apache.org" is an allowed origin and
1098
     * "https://tomcat.apache.org" is not, because scheme doesn't match
1099
     * 
1100
     * @throws ServletException
1101
     */
1102
    @Test
1103
    public void testCheckForSchemeVariance() throws ServletException {
1104
        MockHttpServletRequest request = new MockHttpServletRequest();
1105
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1106
                "https://tomcat.apache.org");
1107
        request.setMethod("POST");
1108
        CORSFilter corsFilter = new CORSFilter();
1109
        corsFilter.init(MockFilterConfigs
1110
                .getSpecificOriginFilterConfig());
1111
        CORSFilter.CORSRequestType requestType =
1112
                corsFilter.checkRequestType(request);
1113
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1114
                requestType);
1115
    }
1116
1117
    /**
1118
     * "http://tomcat.apache.org" is an allowed origin and
1119
     * "http://tomcat.apache.org:8080" is not, because ports doesn't match
1120
     * 
1121
     * @throws ServletException
1122
     * @throws IOException
1123
     */
1124
    @Test
1125
    public void testCheckForPortVariance() throws ServletException, IOException {
1126
        MockHttpServletRequest request = new MockHttpServletRequest();
1127
        MockHttpServletResponse response = new MockHttpServletResponse();
1128
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1129
                "http://tomcat.apache.org:8080");
1130
        request.setMethod("GET");
1131
        CORSFilter corsFilter = new CORSFilter();
1132
        corsFilter.init(MockFilterConfigs
1133
                .getSpecificOriginFilterConfig());
1134
        corsFilter.doFilter(request, response, filterChain);
1135
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN,
1136
                response.getStatus());
1137
    }
1138
1139
    /**
1140
     * Tests for failure, when an invalid {@link HttpServletRequest} is
1141
     * encountered.
1142
     * 
1143
     * @throws ServletException
1144
     */
1145
    @Test(expected = IllegalArgumentException.class)
1146
    public void testCheckRequestTypeNull() throws ServletException {
1147
        HttpServletRequest request = null;
1148
        CORSFilter corsFilter = new CORSFilter();
1149
        corsFilter.checkRequestType(request);
1150
    }
1151
1152
    @Test
1153
    public void testJoin() {
1154
        Set<String> elements = new LinkedHashSet<String>();
1155
        String separator = ",";
1156
        elements.add("world");
1157
        elements.add("peace");
1158
        String join = CORSFilter.join(elements, separator);
1159
        Assert.assertTrue("world,peace".equals(join));
1160
    }
1161
1162
    @Test
1163
    public void testJoinSingleElement() {
1164
        Set<String> elements = new LinkedHashSet<String>();
1165
        String separator = ",";
1166
        elements.add("world");
1167
        String join = CORSFilter.join(elements, separator);
1168
        Assert.assertTrue("world".equals(join));
1169
    }
1170
1171
    @Test
1172
    public void testJoinSepNull() {
1173
        Set<String> elements = new LinkedHashSet<String>();
1174
        String separator = null;
1175
        elements.add("world");
1176
        elements.add("peace");
1177
        String join = CORSFilter.join(elements, separator);
1178
        Assert.assertTrue("world,peace".equals(join));
1179
    }
1180
1181
    @Test
1182
    public void testJoinElementsNull() {
1183
        Set<String> elements = null;
1184
        String separator = ",";
1185
        String join = CORSFilter.join(elements, separator);
1186
1187
        Assert.assertNull(join);
1188
    }
1189
1190
    @Test
1191
    public void testJoinOneNullElement() {
1192
        Set<String> elements = new LinkedHashSet<String>();
1193
        String separator = ",";
1194
        elements.add(null);
1195
        elements.add("peace");
1196
        String join = CORSFilter.join(elements, separator);
1197
        Assert.assertTrue(",peace".equals(join));
1198
    }
1199
1200
    @Test
1201
    public void testJoinAllNullElements() {
1202
        Set<String> elements = new LinkedHashSet<String>();
1203
        String separator = ",";
1204
        elements.add(null);
1205
        elements.add(null);
1206
        String join = CORSFilter.join(elements, separator);
1207
        Assert.assertTrue("".equals(join));
1208
    }
1209
1210
    @Test
1211
    public void testJoinAllEmptyElements() {
1212
        Set<String> elements = new LinkedHashSet<String>();
1213
        String separator = ",";
1214
        elements.add("");
1215
        elements.add("");
1216
        String join = CORSFilter.join(elements, separator);
1217
        Assert.assertTrue("".equals(join));
1218
    }
1219
1220
    @Test
1221
    public void testJoinPipeSeparator() {
1222
        Set<String> elements = new LinkedHashSet<String>();
1223
        String separator = "|";
1224
        elements.add("world");
1225
        elements.add("peace");
1226
        String join = CORSFilter.join(elements, separator);
1227
        Assert.assertTrue("world|peace".equals(join));
1228
    }
1229
1230
    @Test
1231
    public void testWithFilterConfig() throws ServletException {
1232
        CORSFilter corsFilter = new CORSFilter();
1233
        corsFilter.init(MockFilterConfigs
1234
                .getDefaultFilterConfig());
1235
        Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6);
1236
        Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4);
1237
        Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
1238
        Assert.assertTrue(corsFilter.isAnyOriginAllowed());
1239
        Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
1240
        Assert.assertTrue(corsFilter.isSupportsCredentials());
1241
        Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800);
1242
        Assert.assertTrue(!corsFilter.isLoggingEnabled());
1243
    }
1244
1245
    @Test(expected = ServletException.class)
1246
    public void testWithFilterConfigInvalidPreflightAge()
1247
            throws ServletException {
1248
        CORSFilter corsFilter = new CORSFilter();
1249
        corsFilter.init(MockFilterConfigs
1250
                .getFilterConfigInvalidMaxPreflightAge());
1251
    }
1252
1253
    @Test
1254
    public void testWithStringParserEmpty() throws ServletException {
1255
        CORSFilter corsFilter = new CORSFilter();
1256
        corsFilter.init(MockFilterConfigs.getEmptyFilterConfig());
1257
        Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 0);
1258
        Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 0);
1259
        Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
1260
        Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
1261
        Assert.assertFalse(corsFilter.isSupportsCredentials());
1262
        Assert.assertTrue(corsFilter.getPreflightMaxAge() == 0);
1263
        Assert.assertTrue(!corsFilter.isLoggingEnabled());
1264
    }
1265
1266
    /**
1267
     * If an init param is null, it's default value will be used.
1268
     * 
1269
     * @throws ServletException
1270
     */
1271
    @Test
1272
    public void testWithStringParserNull() throws ServletException {
1273
        CORSFilter corsFilter = new CORSFilter();
1274
        corsFilter.init(MockFilterConfigs.getNullFilterConfig());
1275
        Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6);
1276
        Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4);
1277
        Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
1278
        Assert.assertTrue(corsFilter.isAnyOriginAllowed());
1279
        Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
1280
        Assert.assertTrue(corsFilter.isSupportsCredentials());
1281
        Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800);
1282
        Assert.assertTrue(!corsFilter.isLoggingEnabled());
1283
    }
1284
1285
    @Test
1286
    public void testValidOrigin() {
1287
        Assert.assertTrue(CORSFilter.isValidOrigin("http://www.w3.org"));
1288
    }
1289
1290
    @Test
1291
    public void testInValidOriginCRLF() {
1292
        Assert.assertFalse(CORSFilter.isValidOrigin("http://www.w3.org\r\n"));
1293
    }
1294
1295
    @Test
1296
    public void testInValidOriginEncodedCRLF1() {
1297
        Assert.assertFalse(CORSFilter.isValidOrigin("http://www.w3.org%0d%0a"));
1298
    }
1299
1300
    @Test
1301
    public void testInValidOriginEncodedCRLF2() {
1302
        Assert.assertFalse(CORSFilter.isValidOrigin("http://www.w3.org%0D%0A"));
1303
    }
1304
1305
    @Test
1306
    public void testInValidOriginEncodedCRLF3() {
1307
        Assert.assertFalse(CORSFilter
1308
                .isValidOrigin("http://www.w3.org%0%0d%0ad%0%0d%0aa"));
1309
    }
1310
1311
    @Test
1312
    public void testCheckInvalidCRLF1() throws ServletException {
1313
        MockHttpServletRequest request = new MockHttpServletRequest();
1314
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1315
                "http://www.w3.org\r\n");
1316
        request.setMethod("GET");
1317
        CORSFilter corsFilter = new CORSFilter();
1318
        corsFilter.init(MockFilterConfigs
1319
                .getDefaultFilterConfig());
1320
        CORSFilter.CORSRequestType requestType =
1321
                corsFilter.checkRequestType(request);
1322
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1323
                requestType);
1324
    }
1325
1326
    @Test
1327
    public void testCheckInvalidCRLF2() throws ServletException {
1328
        MockHttpServletRequest request = new MockHttpServletRequest();
1329
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1330
                "http://www.w3.org\r\n");
1331
        request.setMethod("GET");
1332
        CORSFilter corsFilter = new CORSFilter();
1333
        corsFilter.init(MockFilterConfigs
1334
                .getDefaultFilterConfig());
1335
        CORSFilter.CORSRequestType requestType =
1336
                corsFilter.checkRequestType(request);
1337
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1338
                requestType);
1339
    }
1340
1341
    @Test
1342
    public void testCheckInvalidCRLF3() throws ServletException {
1343
        MockHttpServletRequest request = new MockHttpServletRequest();
1344
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1345
                "http://www.w3.org%0d%0a");
1346
        request.setMethod("GET");
1347
        CORSFilter corsFilter = new CORSFilter();
1348
        corsFilter.init(MockFilterConfigs
1349
                .getDefaultFilterConfig());
1350
        CORSFilter.CORSRequestType requestType =
1351
                corsFilter.checkRequestType(request);
1352
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1353
                requestType);
1354
    }
1355
1356
    @Test
1357
    public void testCheckInvalidCRLF4() throws ServletException {
1358
        MockHttpServletRequest request = new MockHttpServletRequest();
1359
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1360
                "http://www.w3.org%0D%0A");
1361
        request.setMethod("GET");
1362
        CORSFilter corsFilter = new CORSFilter();
1363
        corsFilter.init(MockFilterConfigs
1364
                .getDefaultFilterConfig());
1365
        CORSFilter.CORSRequestType requestType =
1366
                corsFilter.checkRequestType(request);
1367
        Assert.assertEquals(CORSFilter.CORSRequestType.INVALID_CORS,
1368
                requestType);
1369
    }
1370
1371
    @Test
1372
    public void testDecorateRequestDisabled() throws IOException,
1373
            ServletException {
1374
        MockHttpServletRequest request = new MockHttpServletRequest();
1375
        request.setHeader(CORSFilter.REQUEST_HEADER_ORIGIN,
1376
                MockFilterConfigs.HTTPS_WWW_APACHE_ORG);
1377
        request.setMethod("GET");
1378
        MockHttpServletResponse response = new MockHttpServletResponse();
1379
1380
        CORSFilter corsFilter = new CORSFilter();
1381
        corsFilter.init(MockFilterConfigs.getFilterConfigDecorateRequestDisabled());
1382
        corsFilter.doFilter(request, response, filterChain);
1383
1384
        Assert.assertTrue(response.getHeader(
1385
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals(
1386
                "https://www.apache.org"));
1387
        Assert.assertNull(request
1388
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
1389
        Assert.assertNull(request
1390
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN));
1391
        Assert.assertNull(request
1392
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS));
1393
        Assert.assertNull(request
1394
                .getAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE));
1395
    }
1396
1397
    @Test
1398
    public void testDestroy() {
1399
        // Nothing to test.
1400
        // NO-OP
1401
    }
1402
}
0
  + native
1403
  + native
(-)test/org/apache/catalina/filters/MockHttpServletResponse.java (+193 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.io.IOException;
4
import java.io.PrintWriter;
5
import java.io.StringWriter;
6
import java.util.ArrayList;
7
import java.util.Collection;
8
import java.util.List;
9
import java.util.Locale;
10
11
import javax.servlet.ServletOutputStream;
12
import javax.servlet.http.Cookie;
13
import javax.servlet.http.HttpServletResponse;
14
15
public class MockHttpServletResponse implements HttpServletResponse {
16
    List headerNames = new ArrayList();
17
    List headerValues = new ArrayList();
18
    PrintWriter pw;
19
    int status;
20
21
    public String getCharacterEncoding() {
22
23
        throw new RuntimeException("Not implemented");
24
    }
25
26
    public String getContentType() {
27
28
        throw new RuntimeException("Not implemented");
29
    }
30
31
    public ServletOutputStream getOutputStream() throws IOException {
32
33
        throw new RuntimeException("Not implemented");
34
    }
35
36
    public PrintWriter getWriter() throws IOException {
37
        if (pw == null) {
38
            pw = new PrintWriter(new StringWriter());
39
        }
40
        return pw;
41
    }
42
43
    public void setCharacterEncoding(String charset) {
44
        throw new RuntimeException("Not implemented");
45
    }
46
47
    public void setContentLength(int len) {
48
        throw new RuntimeException("Not implemented");
49
    }
50
51
    public void setContentType(String type) {
52
53
    }
54
55
    public void setBufferSize(int size) {
56
        throw new RuntimeException("Not implemented");
57
    }
58
59
    public int getBufferSize() {
60
        throw new RuntimeException("Not implemented");
61
    }
62
63
    public void flushBuffer() throws IOException {
64
        throw new RuntimeException("Not implemented");
65
    }
66
67
    public void resetBuffer() {
68
    }
69
70
    public boolean isCommitted() {
71
        throw new RuntimeException("Not implemented");
72
    }
73
74
    public void reset() {
75
76
    }
77
78
    public void setLocale(Locale loc) {
79
        throw new RuntimeException("Not implemented");
80
    }
81
82
    public Locale getLocale() {
83
84
        throw new RuntimeException("Not implemented");
85
    }
86
87
    public void addCookie(Cookie cookie) {
88
        throw new RuntimeException("Not implemented");
89
    }
90
91
    public boolean containsHeader(String name) {
92
        throw new RuntimeException("Not implemented");
93
    }
94
95
    public String encodeURL(String url) {
96
97
        throw new RuntimeException("Not implemented");
98
    }
99
100
    public String encodeRedirectURL(String url) {
101
102
        throw new RuntimeException("Not implemented");
103
    }
104
105
    public String encodeUrl(String url) {
106
107
        throw new RuntimeException("Not implemented");
108
    }
109
110
    public String encodeRedirectUrl(String url) {
111
112
        throw new RuntimeException("Not implemented");
113
    }
114
115
    public void sendError(int sc, String msg) throws IOException {
116
117
    }
118
119
    public void sendError(int sc) throws IOException {
120
121
    }
122
123
    public void sendRedirect(String location) throws IOException {
124
        throw new RuntimeException("Not implemented");
125
    }
126
127
    public void setDateHeader(String name, long date) {
128
        throw new RuntimeException("Not implemented");
129
    }
130
131
    public void addDateHeader(String name, long date) {
132
        throw new RuntimeException("Not implemented");
133
    }
134
135
    public String getHeader(String name) {
136
        int index = headerNames.indexOf(name);
137
        if (index != -1) {
138
            return (String) headerValues.get(index);
139
        }
140
        return null;
141
    }
142
143
    public void setHeader(String name, String value) {
144
        int index = headerNames.indexOf(name);
145
        if (index != -1) {
146
            headerValues.set(index, value);
147
        } else {
148
            headerNames.add(name);
149
            headerValues.add(value);
150
        }
151
    }
152
153
    public void addHeader(String name, String value) {
154
        headerNames.add(name);
155
        headerValues.add(value);
156
    }
157
158
    public void setIntHeader(String name, int value) {
159
        throw new RuntimeException("Not implemented");
160
    }
161
162
    public void addIntHeader(String name, int value) {
163
        throw new RuntimeException("Not implemented");
164
    }
165
166
    public void setStatus(int sc) {
167
        this.status = sc;
168
    }
169
170
    public int getStatus() {
171
        return this.status;
172
    }
173
174
    public void setStatus(int sc, String sm) {
175
176
    }
177
178
    @Override
179
    public void setContentLengthLong(long length) {
180
        throw new RuntimeException("Not implemented");
181
    }
182
183
    @Override
184
    public Collection<String> getHeaders(String name) {
185
        throw new RuntimeException("Not implemented");
186
    }
187
188
    @Override
189
    public Collection<String> getHeaderNames() {
190
        throw new RuntimeException("Not implemented");
191
    }
192
193
}
0
  + native
194
  + native
(-)java/org/apache/catalina/filters/CORSFilter.java (+1130 lines)
Line 0 Link Here
1
package org.apache.catalina.filters;
2
3
import java.io.IOException;
4
import java.net.URI;
5
import java.net.URISyntaxException;
6
import java.util.Arrays;
7
import java.util.Collection;
8
import java.util.HashSet;
9
import java.util.LinkedList;
10
import java.util.List;
11
import java.util.Set;
12
13
import javax.servlet.Filter;
14
import javax.servlet.FilterChain;
15
import javax.servlet.FilterConfig;
16
import javax.servlet.ServletException;
17
import javax.servlet.ServletRequest;
18
import javax.servlet.ServletResponse;
19
import javax.servlet.http.HttpServletRequest;
20
import javax.servlet.http.HttpServletResponse;
21
22
/**
23
 * <p>
24
 * A {@link Filter} that enable client-side cross-origin requests by
25
 * implementing W3C's CORS (<b>C</b>ross-<b>O</b>rigin <b>R</b>esource
26
 * <b>S</b>haring) specification for resources. Each {@link HttpServletRequest}
27
 * request is inspected as per specification, and appropriate response headers
28
 * are added to {@link HttpServletResponse}.
29
 * </p>
30
 * 
31
 * <p>
32
 * By default, it also sets following request attributes, that helps to
33
 * determine nature of request downstream.
34
 * <ul>
35
 * <li><b>cors.isCorsRequest:</b> Flag to determine if request is a CORS
36
 * request. Set to <code>true</code> if CORS request; <code>false</code>
37
 * otherwise.</li>
38
 * <li><b>cors.request.origin:</b> The Origin URL, i.e. the URL of the page from
39
 * where the request is originated.</li>
40
 * <li>
41
 * <b>cors.request.type:</b> Type of request. Possible values:
42
 * <ul>
43
 * <li>SIMPLE: A request which is not preceded by a pre-flight request.</li>
44
 * <li>ACTUAL: A request which is preceded by a pre-flight request.</li>
45
 * <li>PRE_FLIGHT: A pre-flight request.</li>
46
 * <li>NOT_CORS: A normal same-origin request.</li>
47
 * <li>INVALID_CORS: A cross-origin request, which is invalid.</li>
48
 * </ul>
49
 * </li>
50
 * <li><b>cors.request.headers:</b> Request headers sent as
51
 * 'Access-Control-Request-Headers' header, for pre-flight request.</li>
52
 * </ul>
53
 * </p>
54
 * 
55
 * @author Mohit Soni
56
 * @see <a href="http://www.w3.org/TR/cors/">CORS specification</a>
57
 * 
58
 */
59
public final class CORSFilter implements Filter {
60
    // ----------------------------------------------------- Instance variables
61
    /**
62
     * Holds filter configuration.
63
     */
64
    private FilterConfig filterConfig;
65
66
    /**
67
     * A {@link Collection} of origins consisting of zero or more origins that
68
     * are allowed access to the resource.
69
     */
70
    private final Collection<String> allowedOrigins;
71
72
    /**
73
     * Determines if any origin is allowed to make request.
74
     */
75
    private boolean anyOriginAllowed;
76
77
    /**
78
     * A {@link Collection} of methods consisting of zero or more methods that
79
     * are supported by the resource.
80
     */
81
    private final Collection<String> allowedHttpMethods;
82
83
    /**
84
     * A {@link Collection} of headers consisting of zero or more header field
85
     * names that are supported by the resource.
86
     */
87
    private final Collection<String> allowedHttpHeaders;
88
89
    /**
90
     * A {@link Collection} of exposed headers consisting of zero or more header
91
     * field names of headers other than the simple response headers that the
92
     * resource might use and can be exposed.
93
     */
94
    private final Collection<String> exposedHeaders;
95
96
    /**
97
     * A supports credentials flag that indicates whether the resource supports
98
     * user credentials in the request. It is true when the resource does and
99
     * false otherwise.
100
     */
101
    private boolean supportsCredentials;
102
103
    /**
104
     * Indicates (in seconds) how long the results of a pre-flight request can
105
     * be cached in a pre-flight result cache.
106
     */
107
    private long preflightMaxAge;
108
109
    /**
110
     * Controls access log logging.
111
     */
112
    private boolean loggingEnabled;
113
114
    /**
115
     * Determines if the request should be decorated or not.
116
     */
117
    private boolean decorateRequest;
118
119
    // --------------------------------------------------------- Constructor(s)
120
    public CORSFilter() {
121
        this.allowedOrigins = new HashSet<String>();
122
        this.allowedHttpMethods = new HashSet<String>();
123
        this.allowedHttpHeaders = new HashSet<String>();
124
        this.exposedHeaders = new HashSet<String>();
125
    }
126
127
    // --------------------------------------------------------- Public methods
128
    @Override
129
    public void doFilter(final ServletRequest servletRequest,
130
            final ServletResponse servletResponse, final FilterChain filterChain)
131
            throws IOException, ServletException {
132
        if (!(servletRequest instanceof HttpServletRequest)
133
                || !(servletResponse instanceof HttpServletResponse)) {
134
            String message = "CORS doesn't support non-HTTP request or response.";
135
            throw new ServletException(message);
136
        }
137
138
        // Safe to downcast at this point.
139
        HttpServletRequest request = (HttpServletRequest) servletRequest;
140
        HttpServletResponse response = (HttpServletResponse) servletResponse;
141
142
        // Determines the CORS request type.
143
        CORSFilter.CORSRequestType requestType = checkRequestType(request);
144
145
        // Adds CORS specific attributes to request.
146
        if (decorateRequest) {
147
            CORSFilter.decorateCORSProperties(request, requestType);
148
        }
149
        switch (requestType) {
150
        case SIMPLE:
151
            // Handles a Simple CORS request.
152
            this.handleSimpleCORS(request, response, filterChain);
153
            break;
154
        case ACTUAL:
155
            // Handles an Actual CORS request.
156
            this.handleSimpleCORS(request, response, filterChain);
157
            break;
158
        case PRE_FLIGHT:
159
            // Handles a Pre-flight CORS request.
160
            this.handlePreflightCORS(request, response, filterChain);
161
            break;
162
        case NOT_CORS:
163
            // Handles a Normal request that is not a cross-origin request.
164
            this.handleNonCORS(request, response, filterChain);
165
            break;
166
        default:
167
            // Handles a CORS request that violates specification.
168
            this.handleInvalidCORS(request, response, filterChain);
169
            break;
170
        }
171
    }
172
173
    @Override
174
    public void init(final FilterConfig filterConfig) throws ServletException {
175
        // Initialize defaults
176
        parseAndStore(DEFAULT_ALLOWED_ORIGINS, DEFAULT_ALLOWED_HTTP_METHODS,
177
                DEFAULT_ALLOWED_HTTP_HEADERS, DEFAULT_EXPOSED_HEADERS,
178
                DEFAULT_SUPPORTS_CREDENTIALS, DEFAULT_PREFLIGHT_MAXAGE,
179
                DEFAULT_LOGGING_ENABLED, DEFAULT_DECORATE_REQUEST);
180
181
        this.filterConfig = filterConfig;
182
        this.loggingEnabled = false;
183
184
        if (filterConfig != null) {
185
            String configAllowedOrigins = filterConfig
186
                    .getInitParameter(PARAM_CORS_ALLOWED_ORIGINS);
187
            String configAllowedHttpMethods = filterConfig
188
                    .getInitParameter(PARAM_CORS_ALLOWED_METHODS);
189
            String configAllowedHttpHeaders = filterConfig
190
                    .getInitParameter(PARAM_CORS_ALLOWED_HEADERS);
191
            String configExposedHeaders = filterConfig
192
                    .getInitParameter(PARAM_CORS_EXPOSED_HEADERS);
193
            String configSupportsCredentials = filterConfig
194
                    .getInitParameter(PARAM_CORS_SUPPORT_CREDENTIALS);
195
            String configPreflightMaxAge = filterConfig
196
                    .getInitParameter(PARAM_CORS_PREFLIGHT_MAXAGE);
197
            String configLoggingEnabled = filterConfig
198
                    .getInitParameter(PARAM_CORS_LOGGING_ENABLED);
199
            String configDecorateRequest = filterConfig
200
                    .getInitParameter(PARAM_CORS_REQUEST_DECORATE);
201
202
            parseAndStore(configAllowedOrigins, configAllowedHttpMethods,
203
                    configAllowedHttpHeaders, configExposedHeaders,
204
                    configSupportsCredentials, configPreflightMaxAge,
205
                    configLoggingEnabled, configDecorateRequest);
206
        }
207
    }
208
209
    // --------------------------------------------------------------- Handlers
210
    /**
211
     * Handles a CORS request of type {@link CORSRequestType}.SIMPLE.
212
     * 
213
     * @param request
214
     *            The {@link HttpServletRequest} object.
215
     * @param response
216
     *            The {@link HttpServletResponse} object.
217
     * @param filterChain
218
     *            The {@link FilterChain} object.
219
     * @throws IOException
220
     * @throws ServletException
221
     * @see <a href="http://www.w3.org/TR/cors/#resource-requests">Simple
222
     *      Cross-Origin Request, Actual Request, and Redirects</a>
223
     */
224
    public void handleSimpleCORS(final HttpServletRequest request,
225
            final HttpServletResponse response, final FilterChain filterChain)
226
            throws IOException, ServletException {
227
        CORSFilter.CORSRequestType requestType = checkRequestType(request);
228
        if (!(requestType == CORSFilter.CORSRequestType.SIMPLE || requestType == CORSFilter.CORSRequestType.ACTUAL)) {
229
            String message = "Expects a HttpServletRequest object of type "
230
                    + CORSFilter.CORSRequestType.SIMPLE + " or "
231
                    + CORSFilter.CORSRequestType.ACTUAL;
232
            throw new IllegalArgumentException(message);
233
        }
234
235
        final String origin = request
236
                .getHeader(CORSFilter.REQUEST_HEADER_ORIGIN);
237
        final String method = request.getMethod();
238
239
        // Section 6.1.2
240
        if (!isOriginAllowed(origin)) {
241
            handleInvalidCORS(request, response, filterChain);
242
            return;
243
        }
244
245
        if (!allowedHttpMethods.contains(method)) {
246
            handleInvalidCORS(request, response, filterChain);
247
            return;
248
        }
249
250
        // Section 6.1.3
251
        // Add a single Access-Control-Allow-Origin header.
252
        if (anyOriginAllowed && !supportsCredentials) {
253
            // If resource doesn't support credentials and if any origin is
254
            // allowed
255
            // to make CORS request, return header with '*'.
256
            response.addHeader(
257
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, "*");
258
        } else {
259
            // If the resource supports credentials add a single
260
            // Access-Control-Allow-Origin header, with the value of the Origin
261
            // header as value.
262
            response.addHeader(
263
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
264
                    origin);
265
        }
266
        // Section 6.1.3
267
        // If the resource supports credentials, add a single
268
        // Access-Control-Allow-Credentials header with the case-sensitive
269
        // string "true" as value.
270
        if (supportsCredentials) {
271
            response.addHeader(
272
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
273
                    "true");
274
        }
275
276
        // Section 6.1.4
277
        // If the list of exposed headers is not empty add one or more
278
        // Access-Control-Expose-Headers headers, with as values the header
279
        // field names given in the list of exposed headers.
280
        if ((exposedHeaders != null) && (exposedHeaders.size() > 0)) {
281
            String exposedHeadersString = join(exposedHeaders, ",");
282
            response.addHeader(
283
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
284
                    exposedHeadersString);
285
        }
286
287
        // Forward the request down the filter chain.
288
        filterChain.doFilter(request, response);
289
    }
290
291
    /**
292
     * Handles CORS pre-flight request.
293
     * 
294
     * @param request
295
     *            The {@link HttpServletRequest} object.
296
     * @param response
297
     *            The {@link HttpServletResponse} object.
298
     * @param filterChain
299
     *            The {@link FilterChain} object.
300
     * @throws IOException
301
     * @throws ServletException
302
     */
303
    public void handlePreflightCORS(final HttpServletRequest request,
304
            final HttpServletResponse response, final FilterChain filterChain)
305
            throws IOException, ServletException {
306
        CORSRequestType requestType = checkRequestType(request);
307
        if (requestType != CORSRequestType.PRE_FLIGHT) {
308
            throw new IllegalArgumentException(
309
                    "Expects a HttpServletRequest object of type "
310
                            + CORSRequestType.PRE_FLIGHT.name().toLowerCase());
311
        }
312
313
        final String origin = request
314
                .getHeader(CORSFilter.REQUEST_HEADER_ORIGIN);
315
316
        // Section 6.2.2
317
        if (!isOriginAllowed(origin)) {
318
            handleInvalidCORS(request, response, filterChain);
319
            return;
320
        }
321
322
        // Section 6.2.3
323
        String accessControlRequestMethod = request
324
                .getHeader(CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
325
        if (accessControlRequestMethod == null
326
                || (!HTTP_METHODS.contains(accessControlRequestMethod.trim()))) {
327
            handleInvalidCORS(request, response, filterChain);
328
            return;
329
        } else {
330
            accessControlRequestMethod = accessControlRequestMethod.trim();
331
        }
332
333
        // Section 6.2.4
334
        String accessControlRequestHeadersHeader = request
335
                .getHeader(CORSFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
336
        List<String> accessControlRequestHeaders = new LinkedList<String>();
337
        if (accessControlRequestHeadersHeader != null
338
                && !accessControlRequestHeadersHeader.trim().isEmpty()) {
339
            String[] headers = accessControlRequestHeadersHeader.trim().split(
340
                    ",");
341
            for (String header : headers) {
342
                accessControlRequestHeaders.add(header.trim().toLowerCase());
343
            }
344
        }
345
346
        // Section 6.2.5
347
        if (!allowedHttpMethods.contains(accessControlRequestMethod)) {
348
            handleInvalidCORS(request, response, filterChain);
349
            return;
350
        }
351
352
        // Section 6.2.6
353
        if (!accessControlRequestHeaders.isEmpty()) {
354
            for (String header : accessControlRequestHeaders) {
355
                if (!allowedHttpHeaders.contains(header)) {
356
                    handleInvalidCORS(request, response, filterChain);
357
                    return;
358
                }
359
            }
360
        }
361
362
        // Section 6.2.7
363
        if (supportsCredentials) {
364
            response.addHeader(
365
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
366
                    origin);
367
            response.addHeader(
368
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
369
                    "true");
370
        } else {
371
            if (anyOriginAllowed) {
372
                response.addHeader(
373
                        CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
374
                        "*");
375
            } else {
376
                response.addHeader(
377
                        CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
378
                        origin);
379
            }
380
        }
381
382
        // Section 6.2.8
383
        if (preflightMaxAge > 0) {
384
            response.addHeader(
385
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE,
386
                    String.valueOf(preflightMaxAge));
387
        }
388
389
        // Section 6.2.9
390
        response.addHeader(
391
                CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
392
                accessControlRequestMethod);
393
394
        // Section 6.2.10
395
        if ((allowedHttpHeaders != null) && (!allowedHttpHeaders.isEmpty())) {
396
            response.addHeader(
397
                    CORSFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
398
                    join(allowedHttpHeaders, ","));
399
        }
400
401
        // Do not forward the request down the filter chain.
402
    }
403
404
    /**
405
     * Handles a request, that's not a CORS request, but is a valid request i.e.
406
     * it is not a cross-origin request. This implementation, just forwards the
407
     * request down the filter chain.
408
     * 
409
     * @param request
410
     *            The {@link HttpServletRequest} object.
411
     * @param response
412
     *            The {@link HttpServletResponse} object.
413
     * @param filterChain
414
     *            The {@link FilterChain} object.
415
     * @throws IOException
416
     * @throws ServletException
417
     */
418
    public void handleNonCORS(final HttpServletRequest request,
419
            final HttpServletResponse response, final FilterChain filterChain)
420
            throws IOException, ServletException {
421
        // Let request pass.
422
        filterChain.doFilter(request, response);
423
    }
424
425
    /**
426
     * Handles a CORS request that violates specification.
427
     * 
428
     * @param request
429
     *            The {@link HttpServletRequest} object.
430
     * @param response
431
     *            The {@link HttpServletResponse} object.
432
     * @param filterChain
433
     *            The {@link FilterChain} object.
434
     * @throws IOException
435
     * @throws ServletException
436
     */
437
    public void handleInvalidCORS(final HttpServletRequest request,
438
            final HttpServletResponse response, final FilterChain filterChain) {
439
        String origin = request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN);
440
        String method = request.getMethod();
441
        String accessControlRequestHeaders = request
442
                .getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
443
444
        String message = "Invalid CORS request; Origin=" + origin + ";Method="
445
                + method;
446
        if (accessControlRequestHeaders != null) {
447
            message = message + ";Access-Control-Request-Headers="
448
                    + accessControlRequestHeaders;
449
        }
450
        response.setContentType("text/plain");
451
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
452
        response.resetBuffer();
453
454
        log(message);
455
    }
456
457
    @Override
458
    public void destroy() {
459
        // NOOP
460
    }
461
462
    // -------------------------------------------------------- Utility methods
463
    /**
464
     * Decorates the {@link HttpServletRequest}, with CORS attributes.
465
     * <ul>
466
     * <li><b>cors.isCorsRequest:</b> Flag to determine if request is a CORS
467
     * request. Set to <code>true</code> if CORS request; <code>false</code>
468
     * otherwise.</li>
469
     * <li><b>cors.request.origin:</b> The Origin URL.</li>
470
     * <li><b>cors.request.type:</b> Type of request. Values:
471
     * <code>simple</code> or <code>preflight</code> or <code>not_cors</code> or
472
     * <code>invalid_cors</code></li>
473
     * <li><b>cors.request.headers:</b> Request headers sent as
474
     * 'Access-Control-Request-Headers' header, for pre-flight request.</li>
475
     * </ul>
476
     * 
477
     * @param request
478
     *            The {@link HttpServletRequest} object.
479
     * @param corsRequestType
480
     *            The {@link CORSRequestType} object.
481
     */
482
    public static void decorateCORSProperties(final HttpServletRequest request,
483
            final CORSRequestType corsRequestType) {
484
        if (request == null) {
485
            throw new IllegalArgumentException(
486
                    "HttpServletRequest object is null");
487
        }
488
489
        if (corsRequestType == null) {
490
            throw new IllegalArgumentException("CORSRequestType object is null");
491
        }
492
493
        switch (corsRequestType) {
494
        case SIMPLE:
495
            request.setAttribute(
496
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, true);
497
            request.setAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
498
                    request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN));
499
            request.setAttribute(
500
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
501
                    corsRequestType.name().toLowerCase());
502
            break;
503
        case ACTUAL:
504
            request.setAttribute(
505
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, true);
506
            request.setAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
507
                    request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN));
508
            request.setAttribute(
509
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
510
                    corsRequestType.name().toLowerCase());
511
            break;
512
        case PRE_FLIGHT:
513
            request.setAttribute(
514
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, true);
515
            request.setAttribute(CORSFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
516
                    request.getHeader(CORSFilter.REQUEST_HEADER_ORIGIN));
517
            request.setAttribute(
518
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
519
                    corsRequestType.name().toLowerCase());
520
            String headers = request
521
                    .getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
522
            if (headers == null) {
523
                headers = "";
524
            }
525
            request.setAttribute(
526
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS, headers);
527
            break;
528
        case NOT_CORS:
529
            request.setAttribute(
530
                    CORSFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST, false);
531
            break;
532
        default:
533
            // Don't set any attributes
534
            break;
535
        }
536
    }
537
538
    /**
539
     * Joins elements of {@link Set} into a string, where each element is
540
     * separated by the provided separator.
541
     * 
542
     * @param elements
543
     *            The {@link Set} containing elements to join together.
544
     * @param joinSeparator
545
     *            The character to be used for separating elements.
546
     * @return The joined {@link String}; <code>null</code> if elements
547
     *         {@link Set} is null.
548
     */
549
    public static String join(final Collection<String> elements,
550
            final String joinSeparator) {
551
        String separator = ",";
552
        if (elements == null) {
553
            return null;
554
        }
555
        if (joinSeparator != null) {
556
            separator = joinSeparator;
557
        }
558
        StringBuilder buffer = new StringBuilder();
559
        boolean isFirst = true;
560
        for (String element : elements) {
561
            if (!isFirst) {
562
                buffer.append(separator);
563
            } else {
564
                isFirst = false;
565
            }
566
567
            if (element != null) {
568
                buffer.append(element);
569
            }
570
        }
571
572
        return buffer.toString();
573
    }
574
575
    /**
576
     * Determines the request type.
577
     * 
578
     * @param request
579
     * @return
580
     */
581
    public CORSRequestType checkRequestType(final HttpServletRequest request) {
582
        CORSRequestType requestType = CORSRequestType.INVALID_CORS;
583
        if (request == null) {
584
            throw new IllegalArgumentException(
585
                    "HttpServletRequest object is null");
586
        }
587
        String originHeader = request.getHeader(REQUEST_HEADER_ORIGIN);
588
        // Section 6.1.1 and Section 6.2.1
589
        if (originHeader != null) {
590
            if (originHeader.isEmpty()) {
591
                requestType = CORSRequestType.INVALID_CORS;
592
            } else if (!isValidOrigin(originHeader)) {
593
                requestType = CORSRequestType.INVALID_CORS;
594
            } else {
595
                String method = request.getMethod();
596
                if (method != null && HTTP_METHODS.contains(method)) {
597
                    if ("OPTIONS".equals(method)) {
598
                        String accessControlRequestMethodHeader = request
599
                                .getHeader(REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
600
                        if (accessControlRequestMethodHeader != null
601
                                && !accessControlRequestMethodHeader.isEmpty()) {
602
                            requestType = CORSRequestType.PRE_FLIGHT;
603
                        } else if (accessControlRequestMethodHeader != null
604
                                && accessControlRequestMethodHeader.isEmpty()) {
605
                            requestType = CORSRequestType.INVALID_CORS;
606
                        } else {
607
                            requestType = CORSRequestType.ACTUAL;
608
                        }
609
                    } else if ("GET".equals(method) || "HEAD".equals(method)) {
610
                        requestType = CORSRequestType.SIMPLE;
611
                    } else if ("POST".equals(method)) {
612
                        String contentType = request.getContentType();
613
                        if (contentType != null) {
614
                            contentType = contentType.toLowerCase().trim();
615
                            if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES
616
                                    .contains(contentType)) {
617
                                requestType = CORSRequestType.SIMPLE;
618
                            } else {
619
                                requestType = CORSRequestType.ACTUAL;
620
                            }
621
                        }
622
                    } else if (COMPLEX_HTTP_METHODS.contains(method)) {
623
                        requestType = CORSRequestType.ACTUAL;
624
                    }
625
                }
626
            }
627
        } else {
628
            requestType = CORSRequestType.NOT_CORS;
629
        }
630
631
        return requestType;
632
    }
633
634
    /**
635
     * Checks if the Origin is allowed to make a CORS request.
636
     * 
637
     * @param origin
638
     *            The Origin.
639
     * @return <code>true</code> if origin is allowed; <code>false</code>
640
     *         otherwise.
641
     */
642
    private boolean isOriginAllowed(final String origin) {
643
        if (anyOriginAllowed) {
644
            return true;
645
        }
646
647
        // If 'Origin' header is a case-sensitive match of any of allowed
648
        // origins, then return true, else return false.
649
        return allowedOrigins.contains(origin);
650
    }
651
652
    private void log(String message) {
653
        if (loggingEnabled) {
654
            filterConfig.getServletContext().log(message);
655
        }
656
    }
657
658
    /**
659
     * Parses each param-value and populates configuration variables. If a param
660
     * is provided, it overrides the default.
661
     * 
662
     * @param allowedOrigins
663
     *            A {@link String} of comma separated origins.
664
     * @param allowedHttpMethods
665
     *            A {@link String} of comma separated HTTP methods.
666
     * @param allowedHttpHeaders
667
     *            A {@link String} of comma separated HTTP headers.
668
     * @param exposedHeaders
669
     *            A {@link String} of comma separated headers that needs to be
670
     *            exposed.
671
     * @param supportsCredentials
672
     *            "true" if support credentials needs to be enabled.
673
     * @param preflightMaxAge
674
     *            The amount of seconds the user agent is allowed to cache the
675
     *            result of the pre-flight request.
676
     * @param loggingEnabled
677
     *            Flag to control logging to access log.
678
     * @throws ServletException
679
     */
680
    private void parseAndStore(final String allowedOrigins,
681
            final String allowedHttpMethods, final String allowedHttpHeaders,
682
            final String exposedHeaders, final String supportsCredentials,
683
            final String preflightMaxAge, final String loggingEnabled,
684
            final String decorateRequest) throws ServletException {
685
        if (allowedOrigins != null) {
686
            if (allowedOrigins.trim().equals("*")) {
687
                this.anyOriginAllowed = true;
688
            } else {
689
                this.anyOriginAllowed = false;
690
                Set<String> setAllowedOrigins = parseStringToSet(allowedOrigins);
691
                this.allowedOrigins.clear();
692
                this.allowedOrigins.addAll(setAllowedOrigins);
693
            }
694
        }
695
696
        if (allowedHttpMethods != null) {
697
            Set<String> setAllowedHttpMethods = parseStringToSet(allowedHttpMethods);
698
            this.allowedHttpMethods.clear();
699
            this.allowedHttpMethods.addAll(setAllowedHttpMethods);
700
        }
701
702
        if (allowedHttpHeaders != null) {
703
            Set<String> setAllowedHttpHeaders = parseStringToSet(allowedHttpHeaders);
704
            Set<String> lowerCaseHeaders = new HashSet<String>();
705
            for (String header : setAllowedHttpHeaders) {
706
                String lowerCase = header.toLowerCase();
707
                lowerCaseHeaders.add(lowerCase);
708
            }
709
            this.allowedHttpHeaders.clear();
710
            this.allowedHttpHeaders.addAll(lowerCaseHeaders);
711
        }
712
713
        if (exposedHeaders != null) {
714
            Set<String> setExposedHeaders = parseStringToSet(exposedHeaders);
715
            this.exposedHeaders.clear();
716
            this.exposedHeaders.addAll(setExposedHeaders);
717
        }
718
719
        if (supportsCredentials != null) {
720
            // For any value other then 'true' this will be false.
721
            this.supportsCredentials = Boolean
722
                    .parseBoolean(supportsCredentials);
723
        }
724
725
        if (preflightMaxAge != null) {
726
            try {
727
                if (!preflightMaxAge.isEmpty()) {
728
                    this.preflightMaxAge = Long.parseLong(preflightMaxAge);
729
                } else {
730
                    this.preflightMaxAge = 0L;
731
                }
732
            } catch (NumberFormatException e) {
733
                throw new ServletException("Unable to parse preflightMaxAge", e);
734
            }
735
        }
736
737
        if (loggingEnabled != null) {
738
            // For any value other then 'true' this will be false.
739
            this.loggingEnabled = Boolean.parseBoolean(loggingEnabled);
740
        }
741
742
        if (decorateRequest != null) {
743
            // For any value other then 'true' this will be false.
744
            this.decorateRequest = Boolean.parseBoolean(decorateRequest);
745
        }
746
    }
747
748
    /**
749
     * Takes a comma separated list and returns a Set<String>.
750
     * 
751
     * @param data
752
     *            A comma separated list of strings.
753
     * @return Set<String>
754
     */
755
    private Set<String> parseStringToSet(final String data) {
756
        String[] splits;
757
758
        if (data != null && data.length() > 0) {
759
            splits = data.split(",");
760
        } else {
761
            splits = new String[] {};
762
        }
763
764
        Set<String> set = new HashSet<String>();
765
        if (splits.length > 0) {
766
            for (String split : splits) {
767
                set.add(split.trim());
768
            }
769
        }
770
771
        return set;
772
    }
773
774
    /**
775
     * Checks if a given origin is valid or not. Criteria:
776
     * <ul>
777
     * <li>If an encoded character is present in origin, it's not valid.</li>
778
     * <li>Origin should be a valid {@link URI}</li>
779
     * </ul>
780
     * 
781
     * @param origin
782
     * @see <a href="http://tools.ietf.org/html/rfc952">RFC952</a>
783
     * @return
784
     */
785
    public static boolean isValidOrigin(String origin) {
786
        // Checks for encoded characters. Helps prevent CRLF injection.
787
        if (origin.contains("%")) {
788
            return false;
789
        }
790
791
        URI originURI;
792
793
        try {
794
            originURI = new URI(origin);
795
        } catch (URISyntaxException e) {
796
            return false;
797
        }
798
        // If scheme for URI is null, return false. Return true otherwise.
799
        return originURI.getScheme() != null;
800
801
    }
802
803
    // -------------------------------------------------------------- Accessors
804
    /**
805
     * Determines if logging is enabled or not.
806
     * 
807
     * @return <code>true</code> if it's enabled; false otherwise.
808
     */
809
    public boolean isLoggingEnabled() {
810
        return loggingEnabled;
811
    }
812
813
    /**
814
     * Determines if any origin is allowed to make CORS request.
815
     * 
816
     * @return <code>true</code> if it's enabled; false otherwise.
817
     */
818
    public boolean isAnyOriginAllowed() {
819
        return anyOriginAllowed;
820
    }
821
822
    /**
823
     * Returns a {@link Set} of headers that should be exposed by browser.
824
     * 
825
     * @return
826
     */
827
    public Collection<String> getExposedHeaders() {
828
        return exposedHeaders;
829
    }
830
831
    /**
832
     * Determines is supports credentials is enabled
833
     * 
834
     * @return
835
     */
836
    public boolean isSupportsCredentials() {
837
        return supportsCredentials;
838
    }
839
840
    /**
841
     * Returns the preflight response cache time in seconds.
842
     * 
843
     * @return Time to cache in seconds.
844
     */
845
    public long getPreflightMaxAge() {
846
        return preflightMaxAge;
847
    }
848
849
    /**
850
     * Returns the {@link Set} of allowed origins that are allowed to make
851
     * requests.
852
     * 
853
     * @return {@link Set}
854
     */
855
    public Collection<String> getAllowedOrigins() {
856
        return allowedOrigins;
857
    }
858
859
    /**
860
     * Returns a {@link Set} of HTTP methods that are allowed to make requests.
861
     * 
862
     * @return {@link Set}
863
     */
864
    public Collection<String> getAllowedHttpMethods() {
865
        return allowedHttpMethods;
866
    }
867
868
    /**
869
     * Returns a {@link Set} of headers support by resource.
870
     * 
871
     * @return {@link Set}
872
     */
873
    public Collection<String> getAllowedHttpHeaders() {
874
        return allowedHttpHeaders;
875
    }
876
877
    // -------------------------------------------------- CORS Response Headers
878
    /**
879
     * The Access-Control-Allow-Origin header indicates whether a resource can
880
     * be shared based by returning the value of the Origin request header in
881
     * the response.
882
     */
883
    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
884
885
    /**
886
     * The Access-Control-Allow-Credentials header indicates whether the
887
     * response to request can be exposed when the omit credentials flag is
888
     * unset. When part of the response to a preflight request it indicates that
889
     * the actual request can include user credentials.
890
     */
891
    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
892
893
    /**
894
     * The Access-Control-Expose-Headers header indicates which headers are safe
895
     * to expose to the API of a CORS API specification
896
     */
897
    public static final String RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
898
899
    /**
900
     * The Access-Control-Max-Age header indicates how long the results of a
901
     * preflight request can be cached in a preflight result cache.
902
     */
903
    public static final String RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
904
905
    /**
906
     * The Access-Control-Allow-Methods header indicates, as part of the
907
     * response to a preflight request, which methods can be used during the
908
     * actual request.
909
     */
910
    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
911
912
    /**
913
     * The Access-Control-Allow-Headers header indicates, as part of the
914
     * response to a preflight request, which header field names can be used
915
     * during the actual request.
916
     */
917
    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
918
919
    // -------------------------------------------------- CORS Request Headers
920
    /**
921
     * The Origin header indicates where the cross-origin request or preflight
922
     * request originates from.
923
     */
924
    public static final String REQUEST_HEADER_ORIGIN = "Origin";
925
926
    /**
927
     * The Access-Control-Request-Method header indicates which method will be
928
     * used in the actual request as part of the preflight request.
929
     */
930
    public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
931
932
    /**
933
     * The Access-Control-Request-Headers header indicates which headers will be
934
     * used in the actual request as part of the preflight request.
935
     */
936
    public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
937
938
    // ----------------------------------------------------- Request attributes
939
    /**
940
     * The prefix to a CORS request attribute.
941
     */
942
    public static final String HTTP_REQUEST_ATTRIBUTE_PREFIX = "cors.";
943
944
    /**
945
     * Attribute that contains the origin of the request.
946
     */
947
    public static final String HTTP_REQUEST_ATTRIBUTE_ORIGIN = HTTP_REQUEST_ATTRIBUTE_PREFIX
948
            + "request.origin";
949
950
    /**
951
     * Boolean value, suggesting if the request is a CORS request or not.
952
     */
953
    public static final String HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST = HTTP_REQUEST_ATTRIBUTE_PREFIX
954
            + "isCorsRequest";
955
956
    /**
957
     * Type of CORS request, of type {@link CORSRequestType}.
958
     */
959
    public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE = HTTP_REQUEST_ATTRIBUTE_PREFIX
960
            + "request.type";
961
962
    /**
963
     * Request headers sent as 'Access-Control-Request-Headers' header, for
964
     * pre-flight request.
965
     */
966
    public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS = HTTP_REQUEST_ATTRIBUTE_PREFIX
967
            + "request.headers";
968
969
    // -------------------------------------------------------------- Constants
970
    /**
971
     * Enumerates varies types of CORS requests. Also, provides utility methods
972
     * to determine the request type.
973
     */
974
    public static enum CORSRequestType {
975
        /**
976
         * A simple HTTP request, i.e. it shouldn't be pre-flighted.
977
         */
978
        SIMPLE,
979
        /**
980
         * A HTTP request that needs to be pre-flighted.
981
         */
982
        ACTUAL,
983
        /**
984
         * A pre-flight CORS request, to get meta information, before a
985
         * non-simple HTTP request is sent.
986
         */
987
        PRE_FLIGHT,
988
        /**
989
         * Not a CORS request, but a normal request.
990
         */
991
        NOT_CORS,
992
        /**
993
         * An invalid CORS request, i.e. it qualifies to be a CORS request, but
994
         * fails to be a valid one.
995
         */
996
        INVALID_CORS
997
    }
998
999
    /**
1000
     * {@link Collection} of HTTP methods. Case sensitive.
1001
     * 
1002
     * @see http://tools.ietf.org/html/rfc2616#section-5.1.1
1003
     */
1004
    public static final Collection<String> HTTP_METHODS = new HashSet<String>(
1005
            Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE",
1006
                    "TRACE", "CONNECT"));
1007
    /**
1008
     * {@link Collection} of non-simple HTTP methods. Case sensitive.
1009
     */
1010
    public static final Collection<String> COMPLEX_HTTP_METHODS = new HashSet<String>(
1011
            Arrays.asList("PUT", "DELETE", "TRACE", "CONNECT"));
1012
    /**
1013
     * {@link Collection} of Simple HTTP methods. Case sensitive.
1014
     * 
1015
     * @see http://www.w3.org/TR/cors/#terminology
1016
     */
1017
    public static final Collection<String> SIMPLE_HTTP_METHODS = new HashSet<String>(
1018
            Arrays.asList("GET", "POST", "HEAD"));
1019
1020
    /**
1021
     * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
1022
     * 
1023
     * @see http://www.w3.org/TR/cors/#terminology
1024
     */
1025
    public static final Collection<String> SIMPLE_HTTP_REQUEST_HEADERS = new HashSet<String>(
1026
            Arrays.asList("Accept", "Accept-Language", "Content-Language"));
1027
1028
    /**
1029
     * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
1030
     * 
1031
     * @see http://www.w3.org/TR/cors/#terminology
1032
     */
1033
    public static final Collection<String> SIMPLE_HTTP_RESPONSE_HEADERS = new HashSet<String>(
1034
            Arrays.asList("Cache-Control", "Content-Language", "Content-Type",
1035
                    "Expires", "Last-Modified", "Pragma"));
1036
1037
    /**
1038
     * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
1039
     * 
1040
     * @see http://www.w3.org/TR/cors/#terminology
1041
     */
1042
    public static final Collection<String> SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES = new HashSet<String>(
1043
            Arrays.asList("application/x-www-form-urlencoded",
1044
                    "multipart/form-data", "text/plain"));
1045
1046
    // ------------------------------------------------ Configuration Defaults
1047
    /**
1048
     * By default, all origins are allowed to make requests.
1049
     */
1050
    public static final String DEFAULT_ALLOWED_ORIGINS = "*";
1051
1052
    /**
1053
     * By default, following methods are supported: GET, POST, HEAD and OPTIONS.
1054
     */
1055
    public static final String DEFAULT_ALLOWED_HTTP_METHODS = "GET,POST,HEAD,OPTIONS";
1056
1057
    /**
1058
     * By default, time duration to cache pre-flight response is 30 mins.
1059
     */
1060
    public static final String DEFAULT_PREFLIGHT_MAXAGE = "1800";
1061
1062
    /**
1063
     * By default, support credentials is turned on.
1064
     */
1065
    public static final String DEFAULT_SUPPORTS_CREDENTIALS = "true";
1066
1067
    /**
1068
     * By default, following headers are supported:
1069
     * Origin,Accept,X-Requested-With, Content-Type,
1070
     * Access-Control-Request-Method, and Access-Control-Request-Headers.
1071
     */
1072
    public static final String DEFAULT_ALLOWED_HTTP_HEADERS = "Origin,Accept,X-Requested-With,Content-Type,"
1073
            + "Access-Control-Request-Method,Access-Control-Request-Headers";
1074
1075
    /**
1076
     * By default, none of the headers are exposed in response.
1077
     */
1078
    public static final String DEFAULT_EXPOSED_HEADERS = "";
1079
1080
    /**
1081
     * By default, access log logging is turned off
1082
     */
1083
    public static final String DEFAULT_LOGGING_ENABLED = "false";
1084
1085
    /**
1086
     * By default, request is decorated with CORS attributes.
1087
     */
1088
    public static final String DEFAULT_DECORATE_REQUEST = "true";
1089
1090
    // ----------------------------------------Filter Config Init param-name(s)
1091
    /**
1092
     * Key to retrieve allowed origins from {@link FilterConfig}.
1093
     */
1094
    public static final String PARAM_CORS_ALLOWED_ORIGINS = "cors.allowed.origins";
1095
1096
    /**
1097
     * Key to retrieve support credentials from {@link FilterConfig}.
1098
     */
1099
    public static final String PARAM_CORS_SUPPORT_CREDENTIALS = "cors.support.credentials";
1100
1101
    /**
1102
     * Key to retrieve exposed headers from {@link FilterConfig}.
1103
     */
1104
    public static final String PARAM_CORS_EXPOSED_HEADERS = "cors.exposed.headers";
1105
1106
    /**
1107
     * Key to retrieve allowed headers from {@link FilterConfig}.
1108
     */
1109
    public static final String PARAM_CORS_ALLOWED_HEADERS = "cors.allowed.headers";
1110
1111
    /**
1112
     * Key to retrieve allowed methods from {@link FilterConfig}.
1113
     */
1114
    public static final String PARAM_CORS_ALLOWED_METHODS = "cors.allowed.methods";
1115
1116
    /**
1117
     * Key to retrieve preflight max age from {@link FilterConfig}.
1118
     */
1119
    public static final String PARAM_CORS_PREFLIGHT_MAXAGE = "cors.preflight.maxage";
1120
1121
    /**
1122
     * Key to retrieve access log logging flag.
1123
     */
1124
    public static final String PARAM_CORS_LOGGING_ENABLED = "cors.logging.enabled";
1125
1126
    /**
1127
     * Key to determine if request should be decorated.
1128
     */
1129
    public static final String PARAM_CORS_REQUEST_DECORATE = "cors.request.decorate";
1130
}
0
  + native
1131
  + native
(-)webapps/docs/config/filter.xml (+112 lines)
Lines 1309-1314 Link Here
1309
1309
1310
</section>
1310
</section>
1311
1311
1312
<section name="CORS Filter">
1313
  <subsection name="Introduction">
1314
    <p>This filter is an implementation of W3C's CORS (Cross-Origin Resource Sharing) <a href="http://www.w3.org/TR/cors/">specification</a>, which is a mechanism that enables cross-origin requests.</p>
1315
    <p>The filter works by adding required <code>Access-Control-*</code> 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 <a href="../images/cors-flowchart.png">here</a>.</p>
1316
    <p>The minimal configuration required to use this filter is:</p>
1317
    <source>
1318
&lt;filter&gt;
1319
  &lt;filter-name&gt;CORSFilter&lt;/filter-name&gt;
1320
  &lt;filter-class&gt;org.apache.catalina.filters.CORSFilter&lt;/filter-class&gt;
1321
&lt;/filter&gt;
1322
&lt;filter-mapping&gt;
1323
  &lt;filter-name>CORSFilter&lt;/filter-name&gt;
1324
  &lt;url-pattern>/*&lt;/url-pattern&gt;
1325
&lt;/filter-mapping&gt;
1326
    </source>
1327
  </subsection>
1328
  <subsection name="Filter Class Name">
1329
    <p>The filter class name for the CORS Filter is <strong><code>org.apache.catalina.filters.CORSFilter</code></strong>.</p>
1330
  </subsection>
1331
  <subsection name="Initialisation parameters">
1332
    <p>The CORS Filter supports following initialisation parameters:</p>
1333
    <attributes>
1334
      <attribute name="cors.allowed.origins" required="false">
1335
        <p>A list of <a href="http://tools.ietf.org/html/rfc6454">origins</a> that are allowed to access the resource. A <code>'*'</code> 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. <strong>Defaults:</strong> <code>*</code> (Any origin is allowed to access the resource).</p>
1336
      </attribute>
1337
      <attribute name="cors.allowed.methods" required="false">
1338
        <p>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: <code>GET,POST</code>. <strong>Defaults:</strong> <code>GET,POST,HEAD,OPTIONS</code></p>
1339
      </attribute>
1340
      <attribute name="cors.allowed.headers" required="false">
1341
        <p>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 <code>'Access-Control-Allow-Headers'</code> header in a pre-flight response. Ex: <code>Origin,Accept</code>. <strong>Defaults:</strong> <code>Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</code></p>
1342
      </attribute>
1343
      <attribute name="cors.exposed.headers" required="false">
1344
        <p>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: <code>X-CUSTOM-HEADER-PING,X-CUSTOM-HEADER-PONG</code>. <strong>Default:</strong> None. Non-simple headers are not exposed by default.</p>
1345
      </attribute>
1346
      <attribute name="cors.preflight.maxage" required="false">
1347
        <p>The amount of seconds, browser is allowed to cache the result of the pre-flight request. This will be included as part of <code>'Access-Control-Max-Age'</code> header in the pre-flight response. A negative value will prevent CORS Filter from adding this response header from pre-flight response. <strong>Defaults:</strong> <code>1800</code></p>
1348
      </attribute>
1349
      <attribute name="cors.support.credentials" required="false">
1350
        <p>A flag that indicates whether the resource supports user credentials. This flag is exposed as part of <code>'Access-Control-Allow-Credentials'</code> header in a pre-flight response. It helps browser determine whether or not an actual request can be made using credentials. <strong>Defaults:</strong> <code>true</code></p>
1351
      </attribute>
1352
      <attribute name="cors.logging.enabled" required="false">
1353
        <p>A flag to control logging to container logs. <strong>Defaults:</strong> <code>false</code></p>
1354
      </attribute>
1355
      <attribute name="cors.request.decorate" required="false">
1356
        <p>A flag to control if CORS specific attributes should be added to HttpServletRequest object or not. <strong>Defaults:</strong> <code>true</code></p>
1357
      </attribute>
1358
    </attributes>
1359
    <p>Here's an example of a more advanced configuration, that overrides defaults:</p>
1360
    <source>
1361
&lt;filter&gt;
1362
  &lt;filter-name&gt;CORSFilter&lt;/filter-name&gt;
1363
  &lt;filter-class&gt;org.apache.catalina.filters.CORSFilter&lt;/filter-class&gt;
1364
  &lt;init-param&gt;
1365
    &lt;description&gt;A comma separated list of allowed origins&lt;/description&gt;
1366
    &lt;param-name&gt;cors.allowed.origins&lt;/param-name&gt;
1367
    &lt;param-value&gt;*&lt;/param-value&gt;
1368
  &lt;/init-param&gt;
1369
  &lt;init-param&gt;
1370
    &lt;description&gt;A comma separated list of HTTP verbs, using which a cross-origin request can be made.&lt;/description&gt;
1371
    &lt;param-name&gt;cors.allowed.methods&lt;/param-name&gt;
1372
    &lt;param-value&gt;GET,POST,HEAD,OPTIONS,PUT&lt;/param-value&gt;
1373
  &lt;/init-param&gt;
1374
  &lt;init-param&gt;
1375
    &lt;description&gt;A comma separated list of allowed headers when making a non simple cross-origin request.&lt;/description&gt;
1376
    &lt;param-name&gt;cors.allowed.headers&lt;/param-name&gt;
1377
    &lt;param-value&gt;Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers&lt;/param-value&gt;
1378
  &lt;/init-param&gt;
1379
  &lt;init-param&gt;
1380
    &lt;description&gt;A comma separated list of non-standard response headers that will be exposed to XMLHttpRequest object.&lt;/description&gt;
1381
    &lt;param-name&gt;cors.exposed.headers&lt;/param-name&gt;
1382
    &lt;param-value&gt;Access-Control-Allow-Origin,Access-Control-Allow-Credentials&lt;/param-value&gt;
1383
  &lt;/init-param&gt;
1384
  &lt;init-param&gt;
1385
    &lt;description&gt;A flag that suggests if a cross-origin request is supported with cookies, BASIC Authentication, etc.&lt;/description&gt;
1386
    &lt;param-name&gt;cors.support.credentials&lt;/param-name&gt;
1387
    &lt;param-value&gt;true&lt;/param-value&gt;
1388
  &lt;/init-param&gt;
1389
  &lt;init-param&gt;
1390
    &lt;description&gt;A flag control logging&lt;/description&gt;
1391
    &lt;param-name&gt;cors.logging.enabled&lt;/param-name&gt;
1392
    &lt;param-value&gt;true&lt;/param-value&gt;
1393
  &lt;/init-param&gt;
1394
  &lt;init-param&gt;
1395
    &lt;description&gt;Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.&lt;/description&gt;
1396
    &lt;param-name&gt;cors.preflight.maxage&lt;/param-name&gt;
1397
    &lt;param-value&gt;10&lt;/param-value&gt;
1398
  &lt;/init-param&gt;
1399
&lt;/filter&gt;
1400
&lt;filter-mapping&gt;
1401
  &lt;filter-name&gt;CORS Filter&lt;/filter-name&gt;
1402
  &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
1403
&lt;/filter-mapping&gt;
1404
    </source>
1405
  </subsection>
1406
  <subsection name="CORS Filter and HttpServletRequest attributes" required="false">
1407
    <p>CORS Filter adds information about a request, in the HttpServletRequest object, for consumption downstream. Following attributes are set, if <code>cors.request.decorate</code> initialisation parameter is <code>true</code>:</p>
1408
    <ul>
1409
      <li><strong>cors.isCorsRequest:</strong> Flag to determine if a request is a CORS request.</li>
1410
      <li><strong>cors.request.origin:</strong> The Origin URL, i.e. the URL of the page from where the request is originated.</li>
1411
      <li><strong>cors.request.type:</strong> Type of CORS request. Possible values: 
1412
        <ul>
1413
          <li><code>SIMPLE</code>: A request which is not preceded by a pre-flight request.</li>
1414
          <li><code>ACTUAL</code>: A request which is preceded by a pre-flight request.</li>
1415
          <li><code>PRE_FLIGHT</code>: A pre-flight request.</li>
1416
          <li><code>NOT_CORS</code>: A normal same-origin request.</li>
1417
          <li><code>INVALID_CORS</code>: A cross-origin request, which is invalid.</li>
1418
        </ul>
1419
      </li>
1420
      <li><strong>cors.request.headers:</strong> Request headers sent as 'Access-Control-Request-Headers' header, for a pre-flight request.</li>
1421
    </ul>
1422
  </subsection>
1423
</section>
1312
1424
1313
</body>
1425
</body>
1314
1426

Return to bug 55046