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