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.util;
020    
021    /**
022     * <p>PathMatcher implementation for Ant-style path patterns.
023     * Examples are provided below.</p>
024     *
025     * <p>Part of this mapping code has been kindly borrowed from
026     * <a href="http://ant.apache.org">Apache Ant</a>.
027     *
028     * <p>The mapping matches URLs using the following rules:<br>
029     * <ul>
030     * <li>? matches one character</li>
031     * <li>* matches zero or more characters</li>
032     * <li>** matches zero or more 'directories' in a path</li>
033     * </ul>
034     *
035     * <p>Some examples:<br>
036     * <ul>
037     * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also
038     * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li>
039     * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the
040     * <code>com</code> directory</li>
041     * <li><code>com/&#42;&#42;/test.jsp</code> - matches all <code>test.jsp</code>
042     * files underneath the <code>com</code> path</li>
043     * <li><code>org/apache/shiro/&#42;&#42;/*.jsp</code> - matches all <code>.jsp</code>
044     * files underneath the <code>org/apache/shiro</code> path</li>
045     * <li><code>org/&#42;&#42;/servlet/bla.jsp</code> - matches
046     * <code>org/apache/shiro/servlet/bla.jsp</code> but also
047     * <code>org/apache/shiro/testing/servlet/bla.jsp</code> and
048     * <code>org/servlet/bla.jsp</code></li>
049     * </ul>
050     *
051     * <p><b>N.B.</b>: This class was borrowed (with much appreciation) from the
052     * <a href="http://www.springframework.org">Spring Framework</a> with modifications.  We didn't want to reinvent the
053     * wheel of great work they've done, but also didn't want to force every Shiro user to depend on Spring</p>
054     *
055     * <p>As per the Apache 2.0 license, the original copyright notice and all author and copyright information have
056     * remained in tact.</p>
057     *
058     * @author Alef Arendsen
059     * @author Juergen Hoeller
060     * @author Rob Harrop
061     * @since 16.07.2003
062     */
063    public class AntPathMatcher implements PatternMatcher {
064    
065        //TODO - complete JavaDoc
066    
067        /**
068         * Default path separator: "/"
069         */
070        public static final String DEFAULT_PATH_SEPARATOR = "/";
071    
072        private String pathSeparator = DEFAULT_PATH_SEPARATOR;
073    
074    
075        /**
076         * Set the path separator to use for pattern parsing.
077         * Default is "/", as in Ant.
078         */
079        public void setPathSeparator(String pathSeparator) {
080            this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
081        }
082    
083    
084        public boolean isPattern(String path) {
085            return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
086        }
087    
088        public boolean matches(String pattern, String source) {
089            return match(pattern, source);
090        }
091    
092        public boolean match(String pattern, String path) {
093            return doMatch(pattern, path, true);
094        }
095    
096        public boolean matchStart(String pattern, String path) {
097            return doMatch(pattern, path, false);
098        }
099    
100    
101        /**
102         * Actually match the given <code>path</code> against the given <code>pattern</code>.
103         *
104         * @param pattern   the pattern to match against
105         * @param path      the path String to test
106         * @param fullMatch whether a full pattern match is required
107         *                  (else a pattern match as far as the given base path goes is sufficient)
108         * @return <code>true</code> if the supplied <code>path</code> matched,
109         *         <code>false</code> if it didn't
110         */
111        protected boolean doMatch(String pattern, String path, boolean fullMatch) {
112            if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
113                return false;
114            }
115    
116            String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
117            String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
118    
119            int pattIdxStart = 0;
120            int pattIdxEnd = pattDirs.length - 1;
121            int pathIdxStart = 0;
122            int pathIdxEnd = pathDirs.length - 1;
123    
124            // Match all elements up to the first **
125            while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
126                String patDir = pattDirs[pattIdxStart];
127                if ("**".equals(patDir)) {
128                    break;
129                }
130                if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
131                    return false;
132                }
133                pattIdxStart++;
134                pathIdxStart++;
135            }
136    
137            if (pathIdxStart > pathIdxEnd) {
138                // Path is exhausted, only match if rest of pattern is * or **'s
139                if (pattIdxStart > pattIdxEnd) {
140                    return (pattern.endsWith(this.pathSeparator) ?
141                            path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
142                }
143                if (!fullMatch) {
144                    return true;
145                }
146                if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") &&
147                        path.endsWith(this.pathSeparator)) {
148                    return true;
149                }
150                for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
151                    if (!pattDirs[i].equals("**")) {
152                        return false;
153                    }
154                }
155                return true;
156            } else if (pattIdxStart > pattIdxEnd) {
157                // String not exhausted, but pattern is. Failure.
158                return false;
159            } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
160                // Path start definitely matches due to "**" part in pattern.
161                return true;
162            }
163    
164            // up to last '**'
165            while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
166                String patDir = pattDirs[pattIdxEnd];
167                if (patDir.equals("**")) {
168                    break;
169                }
170                if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
171                    return false;
172                }
173                pattIdxEnd--;
174                pathIdxEnd--;
175            }
176            if (pathIdxStart > pathIdxEnd) {
177                // String is exhausted
178                for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
179                    if (!pattDirs[i].equals("**")) {
180                        return false;
181                    }
182                }
183                return true;
184            }
185    
186            while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
187                int patIdxTmp = -1;
188                for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
189                    if (pattDirs[i].equals("**")) {
190                        patIdxTmp = i;
191                        break;
192                    }
193                }
194                if (patIdxTmp == pattIdxStart + 1) {
195                    // '**/**' situation, so skip one
196                    pattIdxStart++;
197                    continue;
198                }
199                // Find the pattern between padIdxStart & padIdxTmp in str between
200                // strIdxStart & strIdxEnd
201                int patLength = (patIdxTmp - pattIdxStart - 1);
202                int strLength = (pathIdxEnd - pathIdxStart + 1);
203                int foundIdx = -1;
204    
205                strLoop:
206                for (int i = 0; i <= strLength - patLength; i++) {
207                    for (int j = 0; j < patLength; j++) {
208                        String subPat = (String) pattDirs[pattIdxStart + j + 1];
209                        String subStr = (String) pathDirs[pathIdxStart + i + j];
210                        if (!matchStrings(subPat, subStr)) {
211                            continue strLoop;
212                        }
213                    }
214                    foundIdx = pathIdxStart + i;
215                    break;
216                }
217    
218                if (foundIdx == -1) {
219                    return false;
220                }
221    
222                pattIdxStart = patIdxTmp;
223                pathIdxStart = foundIdx + patLength;
224            }
225    
226            for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
227                if (!pattDirs[i].equals("**")) {
228                    return false;
229                }
230            }
231    
232            return true;
233        }
234    
235        /**
236         * Tests whether or not a string matches against a pattern.
237         * The pattern may contain two special characters:<br>
238         * '*' means zero or more characters<br>
239         * '?' means one and only one character
240         *
241         * @param pattern pattern to match against.
242         *                Must not be <code>null</code>.
243         * @param str     string which must be matched against the pattern.
244         *                Must not be <code>null</code>.
245         * @return <code>true</code> if the string matches against the
246         *         pattern, or <code>false</code> otherwise.
247         */
248        private boolean matchStrings(String pattern, String str) {
249            char[] patArr = pattern.toCharArray();
250            char[] strArr = str.toCharArray();
251            int patIdxStart = 0;
252            int patIdxEnd = patArr.length - 1;
253            int strIdxStart = 0;
254            int strIdxEnd = strArr.length - 1;
255            char ch;
256    
257            boolean containsStar = false;
258            for (char aPatArr : patArr) {
259                if (aPatArr == '*') {
260                    containsStar = true;
261                    break;
262                }
263            }
264    
265            if (!containsStar) {
266                // No '*'s, so we make a shortcut
267                if (patIdxEnd != strIdxEnd) {
268                    return false; // Pattern and string do not have the same size
269                }
270                for (int i = 0; i <= patIdxEnd; i++) {
271                    ch = patArr[i];
272                    if (ch != '?') {
273                        if (ch != strArr[i]) {
274                            return false;// Character mismatch
275                        }
276                    }
277                }
278                return true; // String matches against pattern
279            }
280    
281    
282            if (patIdxEnd == 0) {
283                return true; // Pattern contains only '*', which matches anything
284            }
285    
286            // Process characters before first star
287            while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
288                if (ch != '?') {
289                    if (ch != strArr[strIdxStart]) {
290                        return false;// Character mismatch
291                    }
292                }
293                patIdxStart++;
294                strIdxStart++;
295            }
296            if (strIdxStart > strIdxEnd) {
297                // All characters in the string are used. Check if only '*'s are
298                // left in the pattern. If so, we succeeded. Otherwise failure.
299                for (int i = patIdxStart; i <= patIdxEnd; i++) {
300                    if (patArr[i] != '*') {
301                        return false;
302                    }
303                }
304                return true;
305            }
306    
307            // Process characters after last star
308            while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
309                if (ch != '?') {
310                    if (ch != strArr[strIdxEnd]) {
311                        return false;// Character mismatch
312                    }
313                }
314                patIdxEnd--;
315                strIdxEnd--;
316            }
317            if (strIdxStart > strIdxEnd) {
318                // All characters in the string are used. Check if only '*'s are
319                // left in the pattern. If so, we succeeded. Otherwise failure.
320                for (int i = patIdxStart; i <= patIdxEnd; i++) {
321                    if (patArr[i] != '*') {
322                        return false;
323                    }
324                }
325                return true;
326            }
327    
328            // process pattern between stars. padIdxStart and patIdxEnd point
329            // always to a '*'.
330            while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
331                int patIdxTmp = -1;
332                for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
333                    if (patArr[i] == '*') {
334                        patIdxTmp = i;
335                        break;
336                    }
337                }
338                if (patIdxTmp == patIdxStart + 1) {
339                    // Two stars next to each other, skip the first one.
340                    patIdxStart++;
341                    continue;
342                }
343                // Find the pattern between padIdxStart & padIdxTmp in str between
344                // strIdxStart & strIdxEnd
345                int patLength = (patIdxTmp - patIdxStart - 1);
346                int strLength = (strIdxEnd - strIdxStart + 1);
347                int foundIdx = -1;
348                strLoop:
349                for (int i = 0; i <= strLength - patLength; i++) {
350                    for (int j = 0; j < patLength; j++) {
351                        ch = patArr[patIdxStart + j + 1];
352                        if (ch != '?') {
353                            if (ch != strArr[strIdxStart + i + j]) {
354                                continue strLoop;
355                            }
356                        }
357                    }
358    
359                    foundIdx = strIdxStart + i;
360                    break;
361                }
362    
363                if (foundIdx == -1) {
364                    return false;
365                }
366    
367                patIdxStart = patIdxTmp;
368                strIdxStart = foundIdx + patLength;
369            }
370    
371            // All characters in the string are used. Check if only '*'s are left
372            // in the pattern. If so, we succeeded. Otherwise failure.
373            for (int i = patIdxStart; i <= patIdxEnd; i++) {
374                if (patArr[i] != '*') {
375                    return false;
376                }
377            }
378    
379            return true;
380        }
381    
382        /**
383         * Given a pattern and a full path, determine the pattern-mapped part.
384         * <p>For example:
385         * <ul>
386         * <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> -> ''</li>
387         * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
388         * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>commit.html</code>'</li>
389         * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
390         * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>cvs/commit.html</code>'</li>
391         * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>docs/cvs/commit.html</code>'</li>
392         * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
393         * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
394         * </ul>
395         * <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'
396         * and '<code>path</code>', but does <strong>not</strong> enforce this.
397         */
398        public String extractPathWithinPattern(String pattern, String path) {
399            String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
400            String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
401    
402            StringBuffer buffer = new StringBuffer();
403    
404            // Add any path parts that have a wildcarded pattern part.
405            int puts = 0;
406            for (int i = 0; i < patternParts.length; i++) {
407                String patternPart = patternParts[i];
408                if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
409                    if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
410                        buffer.append(this.pathSeparator);
411                    }
412                    buffer.append(pathParts[i]);
413                    puts++;
414                }
415            }
416    
417            // Append any trailing path parts.
418            for (int i = patternParts.length; i < pathParts.length; i++) {
419                if (puts > 0 || i > 0) {
420                    buffer.append(this.pathSeparator);
421                }
422                buffer.append(pathParts[i]);
423            }
424    
425            return buffer.toString();
426        }
427    
428    }