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.HttpMethod;
22  import io.netty.util.internal.StringUtil;
23  
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.LinkedHashSet;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.Set;
30  import java.util.concurrent.Callable;
31  
32  /**
33   * Configuration for Cross-Origin Resource Sharing (CORS).
34   */
35  public final class CorsConfig {
36  
37      private final Set<String> origins;
38      private final boolean anyOrigin;
39      private final boolean enabled;
40      private final Set<String> exposeHeaders;
41      private final boolean allowCredentials;
42      private final long maxAge;
43      private final Set<HttpMethod> allowedRequestMethods;
44      private final Set<String> allowedRequestHeaders;
45      private final boolean allowNullOrigin;
46      private final Map<CharSequence, Callable<?>> preflightHeaders;
47      private final boolean shortCircuit;
48  
49      CorsConfig(final CorsConfigBuilder builder) {
50          origins = new LinkedHashSet<String>(builder.origins);
51          anyOrigin = builder.anyOrigin;
52          enabled = builder.enabled;
53          exposeHeaders = builder.exposeHeaders;
54          allowCredentials = builder.allowCredentials;
55          maxAge = builder.maxAge;
56          allowedRequestMethods = builder.requestMethods;
57          allowedRequestHeaders = builder.requestHeaders;
58          allowNullOrigin = builder.allowNullOrigin;
59          preflightHeaders = builder.preflightHeaders;
60          shortCircuit = builder.shortCircuit;
61      }
62  
63      /**
64       * Determines if support for CORS is enabled.
65       *
66       * @return {@code true} if support for CORS is enabled, false otherwise.
67       */
68      public boolean isCorsSupportEnabled() {
69          return enabled;
70      }
71  
72      /**
73       * Determines whether a wildcard origin, '*', is supported.
74       *
75       * @return {@code boolean} true if any origin is allowed.
76       */
77      public boolean isAnyOriginSupported() {
78          return anyOrigin;
79      }
80  
81      /**
82       * Returns the allowed origin. This can either be a wildcard or an origin value.
83       *
84       * @return the value that will be used for the CORS response header 'Access-Control-Allow-Origin'
85       */
86      public String origin() {
87          return origins.isEmpty() ? "*" : origins.iterator().next();
88      }
89  
90      /**
91       * Returns the set of allowed origins.
92       *
93       * @return {@code Set} the allowed origins.
94       */
95      public Set<String> origins() {
96          return origins;
97      }
98  
99      /**
100      * Web browsers may set the 'Origin' request header to 'null' if a resource is loaded
101      * from the local file system.
102      *
103      * If isNullOriginAllowed is true then the server will response with the wildcard for the
104      * the CORS response header 'Access-Control-Allow-Origin'.
105      *
106      * @return {@code true} if a 'null' origin should be supported.
107      */
108     public boolean isNullOriginAllowed() {
109         return allowNullOrigin;
110     }
111 
112     /**
113      * Returns a set of headers to be exposed to calling clients.
114      *
115      * During a simple CORS request only certain response headers are made available by the
116      * browser, for example using:
117      * <pre>
118      * xhr.getResponseHeader("Content-Type");
119      * </pre>
120      * The headers that are available by default are:
121      * <ul>
122      * <li>Cache-Control</li>
123      * <li>Content-Language</li>
124      * <li>Content-Type</li>
125      * <li>Expires</li>
126      * <li>Last-Modified</li>
127      * <li>Pragma</li>
128      * </ul>
129      * To expose other headers they need to be specified, which is what this method enables by
130      * adding the headers names to the CORS 'Access-Control-Expose-Headers' response header.
131      *
132      * @return {@code List<String>} a list of the headers to expose.
133      */
134     public Set<String> exposedHeaders() {
135         return Collections.unmodifiableSet(exposeHeaders);
136     }
137 
138     /**
139      * Determines if cookies are supported for CORS requests.
140      *
141      * By default cookies are not included in CORS requests but if isCredentialsAllowed returns
142      * true cookies will be added to CORS requests. Setting this value to true will set the
143      * CORS 'Access-Control-Allow-Credentials' response header to true.
144      *
145      * Please note that cookie support needs to be enabled on the client side as well.
146      * The client needs to opt-in to send cookies by calling:
147      * <pre>
148      * xhr.withCredentials = true;
149      * </pre>
150      * The default value for 'withCredentials' is false in which case no cookies are sent.
151      * Setting this to true will included cookies in cross origin requests.
152      *
153      * @return {@code true} if cookies are supported.
154      */
155     public boolean isCredentialsAllowed() {
156         return allowCredentials;
157     }
158 
159     /**
160      * Gets the maxAge setting.
161      *
162      * When making a preflight request the client has to perform two request with can be inefficient.
163      * This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
164      * caching of the preflight response for the specified time. During this time no preflight
165      * request will be made.
166      *
167      * @return {@code long} the time in seconds that a preflight request may be cached.
168      */
169     public long maxAge() {
170         return maxAge;
171     }
172 
173     /**
174      * Returns the allowed set of Request Methods. The Http methods that should be returned in the
175      * CORS 'Access-Control-Request-Method' response header.
176      *
177      * @return {@code Set} of {@link HttpMethod}s that represent the allowed Request Methods.
178      */
179     public Set<HttpMethod> allowedRequestMethods() {
180         return Collections.unmodifiableSet(allowedRequestMethods);
181     }
182 
183     /**
184      * Returns the allowed set of Request Headers.
185      *
186      * The header names returned from this method will be used to set the CORS
187      * 'Access-Control-Allow-Headers' response header.
188      *
189      * @return {@code Set<String>} of strings that represent the allowed Request Headers.
190      */
191     public Set<String> allowedRequestHeaders() {
192         return Collections.unmodifiableSet(allowedRequestHeaders);
193     }
194 
195     /**
196      * Returns HTTP response headers that should be added to a CORS preflight response.
197      *
198      * @return {@link HttpHeaders} the HTTP response headers to be added.
199      */
200     public HttpHeaders preflightResponseHeaders() {
201         if (preflightHeaders.isEmpty()) {
202             return EmptyHttpHeaders.INSTANCE;
203         }
204         final HttpHeaders preflightHeaders = new DefaultHttpHeaders();
205         for (Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) {
206             final Object value = getValue(entry.getValue());
207             if (value instanceof Iterable) {
208                 preflightHeaders.add(entry.getKey(), (Iterable<?>) value);
209             } else {
210                 preflightHeaders.add(entry.getKey(), value);
211             }
212         }
213         return preflightHeaders;
214     }
215 
216     /**
217      * Determines whether a CORS request should be rejected if it's invalid before being
218      * further processing.
219      *
220      * CORS headers are set after a request is processed. This may not always be desired
221      * and this setting will check that the Origin is valid and if it is not valid no
222      * further processing will take place, and a error will be returned to the calling client.
223      *
224      * @return {@code true} if a CORS request should short-circuit upon receiving an invalid Origin header.
225      */
226     public boolean isShortCircuit() {
227         return shortCircuit;
228     }
229 
230     /**
231      * @deprecated Use {@link #isShortCircuit()} instead.
232      */
233     @Deprecated
234     public boolean isShortCurcuit() {
235         return isShortCircuit();
236     }
237 
238     private static <T> T getValue(final Callable<T> callable) {
239         try {
240             return callable.call();
241         } catch (final Exception e) {
242             throw new IllegalStateException("Could not generate value for callable [" + callable + ']', e);
243         }
244     }
245 
246     @Override
247     public String toString() {
248         return StringUtil.simpleClassName(this) + "[enabled=" + enabled +
249                 ", origins=" + origins +
250                 ", anyOrigin=" + anyOrigin +
251                 ", exposedHeaders=" + exposeHeaders +
252                 ", isCredentialsAllowed=" + allowCredentials +
253                 ", maxAge=" + maxAge +
254                 ", allowedRequestMethods=" + allowedRequestMethods +
255                 ", allowedRequestHeaders=" + allowedRequestHeaders +
256                 ", preflightHeaders=" + preflightHeaders + ']';
257     }
258 
259     /**
260      * @deprecated Use {@link CorsConfigBuilder#forAnyOrigin()} instead.
261      */
262     @Deprecated
263     public static Builder withAnyOrigin() {
264         return new Builder();
265     }
266 
267     /**
268      * @deprecated Use {@link CorsConfigBuilder#forOrigin(String)} instead.
269      */
270     @Deprecated
271     public static Builder withOrigin(final String origin) {
272         if ("*".equals(origin)) {
273             return new Builder();
274         }
275         return new Builder(origin);
276     }
277 
278     /**
279      * @deprecated Use {@link CorsConfigBuilder#forOrigins(String...)} instead.
280      */
281     @Deprecated
282     public static Builder withOrigins(final String... origins) {
283         return new Builder(origins);
284     }
285 
286     /**
287      * @deprecated Use {@link CorsConfigBuilder} instead.
288      */
289     @Deprecated
290     public static class Builder {
291 
292         private final CorsConfigBuilder builder;
293 
294         /**
295          * @deprecated Use {@link CorsConfigBuilder} instead.
296          */
297         @Deprecated
298         public Builder(final String... origins) {
299             builder = new CorsConfigBuilder(origins);
300         }
301 
302         /**
303          * @deprecated Use {@link CorsConfigBuilder} instead.
304          */
305         @Deprecated
306         public Builder() {
307             builder = new CorsConfigBuilder();
308         }
309 
310         /**
311          * @deprecated Use {@link CorsConfigBuilder#allowNullOrigin()} instead.
312          */
313         @Deprecated
314         public Builder allowNullOrigin() {
315             builder.allowNullOrigin();
316             return this;
317         }
318 
319         /**
320          * @deprecated Use {@link CorsConfigBuilder#disable()} instead.
321          */
322         @Deprecated
323         public Builder disable() {
324             builder.disable();
325             return this;
326         }
327 
328         /**
329          * @deprecated Use {@link CorsConfigBuilder#exposeHeaders(String...)} instead.
330          */
331         @Deprecated
332         public Builder exposeHeaders(final String... headers) {
333             builder.exposeHeaders(headers);
334             return this;
335         }
336 
337         /**
338          * @deprecated Use {@link CorsConfigBuilder#allowCredentials()} instead.
339          */
340         @Deprecated
341         public Builder allowCredentials() {
342             builder.allowCredentials();
343             return this;
344         }
345 
346         /**
347          * @deprecated Use {@link CorsConfigBuilder#maxAge(long)} instead.
348          */
349         @Deprecated
350         public Builder maxAge(final long max) {
351             builder.maxAge(max);
352             return this;
353         }
354 
355         /**
356          * @deprecated Use {@link CorsConfigBuilder#allowedRequestMethods(HttpMethod...)} instead.
357          */
358         @Deprecated
359         public Builder allowedRequestMethods(final HttpMethod... methods) {
360             builder.allowedRequestMethods(methods);
361             return this;
362         }
363 
364         /**
365          * @deprecated Use {@link CorsConfigBuilder#allowedRequestHeaders(String...)} instead.
366          */
367         @Deprecated
368         public Builder allowedRequestHeaders(final String... headers) {
369             builder.allowedRequestHeaders(headers);
370             return this;
371         }
372 
373         /**
374          * @deprecated Use {@link CorsConfigBuilder#preflightResponseHeader(CharSequence, Object...)} instead.
375          */
376         @Deprecated
377         public Builder preflightResponseHeader(final CharSequence name, final Object... values) {
378             builder.preflightResponseHeader(name, values);
379             return this;
380         }
381 
382         /**
383          * @deprecated Use {@link CorsConfigBuilder#preflightResponseHeader(CharSequence, Iterable)} instead.
384          */
385         @Deprecated
386         public <T> Builder preflightResponseHeader(final CharSequence name, final Iterable<T> value) {
387             builder.preflightResponseHeader(name, value);
388             return this;
389         }
390 
391         /**
392          * @deprecated Use {@link CorsConfigBuilder#preflightResponseHeader(CharSequence, Callable)} instead.
393          */
394         @Deprecated
395         public <T> Builder preflightResponseHeader(final String name, final Callable<T> valueGenerator) {
396             builder.preflightResponseHeader(name, valueGenerator);
397             return this;
398         }
399 
400         /**
401          * @deprecated Use {@link CorsConfigBuilder#noPreflightResponseHeaders()} instead.
402          */
403         @Deprecated
404         public Builder noPreflightResponseHeaders() {
405             builder.noPreflightResponseHeaders();
406             return this;
407         }
408 
409         /**
410          * @deprecated Use {@link CorsConfigBuilder#build()} instead.
411          */
412         @Deprecated
413         public CorsConfig build() {
414             return builder.build();
415         }
416 
417         /**
418          * @deprecated Use {@link CorsConfigBuilder#shortCircuit()} instead.
419          */
420         @Deprecated
421         public Builder shortCurcuit() {
422             builder.shortCircuit();
423             return this;
424         }
425     }
426 
427     /**
428      * @deprecated Removed without alternatives.
429      */
430     @Deprecated
431     public static final class DateValueGenerator implements Callable<Date> {
432 
433         @Override
434         public Date call() throws Exception {
435             return new Date();
436         }
437     }
438 }