View Javadoc
1   /*
2    * Copyright 2015 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.HttpHeaderNames;
19  import io.netty.handler.codec.http.HttpMethod;
20  
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.Date;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.LinkedHashSet;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.concurrent.Callable;
30  
31  /**
32   * Builder used to configure and build a {@link CorsConfig} instance.
33   */
34  public final class CorsConfigBuilder {
35  
36      /**
37       * Creates a Builder instance with it's origin set to '*'.
38       *
39       * @return Builder to support method chaining.
40       */
41      public static CorsConfigBuilder forAnyOrigin() {
42          return new CorsConfigBuilder();
43      }
44  
45      /**
46       * Creates a {@link CorsConfigBuilder} instance with the specified origin.
47       *
48       * @return {@link CorsConfigBuilder} to support method chaining.
49       */
50      public static CorsConfigBuilder forOrigin(final String origin) {
51          if ("*".equals(origin)) {
52              return new CorsConfigBuilder();
53          }
54          return new CorsConfigBuilder(origin);
55      }
56  
57      /**
58       * Creates a {@link CorsConfigBuilder} instance with the specified origins.
59       *
60       * @return {@link CorsConfigBuilder} to support method chaining.
61       */
62      public static CorsConfigBuilder forOrigins(final String... origins) {
63          return new CorsConfigBuilder(origins);
64      }
65  
66      final Set<String> origins;
67      final boolean anyOrigin;
68      boolean allowNullOrigin;
69      boolean enabled = true;
70      boolean allowCredentials;
71      final Set<String> exposeHeaders = new HashSet<String>();
72      long maxAge;
73      final Set<HttpMethod> requestMethods = new HashSet<HttpMethod>();
74      final Set<String> requestHeaders = new HashSet<String>();
75      final Map<CharSequence, Callable<?>> preflightHeaders = new HashMap<CharSequence, Callable<?>>();
76      private boolean noPreflightHeaders;
77      boolean shortCircuit;
78  
79      /**
80       * Creates a new Builder instance with the origin passed in.
81       *
82       * @param origins the origin to be used for this builder.
83       */
84      CorsConfigBuilder(final String... origins) {
85          this.origins = new LinkedHashSet<String>(Arrays.asList(origins));
86          anyOrigin = false;
87      }
88  
89      /**
90       * Creates a new Builder instance allowing any origin, "*" which is the
91       * wildcard origin.
92       *
93       */
94      CorsConfigBuilder() {
95          anyOrigin = true;
96          origins = Collections.emptySet();
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. Calling this method will enable a successful CORS response
102      * with a {@code "null"} value for the the CORS response header 'Access-Control-Allow-Origin'.
103      *
104      * @return {@link CorsConfigBuilder} to support method chaining.
105      */
106     public CorsConfigBuilder allowNullOrigin() {
107         allowNullOrigin = true;
108         return this;
109     }
110 
111     /**
112      * Disables CORS support.
113      *
114      * @return {@link CorsConfigBuilder} to support method chaining.
115      */
116     public CorsConfigBuilder disable() {
117         enabled = false;
118         return this;
119     }
120 
121     /**
122      * Specifies the headers to be exposed to calling clients.
123      *
124      * During a simple CORS request, only certain response headers are made available by the
125      * browser, for example using:
126      * <pre>
127      * xhr.getResponseHeader("Content-Type");
128      * </pre>
129      *
130      * The headers that are available by default are:
131      * <ul>
132      * <li>Cache-Control</li>
133      * <li>Content-Language</li>
134      * <li>Content-Type</li>
135      * <li>Expires</li>
136      * <li>Last-Modified</li>
137      * <li>Pragma</li>
138      * </ul>
139      *
140      * To expose other headers they need to be specified which is what this method enables by
141      * adding the headers to the CORS 'Access-Control-Expose-Headers' response header.
142      *
143      * @param headers the values to be added to the 'Access-Control-Expose-Headers' response header
144      * @return {@link CorsConfigBuilder} to support method chaining.
145      */
146     public CorsConfigBuilder exposeHeaders(final String... headers) {
147         exposeHeaders.addAll(Arrays.asList(headers));
148         return this;
149     }
150 
151     /**
152      * Specifies the headers to be exposed to calling clients.
153      *
154      * During a simple CORS request, only certain response headers are made available by the
155      * browser, for example using:
156      * <pre>
157      * xhr.getResponseHeader(HttpHeaderNames.CONTENT_TYPE);
158      * </pre>
159      *
160      * The headers that are available by default are:
161      * <ul>
162      * <li>Cache-Control</li>
163      * <li>Content-Language</li>
164      * <li>Content-Type</li>
165      * <li>Expires</li>
166      * <li>Last-Modified</li>
167      * <li>Pragma</li>
168      * </ul>
169      *
170      * To expose other headers they need to be specified which is what this method enables by
171      * adding the headers to the CORS 'Access-Control-Expose-Headers' response header.
172      *
173      * @param headers the values to be added to the 'Access-Control-Expose-Headers' response header
174      * @return {@link CorsConfigBuilder} to support method chaining.
175      */
176     public CorsConfigBuilder exposeHeaders(final CharSequence... headers) {
177         for (CharSequence header: headers) {
178             exposeHeaders.add(header.toString());
179         }
180         return this;
181     }
182 
183     /**
184      * By default cookies are not included in CORS requests, but this method will enable cookies to
185      * be added to CORS requests. Calling this method will set the CORS 'Access-Control-Allow-Credentials'
186      * response header to true.
187      *
188      * Please note, that cookie support needs to be enabled on the client side as well.
189      * The client needs to opt-in to send cookies by calling:
190      * <pre>
191      * xhr.withCredentials = true;
192      * </pre>
193      * The default value for 'withCredentials' is false in which case no cookies are sent.
194      * Setting this to true will included cookies in cross origin requests.
195      *
196      * @return {@link CorsConfigBuilder} to support method chaining.
197      */
198     public CorsConfigBuilder allowCredentials() {
199         allowCredentials = true;
200         return this;
201     }
202 
203     /**
204      * When making a preflight request the client has to perform two request with can be inefficient.
205      * This setting will set the CORS 'Access-Control-Max-Age' response header and enables the
206      * caching of the preflight response for the specified time. During this time no preflight
207      * request will be made.
208      *
209      * @param max the maximum time, in seconds, that the preflight response may be cached.
210      * @return {@link CorsConfigBuilder} to support method chaining.
211      */
212     public CorsConfigBuilder maxAge(final long max) {
213         maxAge = max;
214         return this;
215     }
216 
217     /**
218      * Specifies the allowed set of HTTP Request Methods that should be returned in the
219      * CORS 'Access-Control-Request-Method' response header.
220      *
221      * @param methods the {@link HttpMethod}s that should be allowed.
222      * @return {@link CorsConfigBuilder} to support method chaining.
223      */
224     public CorsConfigBuilder allowedRequestMethods(final HttpMethod... methods) {
225         requestMethods.addAll(Arrays.asList(methods));
226         return this;
227     }
228 
229     /**
230      * Specifies the if headers that should be returned in the CORS 'Access-Control-Allow-Headers'
231      * response header.
232      *
233      * If a client specifies headers on the request, for example by calling:
234      * <pre>
235      * xhr.setRequestHeader('My-Custom-Header', "SomeValue");
236      * </pre>
237      * the server will receive the above header name in the 'Access-Control-Request-Headers' of the
238      * preflight request. The server will then decide if it allows this header to be sent for the
239      * real request (remember that a preflight is not the real request but a request asking the server
240      * if it allow a request).
241      *
242      * @param headers the headers to be added to the preflight 'Access-Control-Allow-Headers' response header.
243      * @return {@link CorsConfigBuilder} to support method chaining.
244      */
245     public CorsConfigBuilder allowedRequestHeaders(final String... headers) {
246         requestHeaders.addAll(Arrays.asList(headers));
247         return this;
248     }
249 
250     /**
251      * Specifies the if headers that should be returned in the CORS 'Access-Control-Allow-Headers'
252      * response header.
253      *
254      * If a client specifies headers on the request, for example by calling:
255      * <pre>
256      * xhr.setRequestHeader('My-Custom-Header', "SomeValue");
257      * </pre>
258      * the server will receive the above header name in the 'Access-Control-Request-Headers' of the
259      * preflight request. The server will then decide if it allows this header to be sent for the
260      * real request (remember that a preflight is not the real request but a request asking the server
261      * if it allow a request).
262      *
263      * @param headers the headers to be added to the preflight 'Access-Control-Allow-Headers' response header.
264      * @return {@link CorsConfigBuilder} to support method chaining.
265      */
266     public CorsConfigBuilder allowedRequestHeaders(final CharSequence... headers) {
267         for (CharSequence header: headers) {
268             requestHeaders.add(header.toString());
269         }
270         return this;
271     }
272 
273     /**
274      * Returns HTTP response headers that should be added to a CORS preflight response.
275      *
276      * An intermediary like a load balancer might require that a CORS preflight request
277      * have certain headers set. This enables such headers to be added.
278      *
279      * @param name the name of the HTTP header.
280      * @param values the values for the HTTP header.
281      * @return {@link CorsConfigBuilder} to support method chaining.
282      */
283     public CorsConfigBuilder preflightResponseHeader(final CharSequence name, final Object... values) {
284         if (values.length == 1) {
285             preflightHeaders.put(name, new ConstantValueGenerator(values[0]));
286         } else {
287             preflightResponseHeader(name, Arrays.asList(values));
288         }
289         return this;
290     }
291 
292     /**
293      * Returns HTTP response headers that should be added to a CORS preflight response.
294      *
295      * An intermediary like a load balancer might require that a CORS preflight request
296      * have certain headers set. This enables such headers to be added.
297      *
298      * @param name the name of the HTTP header.
299      * @param value the values for the HTTP header.
300      * @param <T> the type of values that the Iterable contains.
301      * @return {@link CorsConfigBuilder} to support method chaining.
302      */
303     public <T> CorsConfigBuilder preflightResponseHeader(final CharSequence name, final Iterable<T> value) {
304         preflightHeaders.put(name, new ConstantValueGenerator(value));
305         return this;
306     }
307 
308     /**
309      * Returns HTTP response headers that should be added to a CORS preflight response.
310      *
311      * An intermediary like a load balancer might require that a CORS preflight request
312      * have certain headers set. This enables such headers to be added.
313      *
314      * Some values must be dynamically created when the HTTP response is created, for
315      * example the 'Date' response header. This can be accomplished by using a Callable
316      * which will have its 'call' method invoked when the HTTP response is created.
317      *
318      * @param name the name of the HTTP header.
319      * @param valueGenerator a Callable which will be invoked at HTTP response creation.
320      * @param <T> the type of the value that the Callable can return.
321      * @return {@link CorsConfigBuilder} to support method chaining.
322      */
323     public <T> CorsConfigBuilder preflightResponseHeader(final CharSequence name, final Callable<T> valueGenerator) {
324         preflightHeaders.put(name, valueGenerator);
325         return this;
326     }
327 
328     /**
329      * Specifies that no preflight response headers should be added to a preflight response.
330      *
331      * @return {@link CorsConfigBuilder} to support method chaining.
332      */
333     public CorsConfigBuilder noPreflightResponseHeaders() {
334         noPreflightHeaders = true;
335         return this;
336     }
337 
338     /**
339      * Specifies that a CORS request should be rejected if it's invalid before being
340      * further processing.
341      *
342      * CORS headers are set after a request is processed. This may not always be desired
343      * and this setting will check that the Origin is valid and if it is not valid no
344      * further processing will take place, and a error will be returned to the calling client.
345      *
346      * @return {@link CorsConfigBuilder} to support method chaining.
347      */
348     public CorsConfigBuilder shortCircuit() {
349         shortCircuit = true;
350         return this;
351     }
352 
353     /**
354      * Builds a {@link CorsConfig} with settings specified by previous method calls.
355      *
356      * @return {@link CorsConfig} the configured CorsConfig instance.
357      */
358     public CorsConfig build() {
359         if (preflightHeaders.isEmpty() && !noPreflightHeaders) {
360             preflightHeaders.put(HttpHeaderNames.DATE, DateValueGenerator.INSTANCE);
361             preflightHeaders.put(HttpHeaderNames.CONTENT_LENGTH, new ConstantValueGenerator("0"));
362         }
363         return new CorsConfig(this);
364     }
365 
366     /**
367      * This class is used for preflight HTTP response values that do not need to be
368      * generated, but instead the value is "static" in that the same value will be returned
369      * for each call.
370      */
371     private static final class ConstantValueGenerator implements Callable<Object> {
372 
373         private final Object value;
374 
375         /**
376          * Sole constructor.
377          *
378          * @param value the value that will be returned when the call method is invoked.
379          */
380         private ConstantValueGenerator(final Object value) {
381             if (value == null) {
382                 throw new IllegalArgumentException("value must not be null");
383             }
384             this.value = value;
385         }
386 
387         @Override
388         public Object call() {
389             return value;
390         }
391     }
392 
393     /**
394      * This callable is used for the DATE preflight HTTP response HTTP header.
395      * It's value must be generated when the response is generated, hence will be
396      * different for every call.
397      */
398     private static final class DateValueGenerator implements Callable<Date> {
399 
400         static final DateValueGenerator INSTANCE = new DateValueGenerator();
401 
402         @Override
403         public Date call() throws Exception {
404             return new Date();
405         }
406     }
407 }