View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.util;
20  
21  import org.apache.shiro.lang.util.StringUtils;
22  
23  /**
24   * <p>PathMatcher implementation for Ant-style path patterns.
25   * Examples are provided below.</p>
26   *
27   * <p>Part of this mapping code has been kindly borrowed from
28   * <a href="http://ant.apache.org">Apache Ant</a>.
29   *
30   * <p>The mapping matches URLs using the following rules:<br>
31   * <ul>
32   * <li>? matches one character</li>
33   * <li>* matches zero or more characters</li>
34   * <li>** matches zero or more 'directories' in a path</li>
35   * </ul>
36   *
37   * <p>Some examples:<br>
38   * <ul>
39   * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also
40   * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li>
41   * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the
42   * <code>com</code> directory</li>
43   * <li><code>com/&#42;&#42;/test.jsp</code> - matches all <code>test.jsp</code>
44   * files underneath the <code>com</code> path</li>
45   * <li><code>org/apache/shiro/&#42;&#42;/*.jsp</code> - matches all <code>.jsp</code>
46   * files underneath the <code>org/apache/shiro</code> path</li>
47   * <li><code>org/&#42;&#42;/servlet/bla.jsp</code> - matches
48   * <code>org/apache/shiro/servlet/bla.jsp</code> but also
49   * <code>org/apache/shiro/testing/servlet/bla.jsp</code> and
50   * <code>org/servlet/bla.jsp</code></li>
51   * </ul>
52   *
53   * <p><b>N.B.</b>: This class was borrowed (with much appreciation) from the
54   * <a href="http://www.springframework.org">Spring Framework</a> with modifications.  We didn't want to reinvent the
55   * wheel of great work they've done, but also didn't want to force every Shiro user to depend on Spring</p>
56   *
57   * <p>As per the Apache 2.0 license, the original copyright notice and all author and copyright information have
58   * remained in tact.</p>
59   *
60   * @since 16.07.2003
61   */
62  public class AntPathMatcher implements PatternMatcher {
63  
64      //TODO - complete JavaDoc
65  
66      /**
67       * Default path separator: "/"
68       */
69      public static final String DEFAULT_PATH_SEPARATOR = "/";
70  
71      private String pathSeparator = DEFAULT_PATH_SEPARATOR;
72  
73  
74      /**
75       * Set the path separator to use for pattern parsing.
76       * Default is "/", as in Ant.
77       */
78      public void setPathSeparator(String pathSeparator) {
79          this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
80      }
81  
82      /**
83       * Checks if {@code path} is a pattern (i.e. contains a '*', or '?').
84       * For example the {@code /foo/**} would return {@code true}, while {@code /bar/} would return {@code false}.
85       *
86       * @param path the string to check
87       * @return this method returns {@code true} if {@code path} contains a '*' or '?', otherwise, {@code false}
88       */
89      public boolean isPattern(String path) {
90          if (path == null) {
91              return false;
92          }
93          return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
94      }
95  
96      public boolean matches(String pattern, String source) {
97          return match(pattern, source);
98      }
99  
100     public boolean match(String pattern, String path) {
101         return doMatch(pattern, path, true);
102     }
103 
104     public boolean matchStart(String pattern, String path) {
105         return doMatch(pattern, path, false);
106     }
107 
108     /**
109      * Actually match the given <code>path</code> against the given <code>pattern</code>.
110      *
111      * @param pattern   the pattern to match against
112      * @param path      the path String to test
113      * @param fullMatch whether a full pattern match is required
114      *                  (else a pattern match as far as the given base path goes is sufficient)
115      * @return <code>true</code> if the supplied <code>path</code> matched,
116      * <code>false</code> if it didn't
117      */
118     @SuppressWarnings({"checkstyle:ReturnCount", "checkstyle:CyclomaticComplexity",
119             "checkstyle:NPathComplexity", "checkstyle:MethodLength"})
120     protected boolean doMatch(String pattern, String path, boolean fullMatch) {
121         if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
122             return false;
123         }
124 
125         String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, false, true);
126         String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator, false, true);
127 
128         int pattIdxStart = 0;
129         int pattIdxEnd = pattDirs.length - 1;
130         int pathIdxStart = 0;
131         int pathIdxEnd = pathDirs.length - 1;
132 
133         // Match all elements up to the first **
134         while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
135             String patDir = pattDirs[pattIdxStart];
136             if ("**".equals(patDir)) {
137                 break;
138             }
139             if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
140                 return false;
141             }
142             pattIdxStart++;
143             pathIdxStart++;
144         }
145 
146         if (pathIdxStart > pathIdxEnd) {
147             // Path is exhausted, only match if rest of pattern is * or **'s
148             if (pattIdxStart > pattIdxEnd) {
149                 return (pattern.endsWith(this.pathSeparator)
150                         ? path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
151             }
152             if (!fullMatch) {
153                 return true;
154             }
155             if (pattIdxStart == pattIdxEnd && "*".equals(pattDirs[pattIdxStart])
156                     && path.endsWith(this.pathSeparator)) {
157                 return true;
158             }
159             for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
160                 if (!"**".equals(pattDirs[i])) {
161                     return false;
162                 }
163             }
164             return true;
165         } else if (pattIdxStart > pattIdxEnd) {
166             // String not exhausted, but pattern is. Failure.
167             return false;
168         } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
169             // Path start definitely matches due to "**" part in pattern.
170             return true;
171         }
172 
173         // up to last '**'
174         while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
175             String patDir = pattDirs[pattIdxEnd];
176             if (patDir.equals("**")) {
177                 break;
178             }
179             if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
180                 return false;
181             }
182             pattIdxEnd--;
183             pathIdxEnd--;
184         }
185         if (pathIdxStart > pathIdxEnd) {
186             // String is exhausted
187             for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
188                 if (!pattDirs[i].equals("**")) {
189                     return false;
190                 }
191             }
192             return true;
193         }
194 
195         while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
196             int patIdxTmp = -1;
197             for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
198                 if (pattDirs[i].equals("**")) {
199                     patIdxTmp = i;
200                     break;
201                 }
202             }
203             if (patIdxTmp == pattIdxStart + 1) {
204                 // '**/**' situation, so skip one
205                 pattIdxStart++;
206                 continue;
207             }
208             // Find the pattern between padIdxStart & padIdxTmp in str between
209             // strIdxStart & strIdxEnd
210             int patLength = (patIdxTmp - pattIdxStart - 1);
211             int strLength = (pathIdxEnd - pathIdxStart + 1);
212             int foundIdx = -1;
213 
214             strLoop:
215             for (int i = 0; i <= strLength - patLength; i++) {
216                 for (int j = 0; j < patLength; j++) {
217                     String subPat = (String) pattDirs[pattIdxStart + j + 1];
218                     String subStr = (String) pathDirs[pathIdxStart + i + j];
219                     if (!matchStrings(subPat, subStr)) {
220                         continue strLoop;
221                     }
222                 }
223                 foundIdx = pathIdxStart + i;
224                 break;
225             }
226 
227             if (foundIdx == -1) {
228                 return false;
229             }
230 
231             pattIdxStart = patIdxTmp;
232             pathIdxStart = foundIdx + patLength;
233         }
234 
235         for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
236             if (!pattDirs[i].equals("**")) {
237                 return false;
238             }
239         }
240 
241         return true;
242     }
243 
244     /**
245      * Tests whether or not a string matches against a pattern.
246      * The pattern may contain two special characters:<br>
247      * '*' means zero or more characters<br>
248      * '?' means one and only one character
249      *
250      * @param pattern pattern to match against.
251      *                Must not be <code>null</code>.
252      * @param str     string which must be matched against the pattern.
253      *                Must not be <code>null</code>.
254      * @return <code>true</code> if the string matches against the
255      * pattern, or <code>false</code> otherwise.
256      */
257     @SuppressWarnings({"checkstyle:ReturnCount", "checkstyle:CyclomaticComplexity",
258             "checkstyle:NPathComplexity", "checkstyle:MethodLength"})
259     private boolean matchStrings(String pattern, String str) {
260         char[] patArr = pattern.toCharArray();
261         char[] strArr = str.toCharArray();
262         int patIdxStart = 0;
263         int patIdxEnd = patArr.length - 1;
264         int strIdxStart = 0;
265         int strIdxEnd = strArr.length - 1;
266         char ch;
267 
268         boolean containsStar = false;
269         for (char aPatArr : patArr) {
270             if (aPatArr == '*') {
271                 containsStar = true;
272                 break;
273             }
274         }
275 
276         if (!containsStar) {
277             // No '*'s, so we make a shortcut
278             if (patIdxEnd != strIdxEnd) {
279                 // Pattern and string do not have the same size
280                 return false;
281             }
282             for (int i = 0; i <= patIdxEnd; i++) {
283                 ch = patArr[i];
284                 if (ch != '?') {
285                     if (ch != strArr[i]) {
286                         // Character mismatch
287                         return false;
288                     }
289                 }
290             }
291             // String matches against pattern
292             return true;
293         }
294 
295 
296         if (patIdxEnd == 0) {
297             // Pattern contains only '*', which matches anything
298             return true;
299         }
300 
301         // Process characters before first star
302         while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
303             if (ch != '?') {
304                 if (ch != strArr[strIdxStart]) {
305                     // Character mismatch
306                     return false;
307                 }
308             }
309             patIdxStart++;
310             strIdxStart++;
311         }
312         if (strIdxStart > strIdxEnd) {
313             // All characters in the string are used. Check if only '*'s are
314             // left in the pattern. If so, we succeeded. Otherwise failure.
315             for (int i = patIdxStart; i <= patIdxEnd; i++) {
316                 if (patArr[i] != '*') {
317                     return false;
318                 }
319             }
320             return true;
321         }
322 
323         // Process characters after last star
324         while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
325             if (ch != '?') {
326                 if (ch != strArr[strIdxEnd]) {
327                     // Character mismatch
328                     return false;
329                 }
330             }
331             patIdxEnd--;
332             strIdxEnd--;
333         }
334         if (strIdxStart > strIdxEnd) {
335             // All characters in the string are used. Check if only '*'s are
336             // left in the pattern. If so, we succeeded. Otherwise failure.
337             for (int i = patIdxStart; i <= patIdxEnd; i++) {
338                 if (patArr[i] != '*') {
339                     return false;
340                 }
341             }
342             return true;
343         }
344 
345         // process pattern between stars. padIdxStart and patIdxEnd point
346         // always to a '*'.
347         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
348             int patIdxTmp = -1;
349             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
350                 if (patArr[i] == '*') {
351                     patIdxTmp = i;
352                     break;
353                 }
354             }
355             if (patIdxTmp == patIdxStart + 1) {
356                 // Two stars next to each other, skip the first one.
357                 patIdxStart++;
358                 continue;
359             }
360             // Find the pattern between padIdxStart & padIdxTmp in str between
361             // strIdxStart & strIdxEnd
362             int patLength = (patIdxTmp - patIdxStart - 1);
363             int strLength = (strIdxEnd - strIdxStart + 1);
364             int foundIdx = -1;
365             strLoop:
366             for (int i = 0; i <= strLength - patLength; i++) {
367                 for (int j = 0; j < patLength; j++) {
368                     ch = patArr[patIdxStart + j + 1];
369                     if (ch != '?') {
370                         if (ch != strArr[strIdxStart + i + j]) {
371                             continue strLoop;
372                         }
373                     }
374                 }
375 
376                 foundIdx = strIdxStart + i;
377                 break;
378             }
379 
380             if (foundIdx == -1) {
381                 return false;
382             }
383 
384             patIdxStart = patIdxTmp;
385             strIdxStart = foundIdx + patLength;
386         }
387 
388         // All characters in the string are used. Check if only '*'s are left
389         // in the pattern. If so, we succeeded. Otherwise failure.
390         for (int i = patIdxStart; i <= patIdxEnd; i++) {
391             if (patArr[i] != '*') {
392                 return false;
393             }
394         }
395 
396         return true;
397     }
398 
399     /**
400      * Given a pattern and a full path, determine the pattern-mapped part.
401      * <p>For example:
402      * <ul>
403      * <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> -> ''</li>
404      * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
405      * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>commit.html</code>'</li>
406      * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
407      * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>cvs/commit.html</code>'</li>
408      * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>docs/cvs/commit.html</code>'</li>
409      * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
410      * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
411      * </ul>
412      * <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'
413      * and '<code>path</code>', but does <strong>not</strong> enforce this.
414      */
415     public String extractPathWithinPattern(String pattern, String path) {
416         String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, false, true);
417         String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator, false, true);
418         StringBuilder builder = new StringBuilder();
419         boolean pathStarted = false;
420 
421         for (int segment = 0; segment < patternParts.length; segment++) {
422             String patternPart = patternParts[segment];
423             if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) {
424                 for (; segment < pathParts.length; segment++) {
425                     if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) {
426                         builder.append(this.pathSeparator);
427                     }
428                     builder.append(pathParts[segment]);
429                     pathStarted = true;
430                 }
431             }
432         }
433 
434         return builder.toString();
435     }
436 
437 
438 }