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 */ 019package org.apache.shiro.util; 020 021import org.apache.shiro.lang.util.StringUtils; 022 023/** 024 * <p>PathMatcher implementation for Ant-style path patterns. 025 * Examples are provided below.</p> 026 * 027 * <p>Part of this mapping code has been kindly borrowed from 028 * <a href="http://ant.apache.org">Apache Ant</a>. 029 * 030 * <p>The mapping matches URLs using the following rules:<br> 031 * <ul> 032 * <li>? matches one character</li> 033 * <li>* matches zero or more characters</li> 034 * <li>** matches zero or more 'directories' in a path</li> 035 * </ul> 036 * 037 * <p>Some examples:<br> 038 * <ul> 039 * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also 040 * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li> 041 * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the 042 * <code>com</code> directory</li> 043 * <li><code>com/**/test.jsp</code> - matches all <code>test.jsp</code> 044 * files underneath the <code>com</code> path</li> 045 * <li><code>org/apache/shiro/**/*.jsp</code> - matches all <code>.jsp</code> 046 * files underneath the <code>org/apache/shiro</code> path</li> 047 * <li><code>org/**/servlet/bla.jsp</code> - matches 048 * <code>org/apache/shiro/servlet/bla.jsp</code> but also 049 * <code>org/apache/shiro/testing/servlet/bla.jsp</code> and 050 * <code>org/servlet/bla.jsp</code></li> 051 * </ul> 052 * 053 * <p><b>N.B.</b>: This class was borrowed (with much appreciation) from the 054 * <a href="http://www.springframework.org">Spring Framework</a> with modifications. We didn't want to reinvent the 055 * wheel of great work they've done, but also didn't want to force every Shiro user to depend on Spring</p> 056 * 057 * <p>As per the Apache 2.0 license, the original copyright notice and all author and copyright information have 058 * remained in tact.</p> 059 * 060 * @since 16.07.2003 061 */ 062public class AntPathMatcher implements PatternMatcher { 063 064 //TODO - complete JavaDoc 065 066 /** 067 * Default path separator: "/" 068 */ 069 public static final String DEFAULT_PATH_SEPARATOR = "/"; 070 071 private String pathSeparator = DEFAULT_PATH_SEPARATOR; 072 073 074 /** 075 * Set the path separator to use for pattern parsing. 076 * Default is "/", as in Ant. 077 */ 078 public void setPathSeparator(String pathSeparator) { 079 this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR); 080 } 081 082 /** 083 * Checks if {@code path} is a pattern (i.e. contains a '*', or '?'). 084 * For example the {@code /foo/**} would return {@code true}, while {@code /bar/} would return {@code false}. 085 * 086 * @param path the string to check 087 * @return this method returns {@code true} if {@code path} contains a '*' or '?', otherwise, {@code false} 088 */ 089 public boolean isPattern(String path) { 090 if (path == null) { 091 return false; 092 } 093 return (path.indexOf('*') != -1 || path.indexOf('?') != -1); 094 } 095 096 public boolean matches(String pattern, String source) { 097 return match(pattern, source); 098 } 099 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}