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