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.util.pattern;
018
019import org.apache.commons.logging.Log;
020import org.apache.commons.logging.LogFactory;
021
022import org.springframework.http.server.PathContainer;
023
024/**
025 * Parser for URI path patterns producing {@link PathPattern} instances that can
026 * then be matched to requests.
027 *
028 * <p>The {@link PathPatternParser} and {@link PathPattern} are specifically
029 * designed for use with HTTP URL paths in web applications where a large number
030 * of URI path patterns, continuously matched against incoming requests,
031 * motivates the need for efficient matching.
032 *
033 * <p>For details of the path pattern syntax see {@link PathPattern}.
034 *
035 * @author Andy Clement
036 * @since 5.0
037 */
038public class PathPatternParser {
039
040        private static final Log logger = LogFactory.getLog(PathPatternParser.class);
041
042        private boolean matchOptionalTrailingSeparator = true;
043
044        private boolean caseSensitive = true;
045
046        private PathContainer.Options pathOptions = PathContainer.Options.HTTP_PATH;
047
048
049        /**
050         * Whether a {@link PathPattern} produced by this parser should
051         * automatically match request paths with a trailing slash.
052         *
053         * <p>If set to {@code true} a {@code PathPattern} without a trailing slash
054         * will also match request paths with a trailing slash. If set to
055         * {@code false} a {@code PathPattern} will only match request paths with
056         * a trailing slash.
057         *
058         * <p>The default is {@code true}.
059         */
060        public void setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {
061                this.matchOptionalTrailingSeparator = matchOptionalTrailingSeparator;
062        }
063
064        /**
065         * Whether optional trailing slashing match is enabled.
066         */
067        public boolean isMatchOptionalTrailingSeparator() {
068                return this.matchOptionalTrailingSeparator;
069        }
070
071        /**
072         * Whether path pattern matching should be case-sensitive.
073         * <p>The default is {@code true}.
074         */
075        public void setCaseSensitive(boolean caseSensitive) {
076                this.caseSensitive = caseSensitive;
077        }
078
079        /**
080         * Whether case-sensitive pattern matching is enabled.
081         */
082        public boolean isCaseSensitive() {
083                return this.caseSensitive;
084        }
085
086        /**
087         * Set options for parsing patterns. These should be the same as the
088         * options used to parse input paths.
089         * <p>{@link org.springframework.http.server.PathContainer.Options#HTTP_PATH}
090         * is used by default.
091         * @since 5.2
092         */
093        public void setPathOptions(PathContainer.Options pathOptions) {
094                this.pathOptions = pathOptions;
095        }
096
097        /**
098         * Return the {@link #setPathOptions configured} pattern parsing options.
099         * @since 5.2
100         */
101        public PathContainer.Options getPathOptions() {
102                return this.pathOptions;
103        }
104
105
106        /**
107         * Process the path pattern content, a character at a time, breaking it into
108         * path elements around separator boundaries and verifying the structure at each
109         * stage. Produces a PathPattern object that can be used for fast matching
110         * against paths. Each invocation of this method delegates to a new instance of
111         * the {@link InternalPathPatternParser} because that class is not thread-safe.
112         * @param pathPattern the input path pattern, e.g. /project/{name}
113         * @return a PathPattern for quickly matching paths against request paths
114         * @throws PatternParseException in case of parse errors
115         */
116        public PathPattern parse(String pathPattern) throws PatternParseException {
117                int wildcardIndex = pathPattern.indexOf("**" + this.pathOptions.separator());
118                if (wildcardIndex != -1 && wildcardIndex != pathPattern.length() - 3) {
119                        logger.warn("'**' patterns are not supported in the middle of patterns and will be rejected in the future. " +
120                                        "Consider using '*' instead for matching a single path segment.");
121                }
122                return new InternalPathPatternParser(this).parse(pathPattern);
123        }
124
125
126        /**
127         * Shared, read-only instance of {@code PathPatternParser}. Uses default settings:
128         * <ul>
129         * <li>{@code matchOptionalTrailingSeparator=true}
130         * <li>{@code caseSensitivetrue}
131         * <li>{@code pathOptions=PathContainer.Options.HTTP_PATH}
132         * </ul>
133         */
134        public final static PathPatternParser defaultInstance = new PathPatternParser() {
135
136                @Override
137                public void setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) {
138                        raiseError();
139                }
140
141                @Override
142                public void setCaseSensitive(boolean caseSensitive) {
143                        raiseError();
144                }
145
146                @Override
147                public void setPathOptions(PathContainer.Options pathOptions) {
148                        raiseError();
149                }
150
151                private void raiseError() {
152                        throw new UnsupportedOperationException(
153                                        "This is a read-only, shared instance that cannot be modified");
154                }
155        };
156}