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