1 /*
2 * Copyright 2013 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License, version
5 * 2.0 (the "License"); you may not use this file except in compliance with the
6 * License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16 package io.netty.handler.codec.http.cors;
17
18 import io.netty.handler.codec.http.DefaultHttpHeaders;
19 import io.netty.handler.codec.http.HttpHeaders;
20 import io.netty.handler.codec.http.HttpHeaders.Names;
21 import io.netty.handler.codec.http.HttpMethod;
22 import io.netty.util.internal.StringUtil;
23
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.LinkedHashSet;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Set;
33 import java.util.concurrent.Callable;
34
35 /**
36 * Configuration for Cross-Origin Resource Sharing (CORS).
37 */
38 public final class CorsConfig {
39
40 private final Set<String> origins;
41 private final boolean anyOrigin;
42 private final boolean enabled;
43 private final Set<String> exposeHeaders;
44 private final boolean allowCredentials;
45 private final long maxAge;
46 private final Set<HttpMethod> allowedRequestMethods;
47 private final Set<String> allowedRequestHeaders;
48 private final boolean allowNullOrigin;
49 private final Map<CharSequence, Callable<?>> preflightHeaders;
50 private final boolean shortCurcuit;
51
52 private CorsConfig(final Builder builder) {
53 origins = new LinkedHashSet<String>(builder.origins);
54 anyOrigin = builder.anyOrigin;
55 enabled = builder.enabled;
56 exposeHeaders = builder.exposeHeaders;
57 allowCredentials = builder.allowCredentials;
58 maxAge = builder.maxAge;
59 allowedRequestMethods = builder.requestMethods;
60 allowedRequestHeaders = builder.requestHeaders;
61 allowNullOrigin = builder.allowNullOrigin;
62 preflightHeaders = builder.preflightHeaders;
63 shortCurcuit = builder.shortCurcuit;
64 }
65
66 /**
67 * Determines if support for CORS is enabled.
68 *
69 * @return {@code true} if support for CORS is enabled, false otherwise.
70 */
71 public boolean isCorsSupportEnabled() {
72 return enabled;
73 }
74
75 /**
76 * Determines whether a wildcard origin, '*', is supported.
77 *
78 * @return {@code boolean} true if any origin is allowed.
79 */
80 public boolean isAnyOriginSupported() {
81 return anyOrigin;
82 }
83
84 /**
85 * Returns the allowed origin. This can either be a wildcard or an origin value.
86 *
87 * @return the value that will be used for the CORS response header 'Access-Control-Allow-Origin'
88 */
89 public String origin() {
90 return origins.isEmpty() ? "*" : origins.iterator().next();
91 }
92
93 /**
94 * Returns the set of allowed origins.
95 *
96 * @return {@code Set} the allowed origins.
97 */
98 public Set<String> origins() {
99 return origins;
100 }
101
102 /**
103 * Web browsers may set the 'Origin' request header to 'null' if a resource is loaded
104 * from the local file system.
105 *
106 * If isNullOriginAllowed is true then the server will response with the wildcard for the
107 * the CORS response header 'Access-Control-Allow-Origin'.
108 *
109 * @return {@code true} if a 'null' origin should be supported.
110 */
111 public boolean isNullOriginAllowed() {
112 return allowNullOrigin;
113 }
114
115 /**
116 * Returns a set of headers to be exposed to calling clients.
117 *
118 * During a simple CORS request only certain response headers are made available by the
119 * browser, for example using:
120 * <pre>
121 * xhr.getResponseHeader("Content-Type");
122 * </pre>
123 * The headers that are available by default are:
124 * <ul>
125 * <li>Cache-Control</li>
126 * <li>Content-Language</li>
127 * <li>Content-Type</li>
128 * <li>Expires</li>
129 * <li>Last-Modified</li>
130 * <li>Pragma</li>
131 * </ul>
132 * To expose other headers they need to be specified, which is what this method enables by
133 * adding the headers names to the CORS 'Access-Control-Expose-Headers' response header.
134 *
135 * @return {@code List<String>} a list of the headers to expose.
136 */
137 public Set<String> exposedHeaders() {
138 return Collections.unmodifiableSet(exposeHeaders);
139 }
140
141 /**
142 * Determines if cookies are supported for CORS requests.
143 *
144 * By default cookies are not included in CORS requests but if isCredentialsAllowed returns
145 * true cookies will be added to CORS requests. Setting this value to true will set the
146 * CORS 'Access-Control-Allow-Credentials' response header to true.
147 *
148 * Please note that cookie support needs to be enabled on the client side as well.
149 * The client needs to opt-in to send cookies by calling:
150 * <pre>
151 * xhr.withCredentials = true;
152 * </pre>
153 * The default value for 'withCredentials' is false in which case no cookies are sent.
154 * Setting this to true will included cookies in cross origin requests.
155 *
156 * @return {@code true} if cookies are supported.
157 */
158 public boolean isCredentialsAllowed() {
159 return allowCredentials;
160 }
161
162 /**
163 * Gets the maxAge setting.
164 *
165 * When making a preflight request the client has to perform two request with can be inefficient.
166 * This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
167 * caching of the preflight response for the specified time. During this time no preflight
168 * request will be made.
169 *
170 * @return {@code long} the time in seconds that a preflight request may be cached.
171 */
172 public long maxAge() {
173 return maxAge;
174 }
175
176 /**
177 * Returns the allowed set of Request Methods. The Http methods that should be returned in the
178 * CORS 'Access-Control-Request-Method' response header.
179 *
180 * @return {@code Set} of {@link HttpMethod}s that represent the allowed Request Methods.
181 */
182 public Set<HttpMethod> allowedRequestMethods() {
183 return Collections.unmodifiableSet(allowedRequestMethods);
184 }
185
186 /**
187 * Returns the allowed set of Request Headers.
188 *
189 * The header names returned from this method will be used to set the CORS
190 * 'Access-Control-Allow-Headers' response header.
191 *
192 * @return {@code Set<String>} of strings that represent the allowed Request Headers.
193 */
194 public Set<String> allowedRequestHeaders() {
195 return Collections.unmodifiableSet(allowedRequestHeaders);
196 }
197
198 /**
199 * Returns HTTP response headers that should be added to a CORS preflight response.
200 *
201 * @return {@link HttpHeaders} the HTTP response headers to be added.
202 */
203 public HttpHeaders preflightResponseHeaders() {
204 if (preflightHeaders.isEmpty()) {
205 return HttpHeaders.EMPTY_HEADERS;
206 }
207 final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
208 for (Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {
209 final Object value = getValue(entry.getValue());
210 if (value instanceof Iterable) {
211 preflightHeaders.add(entry.getKey(), (Iterable<?>) value);
212 } else {
213 preflightHeaders.add(entry.getKey(), value);
214 }
215 }
216 return preflightHeaders;
217 }
218
219 /**
220 * Determines whether a CORS request should be rejected if it's invalid before being
221 * further processing.
222 *
223 * CORS headers are set after a request is processed. This may not always be desired
224 * and this setting will check that the Origin is valid and if it is not valid no
225 * further processing will take place, and a error will be returned to the calling client.
226 *
227 * @return {@code true} if a CORS request should short-circuit upon receiving an invalid Origin header.
228 */
229 public boolean isShortCurcuit() {
230 return shortCurcuit;
231 }
232
233 private static <T> T getValue(final Callable<T> callable) {
234 try {
235 return callable.call();
236 } catch (final Exception e) {
237 throw new IllegalStateException("Could not generate value for callable [" + callable + ']', e);
238 }
239 }
240
241 @Override
242 public String toString() {
243 return StringUtil.simpleClassName(this) + "[enabled=" + enabled +
244 ", origins=" + origins +
245 ", anyOrigin=" + anyOrigin +
246 ", exposedHeaders=" + exposeHeaders +
247 ", isCredentialsAllowed=" + allowCredentials +
248 ", maxAge=" + maxAge +
249 ", allowedRequestMethods=" + allowedRequestMethods +
250 ", allowedRequestHeaders=" + allowedRequestHeaders +
251 ", preflightHeaders=" + preflightHeaders + ']';
252 }
253
254 /**
255 * Creates a Builder instance with it's origin set to '*'.
256 *
257 * @return Builder to support method chaining.
258 */
259 public static Builder withAnyOrigin() {
260 return new Builder();
261 }
262
263 /**
264 * Creates a {@link Builder} instance with the specified origin.
265 *
266 * @return {@link Builder} to support method chaining.
267 */
268 public static Builder withOrigin(final String origin) {
269 if ("*".equals(origin)) {
270 return new Builder();
271 }
272 return new Builder(origin);
273 }
274
275 /**
276 * Creates a {@link Builder} instance with the specified origins.
277 *
278 * @return {@link Builder} to support method chaining.
279 */
280 public static Builder withOrigins(final String... origins) {
281 return new Builder(origins);
282 }
283
284 /**
285 * Builder used to configure and build a CorsConfig instance.
286 */
287 public static class Builder {
288
289 private final Set<String> origins;
290 private final boolean anyOrigin;
291 private boolean allowNullOrigin;
292 private boolean enabled = true;
293 private boolean allowCredentials;
294 private final Set<String> exposeHeaders = new HashSet<String>();
295 private long maxAge;
296 private final Set<HttpMethod> requestMethods = new HashSet<HttpMethod>();
297 private final Set<String> requestHeaders = new HashSet<String>();
298 private final Map<CharSequence, Callable<?>> preflightHeaders = new HashMap<CharSequence, Callable<?>>();
299 private boolean noPreflightHeaders;
300 private boolean shortCurcuit;
301
302 /**
303 * Creates a new Builder instance with the origin passed in.
304 *
305 * @param origins the origin to be used for this builder.
306 */
307 public Builder(final String... origins) {
308 this.origins = new LinkedHashSet<String>(Arrays.asList(origins));
309 anyOrigin = false;
310 }
311
312 /**
313 * Creates a new Builder instance allowing any origin, "*" which is the
314 * wildcard origin.
315 *
316 */
317 public Builder() {
318 anyOrigin = true;
319 origins = Collections.emptySet();
320 }
321
322 /**
323 * Web browsers may set the 'Origin' request header to 'null' if a resource is loaded
324 * from the local file system. Calling this method will enable a successful CORS response
325 * with a wildcard for the the CORS response header 'Access-Control-Allow-Origin'.
326 *
327 * @return {@link Builder} to support method chaining.
328 */
329 public Builder allowNullOrigin() {
330 allowNullOrigin = true;
331 return this;
332 }
333
334 /**
335 * Disables CORS support.
336 *
337 * @return {@link Builder} to support method chaining.
338 */
339 public Builder disable() {
340 enabled = false;
341 return this;
342 }
343
344 /**
345 * Specifies the headers to be exposed to calling clients.
346 *
347 * During a simple CORS request, only certain response headers are made available by the
348 * browser, for example using:
349 * <pre>
350 * xhr.getResponseHeader("Content-Type");
351 * </pre>
352 *
353 * The headers that are available by default are:
354 * <ul>
355 * <li>Cache-Control</li>
356 * <li>Content-Language</li>
357 * <li>Content-Type</li>
358 * <li>Expires</li>
359 * <li>Last-Modified</li>
360 * <li>Pragma</li>
361 * </ul>
362 *
363 * To expose other headers they need to be specified which is what this method enables by
364 * adding the headers to the CORS 'Access-Control-Expose-Headers' response header.
365 *
366 * @param headers the values to be added to the 'Access-Control-Expose-Headers' response header
367 * @return {@link Builder} to support method chaining.
368 */
369 public Builder exposeHeaders(final String... headers) {
370 exposeHeaders.addAll(Arrays.asList(headers));
371 return this;
372 }
373
374 /**
375 * By default cookies are not included in CORS requests, but this method will enable cookies to
376 * be added to CORS requests. Calling this method will set the CORS 'Access-Control-Allow-Credentials'
377 * response header to true.
378 *
379 * Please note, that cookie support needs to be enabled on the client side as well.
380 * The client needs to opt-in to send cookies by calling:
381 * <pre>
382 * xhr.withCredentials = true;
383 * </pre>
384 * The default value for 'withCredentials' is false in which case no cookies are sent.
385 * Settning this to true will included cookies in cross origin requests.
386 *
387 * @return {@link Builder} to support method chaining.
388 */
389 public Builder allowCredentials() {
390 allowCredentials = true;
391 return this;
392 }
393
394 /**
395 * When making a preflight request the client has to perform two request with can be inefficient.
396 * This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
397 * caching of the preflight response for the specified time. During this time no preflight
398 * request will be made.
399 *
400 * @param max the maximum time, in seconds, that the preflight response may be cached.
401 * @return {@link Builder} to support method chaining.
402 */
403 public Builder maxAge(final long max) {
404 maxAge = max;
405 return this;
406 }
407
408 /**
409 * Specifies the allowed set of HTTP Request Methods that should be returned in the
410 * CORS 'Access-Control-Request-Method' response header.
411 *
412 * @param methods the {@link HttpMethod}s that should be allowed.
413 * @return {@link Builder} to support method chaining.
414 */
415 public Builder allowedRequestMethods(final HttpMethod... methods) {
416 requestMethods.addAll(Arrays.asList(methods));
417 return this;
418 }
419
420 /**
421 * Specifies the if headers that should be returned in the CORS 'Access-Control-Allow-Headers'
422 * response header.
423 *
424 * If a client specifies headers on the request, for example by calling:
425 * <pre>
426 * xhr.setRequestHeader('My-Custom-Header', "SomeValue");
427 * </pre>
428 * the server will recieve the above header name in the 'Access-Control-Request-Headers' of the
429 * preflight request. The server will then decide if it allows this header to be sent for the
430 * real request (remember that a preflight is not the real request but a request asking the server
431 * if it allow a request).
432 *
433 * @param headers the headers to be added to the preflight 'Access-Control-Allow-Headers' response header.
434 * @return {@link Builder} to support method chaining.
435 */
436 public Builder allowedRequestHeaders(final String... headers) {
437 requestHeaders.addAll(Arrays.asList(headers));
438 return this;
439 }
440
441 /**
442 * Returns HTTP response headers that should be added to a CORS preflight response.
443 *
444 * An intermediary like a load balancer might require that a CORS preflight request
445 * have certain headers set. This enables such headers to be added.
446 *
447 * @param name the name of the HTTP header.
448 * @param values the values for the HTTP header.
449 * @return {@link Builder} to support method chaining.
450 */
451 public Builder preflightResponseHeader(final CharSequence name, final Object... values) {
452 if (values.length == 1) {
453 preflightHeaders.put(name, new ConstantValueGenerator(values[0]));
454 } else {
455 preflightResponseHeader(name, Arrays.asList(values));
456 }
457 return this;
458 }
459
460 /**
461 * Returns HTTP response headers that should be added to a CORS preflight response.
462 *
463 * An intermediary like a load balancer might require that a CORS preflight request
464 * have certain headers set. This enables such headers to be added.
465 *
466 * @param name the name of the HTTP header.
467 * @param value the values for the HTTP header.
468 * @param <T> the type of values that the Iterable contains.
469 * @return {@link Builder} to support method chaining.
470 */
471 public <T> Builder preflightResponseHeader(final CharSequence name, final Iterable<T> value) {
472 preflightHeaders.put(name, new ConstantValueGenerator(value));
473 return this;
474 }
475
476 /**
477 * Returns HTTP response headers that should be added to a CORS preflight response.
478 *
479 * An intermediary like a load balancer might require that a CORS preflight request
480 * have certain headers set. This enables such headers to be added.
481 *
482 * Some values must be dynamically created when the HTTP response is created, for
483 * example the 'Date' response header. This can be occomplished by using a Callable
484 * which will have its 'call' method invoked when the HTTP response is created.
485 *
486 * @param name the name of the HTTP header.
487 * @param valueGenerator a Callable which will be invoked at HTTP response creation.
488 * @param <T> the type of the value that the Callable can return.
489 * @return {@link Builder} to support method chaining.
490 */
491 public <T> Builder preflightResponseHeader(final String name, final Callable<T> valueGenerator) {
492 preflightHeaders.put(name, valueGenerator);
493 return this;
494 }
495
496 /**
497 * Specifies that no preflight response headers should be added to a preflight response.
498 *
499 * @return {@link Builder} to support method chaining.
500 */
501 public Builder noPreflightResponseHeaders() {
502 noPreflightHeaders = true;
503 return this;
504 }
505
506 /**
507 * Builds a {@link CorsConfig} with settings specified by previous method calls.
508 *
509 * @return {@link CorsConfig} the configured CorsConfig instance.
510 */
511 public CorsConfig build() {
512 if (preflightHeaders.isEmpty() && !noPreflightHeaders) {
513 preflightHeaders.put(Names.DATE, new DateValueGenerator());
514 preflightHeaders.put(Names.CONTENT_LENGTH, new ConstantValueGenerator("0"));
515 }
516 return new CorsConfig(this);
517 }
518
519 /**
520 * Specifies that a CORS request should be rejected if it's invalid before being
521 * further processing.
522 *
523 * CORS headers are set after a request is processed. This may not always be desired
524 * and this setting will check that the Origin is valid and if it is not valid no
525 * further processing will take place, and a error will be returned to the calling client.
526 *
527 * @return {@link Builder} to support method chaining.
528 */
529 public Builder shortCurcuit() {
530 shortCurcuit = true;
531 return this;
532 }
533 }
534
535 /**
536 * This class is used for preflight HTTP response values that do not need to be
537 * generated, but instead the value is "static" in that the same value will be returned
538 * for each call.
539 */
540 private static final class ConstantValueGenerator implements Callable<Object> {
541
542 private final Object value;
543
544 /**
545 * Sole constructor.
546 *
547 * @param value the value that will be returned when the call method is invoked.
548 */
549 private ConstantValueGenerator(final Object value) {
550 if (value == null) {
551 throw new IllegalArgumentException("value must not be null");
552 }
553 this.value = value;
554 }
555
556 @Override
557 public Object call() {
558 return value;
559 }
560 }
561
562 /**
563 * This callable is used for the DATE preflight HTTP response HTTP header.
564 * It's value must be generated when the response is generated, hence will be
565 * different for every call.
566 */
567 public static final class DateValueGenerator implements Callable<Date> {
568
569 @Override
570 public Date call() throws Exception {
571 return new Date();
572 }
573 }
574
575 }