1 //
2 // ========================================================================
3 // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.start;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.file.FileSystem;
24 import java.nio.file.FileSystems;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.PathMatcher;
28
29 /**
30 * Common PathMatcher implementations.
31 */
32 public class PathMatchers
33 {
34 private static class NonHiddenMatcher implements PathMatcher
35 {
36 @Override
37 public boolean matches(Path path)
38 {
39 try
40 {
41 return !Files.isHidden(path);
42 }
43 catch (IOException e)
44 {
45 StartLog.debug(e);
46 return false;
47 }
48 }
49 }
50
51 private static final char GLOB_CHARS[] = "*?".toCharArray();
52 private static final char SYNTAXED_GLOB_CHARS[] = "{}[]|:".toCharArray();
53 private static final Path EMPTY_PATH = new File(".").toPath();
54
55 /**
56 * Convert a pattern to a Path object.
57 *
58 * @param pattern
59 * the raw pattern (can contain "glob:" or "regex:" syntax indicator)
60 * @return the Path version of the pattern provided.
61 */
62 private static Path asPath(final String pattern)
63 {
64 String test = pattern;
65 if (test.startsWith("glob:"))
66 {
67 test = test.substring("glob:".length());
68 }
69 else if (test.startsWith("regex:"))
70 {
71 test = test.substring("regex:".length());
72 }
73 return new File(test).toPath();
74 }
75
76 public static PathMatcher getMatcher(final String rawpattern)
77 {
78 FileSystem fs = FileSystems.getDefault();
79
80 String pattern = rawpattern;
81
82 // Strip trailing slash (if present)
83 int lastchar = pattern.charAt(pattern.length() - 1);
84 if (lastchar == '/' || lastchar == '\\')
85 {
86 pattern = pattern.substring(0,pattern.length() - 1);
87 }
88
89 // If using FileSystem.getPathMatcher() with "glob:" or "regex:"
90 // use FileSystem default pattern behavior
91 if (pattern.startsWith("glob:") || pattern.startsWith("regex:"))
92 {
93 StartLog.debug("Using Standard " + fs.getClass().getName() + " pattern: " + pattern);
94 return fs.getPathMatcher(pattern);
95 }
96
97 // If the pattern starts with a root path then its assumed to
98 // be a full system path
99 if (isAbsolute(pattern))
100 {
101 String pat = "glob:" + pattern;
102 StartLog.debug("Using absolute path pattern: " + pat);
103 return fs.getPathMatcher(pat);
104 }
105
106 // Doesn't start with filesystem root, then assume the pattern
107 // is a relative file path pattern.
108 String pat = "glob:**/" + pattern;
109 StartLog.debug("Using relative path pattern: " + pat);
110 return fs.getPathMatcher(pat);
111 }
112
113 public static PathMatcher getNonHidden()
114 {
115 return new NonHiddenMatcher();
116 }
117
118 /**
119 * Provide the non-glob / non-regex prefix on the pattern as a Path reference.
120 *
121 * @param pattern
122 * the pattern to test
123 * @return the Path representing the search root for the pattern provided.
124 */
125 public static Path getSearchRoot(final String pattern)
126 {
127 StringBuilder root = new StringBuilder();
128
129 int start = 0;
130 boolean syntaxed = false;
131 if (pattern.startsWith("glob:"))
132 {
133 start = "glob:".length();
134 syntaxed = true;
135 }
136 else if (pattern.startsWith("regex:"))
137 {
138 start = "regex:".length();
139 syntaxed = true;
140 }
141 int len = pattern.length();
142 int lastSep = 0;
143 for (int i = start; i < len; i++)
144 {
145 int cp = pattern.codePointAt(i);
146 if (cp < 127)
147 {
148 char c = (char)cp;
149
150 // unix path case
151 if (c == '/')
152 {
153 root.append(c);
154 lastSep = root.length();
155 }
156 else if (c == '\\')
157 {
158 root.append("\\");
159 lastSep = root.length();
160
161 // possible escaped sequence.
162 // only really interested in windows escape sequences "\\"
163 int count = countChars(pattern,i+1,'\\');
164 if (count > 0)
165 {
166 // skip extra slashes
167 i += count;
168 }
169 }
170 else
171 {
172 if (isGlob(c,syntaxed))
173 {
174 break;
175 }
176 root.append(c);
177 }
178 }
179 else
180 {
181 root.appendCodePoint(cp);
182 }
183 }
184
185 String rootPath = root.substring(0,lastSep);
186 if (rootPath.length() <= 0)
187 {
188 return EMPTY_PATH;
189 }
190
191 return asPath(rootPath);
192 }
193
194 private static int countChars(String pattern, int offset, char c)
195 {
196 int count = 0;
197 int len = pattern.length();
198 for (int i = offset; i < len; i++)
199 {
200 if (pattern.charAt(i) == c)
201 {
202 count++;
203 }
204 else
205 {
206 break;
207 }
208 }
209 return count;
210 }
211
212 /**
213 * Tests if provided pattern is an absolute reference (or not)
214 *
215 * @param pattern
216 * the pattern to test
217 * @return true if pattern is an absolute reference.
218 */
219 public static boolean isAbsolute(final String pattern)
220 {
221 Path searchRoot = getSearchRoot(pattern);
222 if (searchRoot == EMPTY_PATH)
223 {
224 return false;
225 }
226 return searchRoot.isAbsolute();
227 }
228
229 /**
230 * Determine if part is a glob pattern.
231 *
232 * @param part
233 * the string to check
234 * @param syntaxed
235 * true if overall pattern is syntaxed with <code>"glob:"</code> or <code>"regex:"</code>
236 * @return true if part has glob characters
237 */
238 private static boolean isGlob(char c, boolean syntaxed)
239 {
240 for (char g : GLOB_CHARS)
241 {
242 if (c == g)
243 {
244 return true;
245 }
246 }
247 if (syntaxed)
248 {
249 for (char g : SYNTAXED_GLOB_CHARS)
250 {
251 if (c == g)
252 {
253 return true;
254 }
255 }
256 }
257 return false;
258 }
259 }