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.HttpHeaders; 20 import io.netty.handler.codec.http.HttpHeaders.Names; 21 import io.netty.handler.codec.http.HttpMethod; 22 import io.netty.util.internal.StringUtil; 23 24 import java.util.Arrays; 25 import java.util.Collections; 26 import java.util.Date; 27 import java.util.HashMap; 28 import java.util.HashSet; 29 import java.util.LinkedHashSet; 30 import java.util.Map; 31 import java.util.Map.Entry; 32 import java.util.Set; 33 import java.util.concurrent.Callable; 34 35 /** 36 * Configuration for Cross-Origin Resource Sharing (CORS). 37 */ 38 public final class CorsConfig { 39 40 private final Set<String> origins; 41 private final boolean anyOrigin; 42 private final boolean enabled; 43 private final Set<String> exposeHeaders; 44 private final boolean allowCredentials; 45 private final long maxAge; 46 private final Set<HttpMethod> allowedRequestMethods; 47 private final Set<String> allowedRequestHeaders; 48 private final boolean allowNullOrigin; 49 private final Map<CharSequence, Callable<?>> preflightHeaders; 50 private final boolean shortCurcuit; 51 52 private CorsConfig(final Builder builder) { 53 origins = new LinkedHashSet<String>(builder.origins); 54 anyOrigin = builder.anyOrigin; 55 enabled = builder.enabled; 56 exposeHeaders = builder.exposeHeaders; 57 allowCredentials = builder.allowCredentials; 58 maxAge = builder.maxAge; 59 allowedRequestMethods = builder.requestMethods; 60 allowedRequestHeaders = builder.requestHeaders; 61 allowNullOrigin = builder.allowNullOrigin; 62 preflightHeaders = builder.preflightHeaders; 63 shortCurcuit = builder.shortCurcuit; 64 } 65 66 /** 67 * Determines if support for CORS is enabled. 68 * 69 * @return {@code true} if support for CORS is enabled, false otherwise. 70 */ 71 public boolean isCorsSupportEnabled() { 72 return enabled; 73 } 74 75 /** 76 * Determines whether a wildcard origin, '*', is supported. 77 * 78 * @return {@code boolean} true if any origin is allowed. 79 */ 80 public boolean isAnyOriginSupported() { 81 return anyOrigin; 82 } 83 84 /** 85 * Returns the allowed origin. This can either be a wildcard or an origin value. 86 * 87 * @return the value that will be used for the CORS response header 'Access-Control-Allow-Origin' 88 */ 89 public String origin() { 90 return origins.isEmpty() ? "*" : origins.iterator().next(); 91 } 92 93 /** 94 * Returns the set of allowed origins. 95 * 96 * @return {@code Set} the allowed origins. 97 */ 98 public Set<String> origins() { 99 return origins; 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. 105 * 106 * If isNullOriginAllowed is true then the server will response with the wildcard for the 107 * the CORS response header 'Access-Control-Allow-Origin'. 108 * 109 * @return {@code true} if a 'null' origin should be supported. 110 */ 111 public boolean isNullOriginAllowed() { 112 return allowNullOrigin; 113 } 114 115 /** 116 * Returns a set of headers to be exposed to calling clients. 117 * 118 * During a simple CORS request only certain response headers are made available by the 119 * browser, for example using: 120 * <pre> 121 * xhr.getResponseHeader("Content-Type"); 122 * </pre> 123 * The headers that are available by default are: 124 * <ul> 125 * <li>Cache-Control</li> 126 * <li>Content-Language</li> 127 * <li>Content-Type</li> 128 * <li>Expires</li> 129 * <li>Last-Modified</li> 130 * <li>Pragma</li> 131 * </ul> 132 * To expose other headers they need to be specified, which is what this method enables by 133 * adding the headers names to the CORS 'Access-Control-Expose-Headers' response header. 134 * 135 * @return {@code List<String>} a list of the headers to expose. 136 */ 137 public Set<String> exposedHeaders() { 138 return Collections.unmodifiableSet(exposeHeaders); 139 } 140 141 /** 142 * Determines if cookies are supported for CORS requests. 143 * 144 * By default cookies are not included in CORS requests but if isCredentialsAllowed returns 145 * true cookies will be added to CORS requests. Setting this value to true will set the 146 * CORS 'Access-Control-Allow-Credentials' response header to true. 147 * 148 * Please note that cookie support needs to be enabled on the client side as well. 149 * The client needs to opt-in to send cookies by calling: 150 * <pre> 151 * xhr.withCredentials = true; 152 * </pre> 153 * The default value for 'withCredentials' is false in which case no cookies are sent. 154 * Setting this to true will included cookies in cross origin requests. 155 * 156 * @return {@code true} if cookies are supported. 157 */ 158 public boolean isCredentialsAllowed() { 159 return allowCredentials; 160 } 161 162 /** 163 * Gets the maxAge setting. 164 * 165 * When making a preflight request the client has to perform two request with can be inefficient. 166 * This setting will set the CORS 'Access-Control-Max-Age' response header and enables the 167 * caching of the preflight response for the specified time. During this time no preflight 168 * request will be made. 169 * 170 * @return {@code long} the time in seconds that a preflight request may be cached. 171 */ 172 public long maxAge() { 173 return maxAge; 174 } 175 176 /** 177 * Returns the allowed set of Request Methods. The Http methods that should be returned in the 178 * CORS 'Access-Control-Request-Method' response header. 179 * 180 * @return {@code Set} of {@link HttpMethod}s that represent the allowed Request Methods. 181 */ 182 public Set<HttpMethod> allowedRequestMethods() { 183 return Collections.unmodifiableSet(allowedRequestMethods); 184 } 185 186 /** 187 * Returns the allowed set of Request Headers. 188 * 189 * The header names returned from this method will be used to set the CORS 190 * 'Access-Control-Allow-Headers' response header. 191 * 192 * @return {@code Set<String>} of strings that represent the allowed Request Headers. 193 */ 194 public Set<String> allowedRequestHeaders() { 195 return Collections.unmodifiableSet(allowedRequestHeaders); 196 } 197 198 /** 199 * Returns HTTP response headers that should be added to a CORS preflight response. 200 * 201 * @return {@link HttpHeaders} the HTTP response headers to be added. 202 */ 203 public HttpHeaders preflightResponseHeaders() { 204 if (preflightHeaders.isEmpty()) { 205 return HttpHeaders.EMPTY_HEADERS; 206 } 207 final HttpHeaders preflightHeaders = new DefaultHttpHeaders(); 208 for (Entry<CharSequence, Callable<?>> entry : this.preflightHeaders.entrySet()) { 209 final Object value = getValue(entry.getValue()); 210 if (value instanceof Iterable) { 211 preflightHeaders.add(entry.getKey(), (Iterable<?>) value); 212 } else { 213 preflightHeaders.add(entry.getKey(), value); 214 } 215 } 216 return preflightHeaders; 217 } 218 219 /** 220 * Determines whether a CORS request should be rejected if it's invalid before being 221 * further processing. 222 * 223 * CORS headers are set after a request is processed. This may not always be desired 224 * and this setting will check that the Origin is valid and if it is not valid no 225 * further processing will take place, and a error will be returned to the calling client. 226 * 227 * @return {@code true} if a CORS request should short-circuit upon receiving an invalid Origin header. 228 */ 229 public boolean isShortCurcuit() { 230 return shortCurcuit; 231 } 232 233 private static <T> T getValue(final Callable<T> callable) { 234 try { 235 return callable.call(); 236 } catch (final Exception e) { 237 throw new IllegalStateException("Could not generate value for callable [" + callable + ']', e); 238 } 239 } 240 241 @Override 242 public String toString() { 243 return StringUtil.simpleClassName(this) + "[enabled=" + enabled + 244 ", origins=" + origins + 245 ", anyOrigin=" + anyOrigin + 246 ", exposedHeaders=" + exposeHeaders + 247 ", isCredentialsAllowed=" + allowCredentials + 248 ", maxAge=" + maxAge + 249 ", allowedRequestMethods=" + allowedRequestMethods + 250 ", allowedRequestHeaders=" + allowedRequestHeaders + 251 ", preflightHeaders=" + preflightHeaders + ']'; 252 } 253 254 /** 255 * Creates a Builder instance with it's origin set to '*'. 256 * 257 * @return Builder to support method chaining. 258 */ 259 public static Builder withAnyOrigin() { 260 return new Builder(); 261 } 262 263 /** 264 * Creates a {@link Builder} instance with the specified origin. 265 * 266 * @return {@link Builder} to support method chaining. 267 */ 268 public static Builder withOrigin(final String origin) { 269 if ("*".equals(origin)) { 270 return new Builder(); 271 } 272 return new Builder(origin); 273 } 274 275 /** 276 * Creates a {@link Builder} instance with the specified origins. 277 * 278 * @return {@link Builder} to support method chaining. 279 */ 280 public static Builder withOrigins(final String... origins) { 281 return new Builder(origins); 282 } 283 284 /** 285 * Builder used to configure and build a CorsConfig instance. 286 */ 287 public static class Builder { 288 289 private final Set<String> origins; 290 private final boolean anyOrigin; 291 private boolean allowNullOrigin; 292 private boolean enabled = true; 293 private boolean allowCredentials; 294 private final Set<String> exposeHeaders = new HashSet<String>(); 295 private long maxAge; 296 private final Set<HttpMethod> requestMethods = new HashSet<HttpMethod>(); 297 private final Set<String> requestHeaders = new HashSet<String>(); 298 private final Map<CharSequence, Callable<?>> preflightHeaders = new HashMap<CharSequence, Callable<?>>(); 299 private boolean noPreflightHeaders; 300 private boolean shortCurcuit; 301 302 /** 303 * Creates a new Builder instance with the origin passed in. 304 * 305 * @param origins the origin to be used for this builder. 306 */ 307 public Builder(final String... origins) { 308 this.origins = new LinkedHashSet<String>(Arrays.asList(origins)); 309 anyOrigin = false; 310 } 311 312 /** 313 * Creates a new Builder instance allowing any origin, "*" which is the 314 * wildcard origin. 315 * 316 */ 317 public Builder() { 318 anyOrigin = true; 319 origins = Collections.emptySet(); 320 } 321 322 /** 323 * Web browsers may set the 'Origin' request header to 'null' if a resource is loaded 324 * from the local file system. Calling this method will enable a successful CORS response 325 * with a wildcard for the the CORS response header 'Access-Control-Allow-Origin'. 326 * 327 * @return {@link Builder} to support method chaining. 328 */ 329 public Builder allowNullOrigin() { 330 allowNullOrigin = true; 331 return this; 332 } 333 334 /** 335 * Disables CORS support. 336 * 337 * @return {@link Builder} to support method chaining. 338 */ 339 public Builder disable() { 340 enabled = false; 341 return this; 342 } 343 344 /** 345 * Specifies the headers to be exposed to calling clients. 346 * 347 * During a simple CORS request, only certain response headers are made available by the 348 * browser, for example using: 349 * <pre> 350 * xhr.getResponseHeader("Content-Type"); 351 * </pre> 352 * 353 * The headers that are available by default are: 354 * <ul> 355 * <li>Cache-Control</li> 356 * <li>Content-Language</li> 357 * <li>Content-Type</li> 358 * <li>Expires</li> 359 * <li>Last-Modified</li> 360 * <li>Pragma</li> 361 * </ul> 362 * 363 * To expose other headers they need to be specified which is what this method enables by 364 * adding the headers to the CORS 'Access-Control-Expose-Headers' response header. 365 * 366 * @param headers the values to be added to the 'Access-Control-Expose-Headers' response header 367 * @return {@link Builder} to support method chaining. 368 */ 369 public Builder exposeHeaders(final String... headers) { 370 exposeHeaders.addAll(Arrays.asList(headers)); 371 return this; 372 } 373 374 /** 375 * By default cookies are not included in CORS requests, but this method will enable cookies to 376 * be added to CORS requests. Calling this method will set the CORS 'Access-Control-Allow-Credentials' 377 * response header to true. 378 * 379 * Please note, that cookie support needs to be enabled on the client side as well. 380 * The client needs to opt-in to send cookies by calling: 381 * <pre> 382 * xhr.withCredentials = true; 383 * </pre> 384 * The default value for 'withCredentials' is false in which case no cookies are sent. 385 * Settning this to true will included cookies in cross origin requests. 386 * 387 * @return {@link Builder} to support method chaining. 388 */ 389 public Builder allowCredentials() { 390 allowCredentials = true; 391 return this; 392 } 393 394 /** 395 * When making a preflight request the client has to perform two request with can be inefficient. 396 * This setting will set the CORS 'Access-Control-Max-Age' response header and enables the 397 * caching of the preflight response for the specified time. During this time no preflight 398 * request will be made. 399 * 400 * @param max the maximum time, in seconds, that the preflight response may be cached. 401 * @return {@link Builder} to support method chaining. 402 */ 403 public Builder maxAge(final long max) { 404 maxAge = max; 405 return this; 406 } 407 408 /** 409 * Specifies the allowed set of HTTP Request Methods that should be returned in the 410 * CORS 'Access-Control-Request-Method' response header. 411 * 412 * @param methods the {@link HttpMethod}s that should be allowed. 413 * @return {@link Builder} to support method chaining. 414 */ 415 public Builder allowedRequestMethods(final HttpMethod... methods) { 416 requestMethods.addAll(Arrays.asList(methods)); 417 return this; 418 } 419 420 /** 421 * Specifies the if headers that should be returned in the CORS 'Access-Control-Allow-Headers' 422 * response header. 423 * 424 * If a client specifies headers on the request, for example by calling: 425 * <pre> 426 * xhr.setRequestHeader('My-Custom-Header', "SomeValue"); 427 * </pre> 428 * the server will recieve the above header name in the 'Access-Control-Request-Headers' of the 429 * preflight request. The server will then decide if it allows this header to be sent for the 430 * real request (remember that a preflight is not the real request but a request asking the server 431 * if it allow a request). 432 * 433 * @param headers the headers to be added to the preflight 'Access-Control-Allow-Headers' response header. 434 * @return {@link Builder} to support method chaining. 435 */ 436 public Builder allowedRequestHeaders(final String... headers) { 437 requestHeaders.addAll(Arrays.asList(headers)); 438 return this; 439 } 440 441 /** 442 * Returns HTTP response headers that should be added to a CORS preflight response. 443 * 444 * An intermediary like a load balancer might require that a CORS preflight request 445 * have certain headers set. This enables such headers to be added. 446 * 447 * @param name the name of the HTTP header. 448 * @param values the values for the HTTP header. 449 * @return {@link Builder} to support method chaining. 450 */ 451 public Builder preflightResponseHeader(final CharSequence name, final Object... values) { 452 if (values.length == 1) { 453 preflightHeaders.put(name, new ConstantValueGenerator(values[0])); 454 } else { 455 preflightResponseHeader(name, Arrays.asList(values)); 456 } 457 return this; 458 } 459 460 /** 461 * Returns HTTP response headers that should be added to a CORS preflight response. 462 * 463 * An intermediary like a load balancer might require that a CORS preflight request 464 * have certain headers set. This enables such headers to be added. 465 * 466 * @param name the name of the HTTP header. 467 * @param value the values for the HTTP header. 468 * @param <T> the type of values that the Iterable contains. 469 * @return {@link Builder} to support method chaining. 470 */ 471 public <T> Builder preflightResponseHeader(final CharSequence name, final Iterable<T> value) { 472 preflightHeaders.put(name, new ConstantValueGenerator(value)); 473 return this; 474 } 475 476 /** 477 * Returns HTTP response headers that should be added to a CORS preflight response. 478 * 479 * An intermediary like a load balancer might require that a CORS preflight request 480 * have certain headers set. This enables such headers to be added. 481 * 482 * Some values must be dynamically created when the HTTP response is created, for 483 * example the 'Date' response header. This can be occomplished by using a Callable 484 * which will have its 'call' method invoked when the HTTP response is created. 485 * 486 * @param name the name of the HTTP header. 487 * @param valueGenerator a Callable which will be invoked at HTTP response creation. 488 * @param <T> the type of the value that the Callable can return. 489 * @return {@link Builder} to support method chaining. 490 */ 491 public <T> Builder preflightResponseHeader(final String name, final Callable<T> valueGenerator) { 492 preflightHeaders.put(name, valueGenerator); 493 return this; 494 } 495 496 /** 497 * Specifies that no preflight response headers should be added to a preflight response. 498 * 499 * @return {@link Builder} to support method chaining. 500 */ 501 public Builder noPreflightResponseHeaders() { 502 noPreflightHeaders = true; 503 return this; 504 } 505 506 /** 507 * Builds a {@link CorsConfig} with settings specified by previous method calls. 508 * 509 * @return {@link CorsConfig} the configured CorsConfig instance. 510 */ 511 public CorsConfig build() { 512 if (preflightHeaders.isEmpty() && !noPreflightHeaders) { 513 preflightHeaders.put(Names.DATE, new DateValueGenerator()); 514 preflightHeaders.put(Names.CONTENT_LENGTH, new ConstantValueGenerator("0")); 515 } 516 return new CorsConfig(this); 517 } 518 519 /** 520 * Specifies that a CORS request should be rejected if it's invalid before being 521 * further processing. 522 * 523 * CORS headers are set after a request is processed. This may not always be desired 524 * and this setting will check that the Origin is valid and if it is not valid no 525 * further processing will take place, and a error will be returned to the calling client. 526 * 527 * @return {@link Builder} to support method chaining. 528 */ 529 public Builder shortCurcuit() { 530 shortCurcuit = true; 531 return this; 532 } 533 } 534 535 /** 536 * This class is used for preflight HTTP response values that do not need to be 537 * generated, but instead the value is "static" in that the same value will be returned 538 * for each call. 539 */ 540 private static final class ConstantValueGenerator implements Callable<Object> { 541 542 private final Object value; 543 544 /** 545 * Sole constructor. 546 * 547 * @param value the value that will be returned when the call method is invoked. 548 */ 549 private ConstantValueGenerator(final Object value) { 550 if (value == null) { 551 throw new IllegalArgumentException("value must not be null"); 552 } 553 this.value = value; 554 } 555 556 @Override 557 public Object call() { 558 return value; 559 } 560 } 561 562 /** 563 * This callable is used for the DATE preflight HTTP response HTTP header. 564 * It's value must be generated when the response is generated, hence will be 565 * different for every call. 566 */ 567 public static final class DateValueGenerator implements Callable<Date> { 568 569 @Override 570 public Date call() throws Exception { 571 return new Date(); 572 } 573 } 574 575 }