001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.shiro.web.filter;
020    
021    import org.apache.shiro.util.AntPathMatcher;
022    import org.apache.shiro.util.PatternMatcher;
023    import org.apache.shiro.web.servlet.AdviceFilter;
024    import org.apache.shiro.web.util.WebUtils;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    
028    import javax.servlet.Filter;
029    import javax.servlet.ServletRequest;
030    import javax.servlet.ServletResponse;
031    import java.util.LinkedHashMap;
032    import java.util.Map;
033    
034    import static org.apache.shiro.util.StringUtils.split;
035    
036    /**
037     * <p>Base class for Filters that will process only specified paths and allow all others to pass through.</p>
038     *
039     * @since 0.9
040     */
041    public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
042    
043        /**
044         * Log available to this class only
045         */
046        private static final Logger log = LoggerFactory.getLogger(PathMatchingFilter.class);
047    
048        /**
049         * PatternMatcher used in determining which paths to react to for a given request.
050         */
051        protected PatternMatcher pathMatcher = new AntPathMatcher();
052    
053        /**
054         * A collection of path-to-config entries where the key is a path which this filter should process and
055         * the value is the (possibly null) configuration element specific to this Filter for that specific path.
056         *
057         * <p>To put it another way, the keys are the paths (urls) that this Filter will process.
058         * <p>The values are filter-specific data that this Filter should use when processing the corresponding
059         * key (path).  The values can be null if no Filter-specific config was specified for that url.
060         */
061        protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
062    
063        /**
064         * Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting
065         * <code>String[]</code> array on the <code>appliedPaths</code> internal Map.
066         * <p/>
067         * That is:
068         * <pre><code>
069         * String[] values = null;
070         * if (config != null) {
071         *     values = split(config);
072         * }
073         *
074         * this.{@link #appliedPaths appliedPaths}.put(path, values);
075         * </code></pre>
076         *
077         * @param path   the application context path to match for executing this filter.
078         * @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
079         * @return this configured filter.
080         */
081        public Filter processPathConfig(String path, String config) {
082            String[] values = null;
083            if (config != null) {
084                values = split(config);
085            }
086    
087            this.appliedPaths.put(path, values);
088            return this;
089        }
090    
091        /**
092         * Returns the context path within the application based on the specified <code>request</code>.
093         * <p/>
094         * This implementation merely delegates to
095         * {@link WebUtils#getPathWithinApplication(javax.servlet.http.HttpServletRequest) WebUtils.getPathWithinApplication(request)},
096         * but can be overridden by subclasses for custom logic.
097         *
098         * @param request the incoming <code>ServletRequest</code>
099         * @return the context path within the application.
100         */
101        protected String getPathWithinApplication(ServletRequest request) {
102            return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
103        }
104    
105        /**
106         * Returns <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
107         * <code>false</code> otherwise.
108         * <p/>
109         * The default implementation acquires the <code>request</code>'s path within the application and determines
110         * if that matches:
111         * <p/>
112         * <code>String requestURI = {@link #getPathWithinApplication(javax.servlet.ServletRequest) getPathWithinApplication(request)};<br/>
113         * return {@link #pathsMatch(String, String) pathsMatch(path,requestURI)}</code>
114         *
115         * @param path    the configured url pattern to check the incoming request against.
116         * @param request the incoming ServletRequest
117         * @return <code>true</code> if the incoming <code>request</code> matches the specified <code>path</code> pattern,
118         *         <code>false</code> otherwise.
119         */
120        protected boolean pathsMatch(String path, ServletRequest request) {
121            String requestURI = getPathWithinApplication(request);
122            if (log.isTraceEnabled()) {
123                log.trace("Attempting to match pattern [" + path + "] with current requestURI [" + requestURI + "]...");
124            }
125            return pathsMatch(path, requestURI);
126        }
127    
128        /**
129         * Returns <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
130         * <code>false</code> otherwise.
131         * <p/>
132         * Simply delegates to
133         * <b><code>this.pathMatcher.{@link PatternMatcher#matches(String, String) matches(pattern,path)}</code></b>,
134         * but can be overridden by subclasses for custom matching behavior.
135         *
136         * @param pattern the pattern to match against
137         * @param path    the value to match with the specified <code>pattern</code>
138         * @return <code>true</code> if the <code>path</code> matches the specified <code>pattern</code> string,
139         *         <code>false</code> otherwise.
140         */
141        protected boolean pathsMatch(String pattern, String path) {
142            return pathMatcher.matches(pattern, path);
143        }
144    
145        /**
146         * Implementation that handles path-matching behavior before a request is evaluated.  If the path matches,
147         * the request will be allowed through via the result from
148         * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}.  If the
149         * path does not match, this filter will allow passthrough immediately.
150         *
151         * <p>In order to retain path-matching functionality, subclasses should not override this method if at all
152         * possible, and instead override
153         * {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead.
154         *
155         * @param request  the incoming ServletRequest
156         * @param response the outgoing ServletResponse
157         * @return true - allow the request chain to continue in this default implementation
158         * @throws Exception
159         */
160        public boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
161    
162            if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
163                if (log.isTraceEnabled()) {
164                    log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");
165                }
166                return true;
167            }
168    
169            for (String path : this.appliedPaths.keySet()) {
170                // If the path does match, then pass on to the subclass implementation for specific checks
171                //(first match 'wins'):
172                if (pathsMatch(path, request)) {
173                    if (log.isTraceEnabled()) {
174                        log.trace("Current requestURI matches pattern [" + path + "].  Performing onPreHandle check...");
175                    }
176                    Object config = this.appliedPaths.get(path);
177                    return onPreHandle(request, response, config);
178                }
179            }
180    
181            //no path matched, allow the request to go through:
182            return true;
183        }
184    
185        /**
186         * Default implementation always returns <code>true</code>.  Should be overridden by subclasses for custom
187         * logic.
188         *
189         * @param request     the incoming ServletRequest
190         * @param response    the outgoing ServletResponse
191         * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
192         * @return <code>true</code> if the request should be able to continue, <code>false</code> if the filter will
193         *         handle the response directly.
194         * @throws Exception if an error occurs
195         */
196        protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
197            return true;
198        }
199    }