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