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 }