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.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 }