// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.start; import java.io.File; import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; /** * Common PathMatcher implementations. */ public class PathMatchers { private static class NonHiddenMatcher implements PathMatcher { @Override public boolean matches(Path path) { try { return !Files.isHidden(path); } catch (IOException e) { StartLog.debug(e); return false; } } } private static final char GLOB_CHARS[] = "*?".toCharArray(); private static final char SYNTAXED_GLOB_CHARS[] = "{}[]|:".toCharArray(); private static final Path EMPTY_PATH = new File(".").toPath(); /** * Convert a pattern to a Path object. * * @param pattern * the raw pattern (can contain "glob:" or "regex:" syntax indicator) * @return the Path version of the pattern provided. */ private static Path asPath(final String pattern) { String test = pattern; if (test.startsWith("glob:")) { test = test.substring("glob:".length()); } else if (test.startsWith("regex:")) { test = test.substring("regex:".length()); } return new File(test).toPath(); } public static PathMatcher getMatcher(final String rawpattern) { FileSystem fs = FileSystems.getDefault(); String pattern = rawpattern; // Strip trailing slash (if present) int lastchar = pattern.charAt(pattern.length() - 1); if (lastchar == '/' || lastchar == '\\') { pattern = pattern.substring(0,pattern.length() - 1); } // If using FileSystem.getPathMatcher() with "glob:" or "regex:" // use FileSystem default pattern behavior if (pattern.startsWith("glob:") || pattern.startsWith("regex:")) { StartLog.debug("Using Standard " + fs.getClass().getName() + " pattern: " + pattern); return fs.getPathMatcher(pattern); } // If the pattern starts with a root path then its assumed to // be a full system path if (isAbsolute(pattern)) { String pat = "glob:" + pattern; StartLog.debug("Using absolute path pattern: " + pat); return fs.getPathMatcher(pat); } // Doesn't start with filesystem root, then assume the pattern // is a relative file path pattern. String pat = "glob:**/" + pattern; StartLog.debug("Using relative path pattern: " + pat); return fs.getPathMatcher(pat); } public static PathMatcher getNonHidden() { return new NonHiddenMatcher(); } /** * Provide the non-glob / non-regex prefix on the pattern as a Path reference. * * @param pattern * the pattern to test * @return the Path representing the search root for the pattern provided. */ public static Path getSearchRoot(final String pattern) { StringBuilder root = new StringBuilder(); int start = 0; boolean syntaxed = false; if (pattern.startsWith("glob:")) { start = "glob:".length(); syntaxed = true; } else if (pattern.startsWith("regex:")) { start = "regex:".length(); syntaxed = true; } int len = pattern.length(); int lastSep = 0; for (int i = start; i < len; i++) { int cp = pattern.codePointAt(i); if (cp < 127) { char c = (char)cp; // unix path case if (c == '/') { root.append(c); lastSep = root.length(); } else if (c == '\\') { root.append("\\"); lastSep = root.length(); // possible escaped sequence. // only really interested in windows escape sequences "\\" int count = countChars(pattern,i+1,'\\'); if (count > 0) { // skip extra slashes i += count; } } else { if (isGlob(c,syntaxed)) { break; } root.append(c); } } else { root.appendCodePoint(cp); } } String rootPath = root.substring(0,lastSep); if (rootPath.length() <= 0) { return EMPTY_PATH; } return asPath(rootPath); } private static int countChars(String pattern, int offset, char c) { int count = 0; int len = pattern.length(); for (int i = offset; i < len; i++) { if (pattern.charAt(i) == c) { count++; } else { break; } } return count; } /** * Tests if provided pattern is an absolute reference (or not) * * @param pattern * the pattern to test * @return true if pattern is an absolute reference. */ public static boolean isAbsolute(final String pattern) { Path searchRoot = getSearchRoot(pattern); if (searchRoot == EMPTY_PATH) { return false; } return searchRoot.isAbsolute(); } /** * Determine if part is a glob pattern. * * @param part * the string to check * @param syntaxed * true if overall pattern is syntaxed with <code>"glob:"</code> or <code>"regex:"</code> * @return true if part has glob characters */ private static boolean isGlob(char c, boolean syntaxed) { for (char g : GLOB_CHARS) { if (c == g) { return true; } } if (syntaxed) { for (char g : SYNTAXED_GLOB_CHARS) { if (c == g) { return true; } } } return false; } }