001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.cors;
018
019import java.util.Collections;
020import java.util.LinkedHashMap;
021import java.util.Map;
022
023import javax.servlet.http.HttpServletRequest;
024
025import org.springframework.lang.Nullable;
026import org.springframework.util.AntPathMatcher;
027import org.springframework.util.Assert;
028import org.springframework.util.PathMatcher;
029import org.springframework.web.util.UrlPathHelper;
030
031/**
032 * Provide a per request {@link CorsConfiguration} instance based on a
033 * collection of {@link CorsConfiguration} mapped on path patterns.
034 *
035 * <p>Exact path mapping URIs (such as {@code "/admin"}) are supported
036 * as well as Ant-style path patterns (such as {@code "/admin/**"}).
037 *
038 * @author Sebastien Deleuze
039 * @since 4.2
040 */
041public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
042
043        private final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
044
045        private PathMatcher pathMatcher = new AntPathMatcher();
046
047        private UrlPathHelper urlPathHelper = new UrlPathHelper();
048
049        @Nullable
050        private String lookupPathAttributeName;
051
052
053        /**
054         * Set the PathMatcher implementation to use for matching URL paths
055         * against registered URL patterns. Default is AntPathMatcher.
056         * @see org.springframework.util.AntPathMatcher
057         */
058        public void setPathMatcher(PathMatcher pathMatcher) {
059                Assert.notNull(pathMatcher, "PathMatcher must not be null");
060                this.pathMatcher = pathMatcher;
061        }
062
063        /**
064         * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
065         * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
066         */
067        public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
068                this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
069        }
070
071        /**
072         * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
073         * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
074         */
075        public void setUrlDecode(boolean urlDecode) {
076                this.urlPathHelper.setUrlDecode(urlDecode);
077        }
078
079        /**
080         * Optionally configure the name of the attribute that caches the lookupPath.
081         * This is used to make the call to
082         * {@link UrlPathHelper#getLookupPathForRequest(HttpServletRequest, String)}
083         * @param lookupPathAttributeName the request attribute to check
084         * @since 5.2
085         */
086        public void setLookupPathAttributeName(@Nullable String lookupPathAttributeName) {
087                this.lookupPathAttributeName = lookupPathAttributeName;
088        }
089
090        /**
091         * Shortcut to same property on underlying {@link #setUrlPathHelper UrlPathHelper}.
092         * @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
093         */
094        public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
095                this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
096        }
097
098        /**
099         * Set the UrlPathHelper to use for resolution of lookup paths.
100         * <p>Use this to override the default UrlPathHelper with a custom subclass.
101         */
102        public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
103                Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
104                this.urlPathHelper = urlPathHelper;
105        }
106
107        /**
108         * Set CORS configuration based on URL patterns.
109         */
110        public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) {
111                this.corsConfigurations.clear();
112                if (corsConfigurations != null) {
113                        this.corsConfigurations.putAll(corsConfigurations);
114                }
115        }
116
117        /**
118         * Get the CORS configuration.
119         */
120        public Map<String, CorsConfiguration> getCorsConfigurations() {
121                return Collections.unmodifiableMap(this.corsConfigurations);
122        }
123
124        /**
125         * Register a {@link CorsConfiguration} for the specified path pattern.
126         */
127        public void registerCorsConfiguration(String path, CorsConfiguration config) {
128                this.corsConfigurations.put(path, config);
129        }
130
131
132        @Override
133        @Nullable
134        public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
135                String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, this.lookupPathAttributeName);
136                for (Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
137                        if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
138                                return entry.getValue();
139                        }
140                }
141                return null;
142        }
143
144}