001/*
002 * Copyright 2002-2020 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.reactive;
018
019import java.util.LinkedHashMap;
020import java.util.Map;
021
022import org.springframework.http.server.PathContainer;
023import org.springframework.lang.Nullable;
024import org.springframework.web.cors.CorsConfiguration;
025import org.springframework.web.server.ServerWebExchange;
026import org.springframework.web.util.pattern.PathPattern;
027import org.springframework.web.util.pattern.PathPatternParser;
028
029/**
030 * Provide a per reactive request {@link CorsConfiguration} instance based on a
031 * collection of {@link CorsConfiguration} mapped on path patterns.
032 *
033 * <p>Exact path mapping URIs (such as {@code "/admin"}) are supported
034 * as well as Ant-style path patterns (such as {@code "/admin/**"}).
035 *
036 * @author Sebastien Deleuze
037 * @author Brian Clozel
038 * @since 5.0
039 */
040public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource {
041
042        private final Map<PathPattern, CorsConfiguration> corsConfigurations;
043
044        private final PathPatternParser patternParser;
045
046
047        /**
048         * Construct a new {@code UrlBasedCorsConfigurationSource} instance with default
049         * {@code PathPatternParser}.
050         * @since 5.0.6
051         */
052        public UrlBasedCorsConfigurationSource() {
053                this(PathPatternParser.defaultInstance);
054        }
055
056        /**
057         * Construct a new {@code UrlBasedCorsConfigurationSource} instance from the supplied
058         * {@code PathPatternParser}.
059         */
060        public UrlBasedCorsConfigurationSource(PathPatternParser patternParser) {
061                this.corsConfigurations = new LinkedHashMap<>();
062                this.patternParser = patternParser;
063        }
064
065
066        /**
067         * Set CORS configuration based on URL patterns.
068         */
069        public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) {
070                this.corsConfigurations.clear();
071                if (corsConfigurations != null) {
072                        corsConfigurations.forEach(this::registerCorsConfiguration);
073                }
074        }
075
076        /**
077         * Register a {@link CorsConfiguration} for the specified path pattern.
078         */
079        public void registerCorsConfiguration(String path, CorsConfiguration config) {
080                this.corsConfigurations.put(this.patternParser.parse(path), config);
081        }
082
083        @Override
084        @Nullable
085        public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
086                PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication();
087                return this.corsConfigurations.entrySet().stream()
088                                .filter(entry -> entry.getKey().matches(lookupPath))
089                                .map(Map.Entry::getValue)
090                                .findFirst()
091                                .orElse(null);
092        }
093
094}