/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.cors;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.server.PathContainer;
import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.ServletRequestPathUtils;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

CorsConfigurationSource that uses URL path patterns to select the CorsConfiguration for a request.

Pattern matching can be done with a PathMatcher or with pre-parsed PathPatterns. The syntax is largely the same with the latter being more tailored for web usage and more efficient. The choice depends on the presence of a resolved String lookupPath or a parsed RequestPath with a fallback on PathMatcher but the fallback can be disabled. For more details, please see setAllowInitLookupPath(boolean).

Author:Sebastien Deleuze, Rossen Stoyanchev
See Also:
Since:4.2
/** * {@code CorsConfigurationSource} that uses URL path patterns to select the * {@code CorsConfiguration} for a request. * * <p>Pattern matching can be done with a {@link PathMatcher} or with pre-parsed * {@link PathPattern}s. The syntax is largely the same with the latter being more * tailored for web usage and more efficient. The choice depends on the presence of a * {@link UrlPathHelper#resolveAndCacheLookupPath resolved} String lookupPath or a * {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath} * with a fallback on {@link PathMatcher} but the fallback can be disabled. * For more details, please see {@link #setAllowInitLookupPath(boolean)}. * * @author Sebastien Deleuze * @author Rossen Stoyanchev * @since 4.2 * @see PathPattern * @see AntPathMatcher */
public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource { private static PathMatcher defaultPathMatcher = new AntPathMatcher(); private final PathPatternParser patternParser; private UrlPathHelper urlPathHelper = UrlPathHelper.defaultInstance; private PathMatcher pathMatcher = defaultPathMatcher; @Nullable private String lookupPathAttributeName; private boolean allowInitLookupPath = true; private final Map<PathPattern, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
Default constructor with PathPatternParser.defaultInstance.
/** * Default constructor with {@link PathPatternParser#defaultInstance}. */
public UrlBasedCorsConfigurationSource() { this(PathPatternParser.defaultInstance); }
Constructor with a PathPatternParser to parse patterns with.
Params:
  • parser – the parser to use
Since:5.3
/** * Constructor with a {@link PathPatternParser} to parse patterns with. * @param parser the parser to use * @since 5.3 */
public UrlBasedCorsConfigurationSource(PathPatternParser parser) { Assert.notNull(parser, "PathPatternParser must not be null"); this.patternParser = parser; }
Shortcut to the same property on the configured UrlPathHelper.
Deprecated:as of 5.3 in favor of using setUrlPathHelper(UrlPathHelper), if at all. For further details, please see setAllowInitLookupPath(boolean).
/** * Shortcut to the * {@link org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath * same property} on the configured {@code UrlPathHelper}. * @deprecated as of 5.3 in favor of using * {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details, * please see {@link #setAllowInitLookupPath(boolean)}. */
@Deprecated public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { initUrlPathHelper(); this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); }
Shortcut to the same property on the configured UrlPathHelper.
Deprecated:as of 5.3 in favor of using setUrlPathHelper(UrlPathHelper), if at all. For further details, please see setAllowInitLookupPath(boolean).
/** * Shortcut to the * {@link org.springframework.web.util.UrlPathHelper#setUrlDecode same property} * on the configured {@code UrlPathHelper}. * @deprecated as of 5.3 in favor of using * {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details, * please see {@link #setAllowInitLookupPath(boolean)}. */
@Deprecated public void setUrlDecode(boolean urlDecode) { initUrlPathHelper(); this.urlPathHelper.setUrlDecode(urlDecode); }
Shortcut to the same property on the configured UrlPathHelper.
Deprecated:as of 5.3 in favor of using setUrlPathHelper(UrlPathHelper), if at all. For further details, please see setAllowInitLookupPath(boolean).
/** * Shortcut to the * {@link org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent * same property} on the configured {@code UrlPathHelper}. * @deprecated as of 5.3 in favor of using * {@link #setUrlPathHelper(UrlPathHelper)}, if at all. For further details, * please see {@link #setAllowInitLookupPath(boolean)}. */
@Deprecated public void setRemoveSemicolonContent(boolean removeSemicolonContent) { initUrlPathHelper(); this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent); } private void initUrlPathHelper() { if (this.urlPathHelper == UrlPathHelper.defaultInstance) { this.urlPathHelper = new UrlPathHelper(); } }
Configure the UrlPathHelper to resolve the lookupPath. This may not be necessary if the lookupPath is expected to be pre-resolved or if parsed PathPatterns are used instead. For further details on that, see setAllowInitLookupPath(boolean).

By default this is UrlPathHelper.defaultInstance.

/** * Configure the {@code UrlPathHelper} to resolve the lookupPath. This may * not be necessary if the lookupPath is expected to be pre-resolved or if * parsed {@code PathPatterns} are used instead. * For further details on that, see {@link #setAllowInitLookupPath(boolean)}. * <p>By default this is {@link UrlPathHelper#defaultInstance}. */
public void setUrlPathHelper(UrlPathHelper urlPathHelper) { Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); this.urlPathHelper = urlPathHelper; }
When enabled, if there is neither a esolved String lookupPath nor a parsed RequestPath then use the configured UrlPathHelper to resolve a String lookupPath. This in turn determines use of URL pattern matching with PathMatcher or with parsed PathPatterns.

In Spring MVC, either a resolved String lookupPath or a parsed RequestPath is always available within DispatcherServlet processing. However in a Servlet Filter such as CorsFilter that may or may not be the case.

By default this is set to true in which case lazy lookupPath initialization is allowed. Set this to false when an application is using parsed PathPatterns in which case the RequestPath can be parsed earlier via ServletRequestPathFilter.

Params:
  • allowInitLookupPath – whether to disable lazy initialization and fail if not already resolved
Since:5.3
/** * When enabled, if there is neither a * {@link UrlPathHelper#resolveAndCacheLookupPath esolved} String lookupPath nor a * {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath} * then use the {@link #setUrlPathHelper configured} {@code UrlPathHelper} * to resolve a String lookupPath. This in turn determines use of URL * pattern matching with {@link PathMatcher} or with parsed {@link PathPattern}s. * <p>In Spring MVC, either a resolved String lookupPath or a parsed * {@code RequestPath} is always available within {@code DispatcherServlet} * processing. However in a Servlet {@code Filter} such as {@code CorsFilter} * that may or may not be the case. * <p>By default this is set to {@code true} in which case lazy lookupPath * initialization is allowed. Set this to {@code false} when an * application is using parsed {@code PathPatterns} in which case the * {@code RequestPath} can be parsed earlier via * {@link org.springframework.web.filter.ServletRequestPathFilter * ServletRequestPathFilter}. * @param allowInitLookupPath whether to disable lazy initialization * and fail if not already resolved * @since 5.3 */
public void setAllowInitLookupPath(boolean allowInitLookupPath) { this.allowInitLookupPath = allowInitLookupPath; }
Configure the name of the attribute that holds the lookupPath extracted via UrlPathHelper.getLookupPathForRequest(HttpServletRequest).

By default this is UrlPathHelper.PATH_ATTRIBUTE.

Params:
  • name – the request attribute to check
Since:5.2
Deprecated:as of 5.3 in favor of UrlPathHelper.PATH_ATTRIBUTE.
/** * Configure the name of the attribute that holds the lookupPath extracted * via {@link UrlPathHelper#getLookupPathForRequest(HttpServletRequest)}. * <p>By default this is {@link UrlPathHelper#PATH_ATTRIBUTE}. * @param name the request attribute to check * @since 5.2 * @deprecated as of 5.3 in favor of {@link UrlPathHelper#PATH_ATTRIBUTE}. */
@Deprecated public void setLookupPathAttributeName(String name) { this.lookupPathAttributeName = name; }
Configure a PathMatcher to use for pattern matching.

This is an advanced property that should be used only when a customized AntPathMatcher or a custom PathMatcher is required.

By default this is AntPathMatcher.

Note: Setting PathMatcher enforces use of String pattern matching even when a parsed RequestPath is available.

/** * Configure a {@code PathMatcher} to use for pattern matching. * <p>This is an advanced property that should be used only when a * customized {@link AntPathMatcher} or a custom PathMatcher is required. * <p>By default this is {@link AntPathMatcher}. * <p><strong>Note:</strong> Setting {@code PathMatcher} enforces use of * String pattern matching even when a * {@link ServletRequestPathUtils#parseAndCache parsed} {@code RequestPath} * is available. */
public void setPathMatcher(PathMatcher pathMatcher) { this.pathMatcher = pathMatcher; }
Set the CORS configuration mappings.

For pattern syntax see AntPathMatcher and PathPattern as well as class-level Javadoc for details on which one may in use. Generally the syntax is largely the same with PathPattern more tailored for web usage.

Params:
  • corsConfigurations – the mappings to use
See Also:
/** * Set the CORS configuration mappings. * <p>For pattern syntax see {@link AntPathMatcher} and {@link PathPattern} * as well as class-level Javadoc for details on which one may in use. * Generally the syntax is largely the same with {@link PathPattern} more * tailored for web usage. * @param corsConfigurations the mappings to use * @see PathPattern * @see AntPathMatcher */
public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) { this.corsConfigurations.clear(); if (corsConfigurations != null) { corsConfigurations.forEach(this::registerCorsConfiguration); } }
Variant of setCorsConfigurations(Map<String,CorsConfiguration>) to register one mapping at a time.
Params:
  • pattern – the mapping pattern
  • config – the CORS configuration to use for the pattern
See Also:
/** * Variant of {@link #setCorsConfigurations(Map)} to register one mapping at a time. * @param pattern the mapping pattern * @param config the CORS configuration to use for the pattern * @see PathPattern * @see AntPathMatcher */
public void registerCorsConfiguration(String pattern, CorsConfiguration config) { this.corsConfigurations.put(this.patternParser.parse(pattern), config); }
Return all configured CORS mappings.
/** * Return all configured CORS mappings. */
public Map<String, CorsConfiguration> getCorsConfigurations() { Map<String, CorsConfiguration> result = CollectionUtils.newHashMap(this.corsConfigurations.size()); this.corsConfigurations.forEach((pattern, config) -> result.put(pattern.getPatternString(), config)); return Collections.unmodifiableMap(result); } @Override @Nullable public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { Object path = resolvePath(request); boolean isPathContainer = (path instanceof PathContainer); for (Map.Entry<PathPattern, CorsConfiguration> entry : this.corsConfigurations.entrySet()) { if (match(path, isPathContainer, entry.getKey())) { return entry.getValue(); } } return null; } @SuppressWarnings("deprecation") private Object resolvePath(HttpServletRequest request) { if (this.allowInitLookupPath && !ServletRequestPathUtils.hasCachedPath(request)) { return (this.lookupPathAttributeName != null ? this.urlPathHelper.getLookupPathForRequest(request, this.lookupPathAttributeName) : this.urlPathHelper.getLookupPathForRequest(request)); } Object lookupPath = ServletRequestPathUtils.getCachedPath(request); if (this.pathMatcher != defaultPathMatcher) { lookupPath = lookupPath.toString(); } return lookupPath; } private boolean match(Object path, boolean isPathContainer, PathPattern pattern) { return (isPathContainer ? pattern.matches((PathContainer) path) : this.pathMatcher.match(pattern.getPatternString(), (String) path)); } }