1
17 package org.apache.tomcat.util.scan;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.net.MalformedURLException;
22 import java.net.URI;
23 import java.net.URL;
24 import java.net.URLClassLoader;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Deque;
28 import java.util.HashSet;
29 import java.util.LinkedList;
30 import java.util.Set;
31 import java.util.jar.Attributes;
32 import java.util.jar.Manifest;
33
34 import javax.servlet.ServletContext;
35
36 import org.apache.juli.logging.Log;
37 import org.apache.juli.logging.LogFactory;
38 import org.apache.tomcat.Jar;
39 import org.apache.tomcat.JarScanFilter;
40 import org.apache.tomcat.JarScanType;
41 import org.apache.tomcat.JarScanner;
42 import org.apache.tomcat.JarScannerCallback;
43 import org.apache.tomcat.util.ExceptionUtils;
44 import org.apache.tomcat.util.buf.UriUtil;
45 import org.apache.tomcat.util.compat.JreCompat;
46 import org.apache.tomcat.util.res.StringManager;
47
48
62 public class StandardJarScanner implements JarScanner {
63
64 private final Log log = LogFactory.getLog(StandardJarScanner.class);
65
66
69 private static final StringManager sm = StringManager.getManager(Constants.Package);
70
71 private static final Set<ClassLoader> CLASSLOADER_HIERARCHY;
72
73 static {
74 Set<ClassLoader> cls = new HashSet<>();
75
76 ClassLoader cl = StandardJarScanner.class.getClassLoader();
77 while (cl != null) {
78 cls.add(cl);
79 cl = cl.getParent();
80 }
81
82 CLASSLOADER_HIERARCHY = Collections.unmodifiableSet(cls);
83 }
84
85
88 private boolean scanClassPath = true;
89 public boolean isScanClassPath() {
90 return scanClassPath;
91 }
92 public void setScanClassPath(boolean scanClassPath) {
93 this.scanClassPath = scanClassPath;
94 }
95
96
99 private boolean scanManifest = true;
100 public boolean isScanManifest() {
101 return scanManifest;
102 }
103 public void setScanManifest(boolean scanManifest) {
104 this.scanManifest = scanManifest;
105 }
106
107
110 private boolean scanAllFiles = false;
111 public boolean isScanAllFiles() {
112 return scanAllFiles;
113 }
114 public void setScanAllFiles(boolean scanAllFiles) {
115 this.scanAllFiles = scanAllFiles;
116 }
117
118
122 private boolean scanAllDirectories = true;
123 public boolean isScanAllDirectories() {
124 return scanAllDirectories;
125 }
126 public void setScanAllDirectories(boolean scanAllDirectories) {
127 this.scanAllDirectories = scanAllDirectories;
128 }
129
130
134 private boolean scanBootstrapClassPath = false;
135 public boolean isScanBootstrapClassPath() {
136 return scanBootstrapClassPath;
137 }
138 public void setScanBootstrapClassPath(boolean scanBootstrapClassPath) {
139 this.scanBootstrapClassPath = scanBootstrapClassPath;
140 }
141
142
145 private JarScanFilter jarScanFilter = new StandardJarScanFilter();
146 @Override
147 public JarScanFilter getJarScanFilter() {
148 return jarScanFilter;
149 }
150 @Override
151 public void setJarScanFilter(JarScanFilter jarScanFilter) {
152 this.jarScanFilter = jarScanFilter;
153 }
154
155
166 @Override
167 public void scan(JarScanType scanType, ServletContext context,
168 JarScannerCallback callback) {
169
170 if (log.isTraceEnabled()) {
171 log.trace(sm.getString("jarScan.webinflibStart"));
172 }
173
174 if (jarScanFilter.isSkipAll()) {
175 return;
176 }
177
178 Set<URL> processedURLs = new HashSet<>();
179
180
181 Set<String> dirList = context.getResourcePaths(Constants.WEB_INF_LIB);
182 if (dirList != null) {
183 for (String path : dirList) {
184 if (path.endsWith(Constants.JAR_EXT) &&
185 getJarScanFilter().check(scanType,
186 path.substring(path.lastIndexOf('/')+1))) {
187
188 if (log.isDebugEnabled()) {
189 log.debug(sm.getString("jarScan.webinflibJarScan", path));
190 }
191 URL url = null;
192 try {
193 url = context.getResource(path);
194 processedURLs.add(url);
195 process(scanType, callback, url, path, true, null);
196 } catch (IOException e) {
197 log.warn(sm.getString("jarScan.webinflibFail", url), e);
198 }
199 } else {
200 if (log.isTraceEnabled()) {
201 log.trace(sm.getString("jarScan.webinflibJarNoScan", path));
202 }
203 }
204 }
205 }
206
207
208 try {
209 URL webInfURL = context.getResource(Constants.WEB_INF_CLASSES);
210 if (webInfURL != null) {
211
212
213
214 processedURLs.add(webInfURL);
215
216 if (isScanAllDirectories()) {
217 URL url = context.getResource(Constants.WEB_INF_CLASSES + "/META-INF");
218 if (url != null) {
219 try {
220 callback.scanWebInfClasses();
221 } catch (IOException e) {
222 log.warn(sm.getString("jarScan.webinfclassesFail"), e);
223 }
224 }
225 }
226 }
227 } catch (MalformedURLException e) {
228
229 }
230
231
232 if (isScanClassPath()) {
233 doScanClassPath(scanType, context, callback, processedURLs);
234 }
235 }
236
237
238 protected void doScanClassPath(JarScanType scanType, ServletContext context,
239 JarScannerCallback callback, Set<URL> processedURLs) {
240 if (log.isTraceEnabled()) {
241 log.trace(sm.getString("jarScan.classloaderStart"));
242 }
243
244 ClassLoader stopLoader = null;
245 if (!isScanBootstrapClassPath()) {
246
247 stopLoader = ClassLoader.getSystemClassLoader().getParent();
248 }
249
250 ClassLoader classLoader = context.getClassLoader();
251
252
253
254 boolean isWebapp = true;
255
256
257
258
259 Deque<URL> classPathUrlsToProcess = new LinkedList<>();
260
261 while (classLoader != null && classLoader != stopLoader) {
262 if (classLoader instanceof URLClassLoader) {
263 if (isWebapp) {
264 isWebapp = isWebappClassLoader(classLoader);
265 }
266
267 classPathUrlsToProcess.addAll(
268 Arrays.asList(((URLClassLoader) classLoader).getURLs()));
269
270 processURLs(scanType, callback, processedURLs, isWebapp, classPathUrlsToProcess);
271 }
272 classLoader = classLoader.getParent();
273 }
274
275 if (JreCompat.isJre9Available()) {
276
277
278
279 addClassPath(classPathUrlsToProcess);
280
281 JreCompat.getInstance().addBootModulePath(classPathUrlsToProcess);
282 processURLs(scanType, callback, processedURLs, false, classPathUrlsToProcess);
283 }
284 }
285
286
287 protected void processURLs(JarScanType scanType, JarScannerCallback callback,
288 Set<URL> processedURLs, boolean isWebapp, Deque<URL> classPathUrlsToProcess) {
289
290 if (jarScanFilter.isSkipAll()) {
291 return;
292 }
293
294 while (!classPathUrlsToProcess.isEmpty()) {
295 URL url = classPathUrlsToProcess.pop();
296
297 if (processedURLs.contains(url)) {
298
299 continue;
300 }
301
302 ClassPathEntry cpe = new ClassPathEntry(url);
303
304
305
306
307
308 if ((cpe.isJar() ||
309 scanType == JarScanType.PLUGGABILITY ||
310 isScanAllDirectories()) &&
311 getJarScanFilter().check(scanType,
312 cpe.getName())) {
313 if (log.isDebugEnabled()) {
314 log.debug(sm.getString("jarScan.classloaderJarScan", url));
315 }
316 try {
317 processedURLs.add(url);
318 process(scanType, callback, url, null, isWebapp, classPathUrlsToProcess);
319 } catch (IOException ioe) {
320 log.warn(sm.getString("jarScan.classloaderFail", url), ioe);
321 }
322 } else {
323
324 if (log.isTraceEnabled()) {
325 log.trace(sm.getString("jarScan.classloaderJarNoScan", url));
326 }
327 }
328 }
329 }
330
331
332 protected void addClassPath(Deque<URL> classPathUrlsToProcess) {
333 String classPath = System.getProperty("java.class.path");
334
335 if (classPath == null || classPath.length() == 0) {
336 return;
337 }
338
339 String[] classPathEntries = classPath.split(File.pathSeparator);
340 for (String classPathEntry : classPathEntries) {
341 File f = new File(classPathEntry);
342 try {
343 classPathUrlsToProcess.add(f.toURI().toURL());
344 } catch (MalformedURLException e) {
345 log.warn(sm.getString("jarScan.classPath.badEntry", classPathEntry), e);
346 }
347 }
348 }
349
350
351
365 private static boolean isWebappClassLoader(ClassLoader classLoader) {
366 return !CLASSLOADER_HIERARCHY.contains(classLoader);
367 }
368
369
370
374 protected void process(JarScanType scanType, JarScannerCallback callback,
375 URL url, String webappPath, boolean isWebapp, Deque<URL> classPathUrlsToProcess)
376 throws IOException {
377
378 if (log.isTraceEnabled()) {
379 log.trace(sm.getString("jarScan.jarUrlStart", url));
380 }
381
382 if ("jar".equals(url.getProtocol()) || url.getPath().endsWith(Constants.JAR_EXT)) {
383 try (Jar jar = JarFactory.newInstance(url)) {
384 if (isScanManifest()) {
385 processManifest(jar, isWebapp, classPathUrlsToProcess);
386 }
387 callback.scan(jar, webappPath, isWebapp);
388 }
389 } else if ("file".equals(url.getProtocol())) {
390 File f;
391 try {
392 f = new File(url.toURI());
393 if (f.isFile() && isScanAllFiles()) {
394
395 URL jarURL = UriUtil.buildJarUrl(f);
396 try (Jar jar = JarFactory.newInstance(jarURL)) {
397 if (isScanManifest()) {
398 processManifest(jar, isWebapp, classPathUrlsToProcess);
399 }
400 callback.scan(jar, webappPath, isWebapp);
401 }
402 } else if (f.isDirectory()) {
403 if (scanType == JarScanType.PLUGGABILITY) {
404 callback.scan(f, webappPath, isWebapp);
405 } else {
406 File metainf = new File(f.getAbsoluteFile() + File.separator + "META-INF");
407 if (metainf.isDirectory()) {
408 callback.scan(f, webappPath, isWebapp);
409 }
410 }
411 }
412 } catch (Throwable t) {
413 ExceptionUtils.handleThrowable(t);
414
415 IOException ioe = new IOException();
416 ioe.initCause(t);
417 throw ioe;
418 }
419 }
420 }
421
422
423 private void processManifest(Jar jar, boolean isWebapp,
424 Deque<URL> classPathUrlsToProcess) throws IOException {
425
426
427
428 if (isWebapp || classPathUrlsToProcess == null) {
429 return;
430 }
431
432 Manifest manifest = jar.getManifest();
433 if (manifest != null) {
434 Attributes attributes = manifest.getMainAttributes();
435 String classPathAttribute = attributes.getValue("Class-Path");
436 if (classPathAttribute == null) {
437 return;
438 }
439 String[] classPathEntries = classPathAttribute.split(" ");
440 for (String classPathEntry : classPathEntries) {
441 classPathEntry = classPathEntry.trim();
442 if (classPathEntry.length() == 0) {
443 continue;
444 }
445 URL jarURL = jar.getJarFileURL();
446 URL classPathEntryURL;
447 try {
448 URI jarURI = jarURL.toURI();
449
459 URI classPathEntryURI = jarURI.resolve(classPathEntry);
460 classPathEntryURL = classPathEntryURI.toURL();
461 } catch (Exception e) {
462 if (log.isDebugEnabled()) {
463 log.debug(sm.getString("jarScan.invalidUri", jarURL), e);
464 }
465 continue;
466 }
467 classPathUrlsToProcess.add(classPathEntryURL);
468 }
469 }
470 }
471
472
473 private static class ClassPathEntry {
474
475 private final boolean jar;
476 private final String name;
477
478 public ClassPathEntry(URL url) {
479 String path = url.getPath();
480 int end = path.lastIndexOf(Constants.JAR_EXT);
481 if (end != -1) {
482 jar = true;
483 int start = path.lastIndexOf('/', end);
484 name = path.substring(start + 1, end + 4);
485 } else {
486 jar = false;
487 if (path.endsWith("/")) {
488 path = path.substring(0, path.length() - 1);
489 }
490 int start = path.lastIndexOf('/');
491 name = path.substring(start + 1);
492 }
493
494 }
495
496 public boolean isJar() {
497 return jar;
498 }
499
500 public String getName() {
501 return name;
502 }
503 }
504 }
505