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/**/test.jsp</code> - matches all <code>test.jsp</code>
042 * files underneath the <code>com</code> path</li>
043 * <li><code>org/apache/shiro/**/*.jsp</code> - matches all <code>.jsp</code>
044 * files underneath the <code>org/apache/shiro</code> path</li>
045 * <li><code>org/**/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 }