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 }