1 /*
2 * This file is part of ClassGraph.
3 *
4 * Author: Luke Hutchison
5 *
6 * Hosted at: https://github.com/classgraph/classgraph
7 *
8 * --
9 *
10 * The MIT License (MIT)
11 *
12 * Copyright (c) 2019 Luke Hutchison
13 *
14 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
15 * documentation files (the "Software"), to deal in the Software without restriction, including without
16 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
17 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
18 * conditions:
19 *
20 * The above copyright notice and this permission notice shall be included in all copies or substantial
21 * portions of the Software.
22 *
23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
27 * OR OTHER DEALINGS IN THE SOFTWARE.
28 */
29 package nonapi.io.github.classgraph.scanspec;
30
31 import java.io.InputStream;
32 import java.lang.reflect.Field;
33 import java.net.URI;
34 import java.net.URL;
35 import java.nio.ByteBuffer;
36 import java.nio.MappedByteBuffer;
37 import java.nio.channels.FileChannel;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Set;
43
44 import io.github.classgraph.ClassGraph.ClasspathElementFilter;
45 import io.github.classgraph.ClassGraphException;
46 import io.github.classgraph.ClassInfo;
47 import io.github.classgraph.ModulePathInfo;
48 import io.github.classgraph.ScanResult;
49 import nonapi.io.github.classgraph.scanspec.WhiteBlackList.WhiteBlackListLeafname;
50 import nonapi.io.github.classgraph.scanspec.WhiteBlackList.WhiteBlackListPrefix;
51 import nonapi.io.github.classgraph.scanspec.WhiteBlackList.WhiteBlackListWholeString;
52 import nonapi.io.github.classgraph.utils.LogNode;
53
54 /**
55 * The scanning specification.
56 */
57 public class ScanSpec {
58 /** Package white/blacklist (with separator '.'). */
59 public WhiteBlackListWholeString packageWhiteBlackList = new WhiteBlackListWholeString('.');
60
61 /** Package prefix white/blacklist, for recursive scanning (with separator '.', ending in '.'). */
62 public WhiteBlackListPrefix packagePrefixWhiteBlackList = new WhiteBlackListPrefix('.');
63
64 /** Path white/blacklist (with separator '/'). */
65 public WhiteBlackListWholeString pathWhiteBlackList = new WhiteBlackListWholeString('/');
66
67 /** Path prefix white/blacklist, for recursive scanning (with separator '/', ending in '/'). */
68 public WhiteBlackListPrefix pathPrefixWhiteBlackList = new WhiteBlackListPrefix('/');
69
70 /** Class white/blacklist (fully-qualified class names, with separator '.'). */
71 public WhiteBlackListWholeString classWhiteBlackList = new WhiteBlackListWholeString('.');
72
73 /** Classfile white/blacklist (path to classfiles, with separator '/', ending in ".class"). */
74 public WhiteBlackListWholeString classfilePathWhiteBlackList = new WhiteBlackListWholeString('/');
75
76 /** Package containing white/blacklisted classes (with separator '.'). */
77 public WhiteBlackListWholeString classPackageWhiteBlackList = new WhiteBlackListWholeString('.');
78
79 /** Path to white/blacklisted classes (with separator '/'). */
80 public WhiteBlackListWholeString classPackagePathWhiteBlackList = new WhiteBlackListWholeString('/');
81
82 /** Module white/blacklist (with separator '.'). */
83 public WhiteBlackListWholeString moduleWhiteBlackList = new WhiteBlackListWholeString('.');
84
85 /** Jar white/blacklist (leafname only, ending in ".jar"). */
86 public WhiteBlackListLeafname jarWhiteBlackList = new WhiteBlackListLeafname('/');
87
88 /** Classpath element resource path white/blacklist. */
89 public WhiteBlackListWholeString classpathElementResourcePathWhiteBlackList = //
90 new WhiteBlackListWholeString('/');
91
92 /** lib/ext jar white/blacklist (leafname only, ending in ".jar"). */
93 public WhiteBlackListLeafname libOrExtJarWhiteBlackList = new WhiteBlackListLeafname('/');
94
95 // -------------------------------------------------------------------------------------------------------------
96
97 /** If true, scan jarfiles. */
98 public boolean scanJars = true;
99
100 /** If true, scan nested jarfiles (jarfiles within jarfiles). */
101 public boolean scanNestedJars = true;
102
103 /** If true, scan directories. */
104 public boolean scanDirs = true;
105
106 /** If true, scan modules. */
107 public boolean scanModules = true;
108
109 /** If true, scan classfile bytecodes, producing {@link ClassInfo} objects. */
110 public boolean enableClassInfo;
111
112 /**
113 * If true, enables the saving of field info during the scan. This information can be obtained using
114 * {@link ClassInfo#getFieldInfo()}. By default, field info is not scanned, for efficiency.
115 */
116 public boolean enableFieldInfo;
117
118 /**
119 * If true, enables the saving of method info during the scan. This information can be obtained using
120 * {@link ClassInfo#getMethodInfo()}. By default, method info is not scanned, for efficiency.
121 */
122 public boolean enableMethodInfo;
123
124 /**
125 * If true, enables the saving of annotation info (for class, field, method or method parameter annotations)
126 * during the scan. This information can be obtained using {@link ClassInfo#getAnnotationInfo()} etc. By
127 * default, annotation info is not scanned, for efficiency.
128 */
129 public boolean enableAnnotationInfo;
130
131 /** Enable the storing of constant initializer values for static final fields in ClassInfo objects. */
132 public boolean enableStaticFinalFieldConstantInitializerValues;
133
134 /** If true, enables the determination of inter-class dependencies. */
135 public boolean enableInterClassDependencies;
136
137 /**
138 * If true, allow external classes (classes outside of whitelisted packages) to be returned in the ScanResult,
139 * if they are directly referred to by a whitelisted class, as a superclass, implemented interface or
140 * annotation. Disabled by default.
141 */
142 public boolean enableExternalClasses;
143
144 /**
145 * If true, system jarfiles (rt.jar) and system packages and modules (java.*, jre.*, etc.) should be scanned .
146 */
147 public boolean enableSystemJarsAndModules;
148
149 /**
150 * If true, ignore class visibility. If false, classes must be public to be scanned.
151 */
152 public boolean ignoreClassVisibility;
153
154 /**
155 * If true, ignore field visibility. If false, fields must be public to be scanned.
156 */
157 public boolean ignoreFieldVisibility;
158
159 /**
160 * If true, ignore method visibility. If false, methods must be public to be scanned.
161 */
162 public boolean ignoreMethodVisibility;
163
164 /**
165 * If true, don't scan runtime-invisible annotations (only scan annotations with RetentionPolicy.RUNTIME).
166 */
167 public boolean disableRuntimeInvisibleAnnotations;
168
169 /**
170 * If true, when classes have superclasses, implemented interfaces or annotations that are external classes,
171 * those classes are also scanned. (Even though this slows down scanning a bit, there is no API for disabling
172 * this currently, since disabling it can lead to problems -- see #261.)
173 */
174 public boolean extendScanningUpwardsToExternalClasses = true;
175
176 /**
177 * URL schemes that are allowed in classpath elements (not counting the optional "jar:" prefix and/or "file:",
178 * which are automatically allowed).
179 */
180 public Set<String> allowedURLSchemes;
181
182 // -------------------------------------------------------------------------------------------------------------
183
184 /**
185 * If non-null, specifies manually-added classloaders that should be searched after the context classloader(s).
186 */
187 public transient List<ClassLoader> addedClassLoaders;
188
189 /**
190 * If non-null, this list of ClassLoaders will be searched instead of the visible/context ClassLoader(s). In
191 * particular, this causes ClassGraph to ignore the java.class.path system property.
192 */
193 public transient List<ClassLoader> overrideClassLoaders;
194
195 /**
196 * If non-null, specifies manually-added ModuleLayers that should be searched after the visible ModuleLayers.
197 */
198 public transient List<Object> addedModuleLayers;
199
200 /**
201 * If non-null, this list of ModuleLayers will be searched instead of the visible ModuleLayers.
202 */
203 public transient List<Object> overrideModuleLayers;
204
205 /**
206 * If non-null, specifies a list of classpath elements (String, {@link URL} or {@link URI} to use to override
207 * the default classpath.
208 */
209 public List<Object> overrideClasspath;
210
211 /** If non-null, a list of filter operations to apply to classpath elements. */
212 public transient List<ClasspathElementFilter> classpathElementFilters;
213
214 /** Whether to initialize classes when loading them. */
215 public boolean initializeLoadedClasses;
216
217 /**
218 * If true, nested jarfiles (jarfiles within jarfiles) that are extracted during scanning are removed from their
219 * temporary directory (e.g. /tmp/ClassGraph-8JX2u4w) after the scan has completed. If false, temporary files
220 * are removed by the {@link ScanResult} finalizer, or on JVM exit.
221 */
222 public boolean removeTemporaryFilesAfterScan;
223
224 /** If true, do not fetch paths from parent classloaders. */
225 public boolean ignoreParentClassLoaders;
226
227 /** If true, do not scan module layers that are the parent of other module layers. */
228 public boolean ignoreParentModuleLayers;
229
230 /** Commandline module path parameters. */
231 public ModulePathInfo modulePathInfo = new ModulePathInfo();
232
233 // -------------------------------------------------------------------------------------------------------------
234
235 /**
236 * The maximum size of an inner (nested) jar that has been deflated (i.e. compressed, not stored) within an
237 * outer jar, before it has to be spilled to disk rather than stored in a RAM-backed {@link ByteBuffer} when it
238 * is deflated, in order for the inner jar's entries to be read. (Note that this situation of having to deflate
239 * a nested jar to RAM or disk in order to read it is rare, because normally adding a jarfile to another jarfile
240 * will store the inner jar, rather than deflate it, because deflating a jarfile does not usually produce any
241 * further compression gains. If an inner jar is stored, not deflated, then its zip entries can be read directly
242 * using ClassGraph's own zipfile central directory parser, which can use file slicing to extract entries
243 * directly from stored nested jars.)
244 *
245 * <p>
246 * This is also the maximum size of a jar downloaded from an {@code http://} or {@code https://} classpath
247 * {@link URL} to RAM. Once this many bytes have been read from the {@link URL}'s {@link InputStream}, then the
248 * RAM contents are spilled over to a temporary file on disk, and the rest of the content is downloaded to the
249 * temporary file. (This is also rare, because normally there are no {@code http://} or {@code https://}
250 * classpath entries.)
251 *
252 * <p>
253 * Default: 64MB (i.e. writing to disk is avoided wherever possible). Setting a lower max RAM size value will
254 * decrease ClassGraph's memory usage if either of the above rare situations occurs.
255 */
256 public int maxBufferedJarRAMSize = 64 * 1024 * 1024;
257
258 /** If true, use a {@link MappedByteBuffer} rather than the {@link FileChannel} API to access file content. */
259 public boolean enableMemoryMapping;
260
261 // -------------------------------------------------------------------------------------------------------------
262
263 /** Constructor for deserialization. */
264 public ScanSpec() {
265 // Intentionally empty
266 }
267
268 // -------------------------------------------------------------------------------------------------------------
269
270 /** Sort prefixes to ensure correct whitelist/blacklist evaluation (see Issue #167). */
271 public void sortPrefixes() {
272 for (final Field field : ScanSpec.class.getDeclaredFields()) {
273 if (WhiteBlackList.class.isAssignableFrom(field.getType())) {
274 try {
275 ((WhiteBlackList) field.get(this)).sortPrefixes();
276 } catch (final ReflectiveOperationException e) {
277 throw ClassGraphException.newClassGraphException("Field is not accessible: " + field, e);
278 }
279 }
280 }
281 }
282
283 // -------------------------------------------------------------------------------------------------------------
284
285 /**
286 * Override the automatically-detected classpath with a custom path. You can specify multiple elements in
287 * separate calls, and if this method is called even once, the default classpath will be overridden, such that
288 * nothing but the provided classpath will be scanned, i.e. causes ClassLoaders to be ignored, as well as the
289 * java.class.path system property.
290 *
291 * @param overrideClasspathElement
292 * The classpath element to add as an override to the default classpath.
293 */
294 public void addClasspathOverride(final Object overrideClasspathElement) {
295 if (this.overrideClasspath == null) {
296 this.overrideClasspath = new ArrayList<>();
297 }
298 this.overrideClasspath
299 .add(overrideClasspathElement instanceof String || overrideClasspathElement instanceof URL
300 || overrideClasspathElement instanceof URI ? overrideClasspathElement
301 : overrideClasspathElement.toString());
302 }
303
304 /**
305 * Add a classpath element filter. The provided ClasspathElementFilter should return true if the path string
306 * passed to it is a path you want to scan.
307 *
308 * @param classpathElementFilter
309 * The classpath element filter to apply to all discovered classpath elements, to decide which should
310 * be scanned.
311 */
312 public void filterClasspathElements(final ClasspathElementFilter classpathElementFilter) {
313 if (this.classpathElementFilters == null) {
314 this.classpathElementFilters = new ArrayList<>(2);
315 }
316 this.classpathElementFilters.add(classpathElementFilter);
317 }
318
319 /**
320 * Add a ClassLoader to the list of ClassLoaders to scan. (This only works if overrideClasspath() is not
321 * called.)
322 *
323 * @param classLoader
324 * The classloader to add.
325 */
326 public void addClassLoader(final ClassLoader classLoader) {
327 if (this.addedClassLoaders == null) {
328 this.addedClassLoaders = new ArrayList<>();
329 }
330 if (classLoader != null) {
331 this.addedClassLoaders.add(classLoader);
332 }
333 }
334
335 /**
336 * Allow a specified URL scheme in classpath elements.
337 *
338 * @param scheme
339 * the scheme, e.g. "http".
340 */
341 public void enableURLScheme(final String scheme) {
342 if (scheme == null || scheme.length() < 2) {
343 throw new IllegalArgumentException("URL schemes must contain at least two characters");
344 }
345 if (allowedURLSchemes == null) {
346 allowedURLSchemes = new HashSet<>();
347 }
348 allowedURLSchemes.add(scheme.toLowerCase());
349 }
350
351 /**
352 * Completely override the list of ClassLoaders to scan. (This only works if overrideClasspath() is not called.)
353 * Causes the java.class.path system property to be ignored.
354 *
355 * @param overrideClassLoaders
356 * The classloaders to override the default context classloaders with.
357 */
358 public void overrideClassLoaders(final ClassLoader... overrideClassLoaders) {
359 if (overrideClassLoaders.length == 0) {
360 throw new IllegalArgumentException("At least one override ClassLoader must be provided");
361 }
362 this.addedClassLoaders = null;
363 this.overrideClassLoaders = new ArrayList<>();
364 for (final ClassLoader classLoader : overrideClassLoaders) {
365 if (classLoader != null) {
366 this.overrideClassLoaders.add(classLoader);
367 }
368 }
369 }
370
371 /**
372 * Return true if the argument is a ModuleLayer or a subclass of ModuleLayer.
373 *
374 * @param moduleLayer
375 * the module layer
376 * @return true if the argument is a ModuleLayer or a subclass of ModuleLayer.
377 */
378 private static boolean isModuleLayer(final Object moduleLayer) {
379 if (moduleLayer == null) {
380 throw new IllegalArgumentException("ModuleLayer references must not be null");
381 }
382 for (Class<?> currClass = moduleLayer.getClass(); currClass != null; currClass = currClass
383 .getSuperclass()) {
384 if (currClass.getName().equals("java.lang.ModuleLayer")) {
385 return true;
386 }
387 }
388 return false;
389 }
390
391 /**
392 * Add a ModuleLayer to the list of ModuleLayers to scan. Use this method if you define your own ModuleLayer,
393 * but the scanning code is not running within that custom ModuleLayer.
394 *
395 * <p>
396 * This call is ignored if it is called before {@link #overrideModuleLayers(Object...)}.
397 *
398 * @param moduleLayer
399 * The additional ModuleLayer to scan. (The parameter is of type {@link Object} for backwards
400 * compatibility with JDK 7 and JDK 8, but the argument should be of type ModuleLayer.)
401 */
402 public void addModuleLayer(final Object moduleLayer) {
403 if (!isModuleLayer(moduleLayer)) {
404 throw new IllegalArgumentException("moduleLayer must be of type java.lang.ModuleLayer");
405 }
406 if (this.addedModuleLayers == null) {
407 this.addedModuleLayers = new ArrayList<>();
408 }
409 this.addedModuleLayers.add(moduleLayer);
410 }
411
412 /**
413 * Completely override (and ignore) the visible ModuleLayers, and instead scan the requested ModuleLayers.
414 *
415 * <p>
416 * This call is ignored if overrideClasspath() is called.
417 *
418 * @param overrideModuleLayers
419 * The ModuleLayers to scan instead of the automatically-detected ModuleLayers. (The parameter is of
420 * type {@link Object}[] for backwards compatibility with JDK 7 and JDK 8, but the argument should be
421 * of type ModuleLayer[].)
422 */
423 public void overrideModuleLayers(final Object... overrideModuleLayers) {
424 if (overrideModuleLayers == null) {
425 throw new IllegalArgumentException("overrideModuleLayers cannot be null");
426 }
427 if (overrideModuleLayers.length == 0) {
428 throw new IllegalArgumentException("At least one override ModuleLayer must be provided");
429 }
430 for (final Object moduleLayer : overrideModuleLayers) {
431 if (!isModuleLayer(moduleLayer)) {
432 throw new IllegalArgumentException("moduleLayer must be of type java.lang.ModuleLayer");
433 }
434 }
435 this.addedModuleLayers = null;
436 this.overrideModuleLayers = new ArrayList<>();
437 Collections.addAll(this.overrideModuleLayers, overrideModuleLayers);
438 }
439
440 // -------------------------------------------------------------------------------------------------------------
441
442 /**
443 * Whether a path is a descendant of a blacklisted path, or an ancestor or descendant of a whitelisted path.
444 */
445 public enum ScanSpecPathMatch {
446 /** Path starts with (or is) a blacklisted path prefix. */
447 HAS_BLACKLISTED_PATH_PREFIX,
448 /** Path starts with a whitelisted path prefix. */
449 HAS_WHITELISTED_PATH_PREFIX,
450 /** Path is whitelisted. */
451 AT_WHITELISTED_PATH,
452 /** Path is an ancestor of a whitelisted path. */
453 ANCESTOR_OF_WHITELISTED_PATH,
454 /** Path is the package of a specifically-whitelisted class. */
455 AT_WHITELISTED_CLASS_PACKAGE,
456 /** Path is not whitelisted and not blacklisted. */
457 NOT_WITHIN_WHITELISTED_PATH
458 }
459
460 /**
461 * Returns true if the given directory path is a descendant of a blacklisted path, or an ancestor or descendant
462 * of a whitelisted path. The path should end in "/".
463 *
464 * @param relativePath
465 * the relative path
466 * @return the {@link ScanSpecPathMatch}
467 */
468 public ScanSpecPathMatch dirWhitelistMatchStatus(final String relativePath) {
469 // In blacklisted path
470 if (pathWhiteBlackList.isBlacklisted(relativePath)) {
471 // The directory is blacklisted.
472 return ScanSpecPathMatch.HAS_BLACKLISTED_PATH_PREFIX;
473 }
474 if (pathPrefixWhiteBlackList.isBlacklisted(relativePath)) {
475 // An prefix of this path is blacklisted.
476 return ScanSpecPathMatch.HAS_BLACKLISTED_PATH_PREFIX;
477 }
478
479 if (pathWhiteBlackList.whitelistIsEmpty() && classPackagePathWhiteBlackList.whitelistIsEmpty()) {
480 // If there are no whitelisted packages, the root package is whitelisted
481 return relativePath.isEmpty() || relativePath.equals("/") ? ScanSpecPathMatch.AT_WHITELISTED_PATH
482 : ScanSpecPathMatch.HAS_WHITELISTED_PATH_PREFIX;
483 }
484
485 // At whitelisted path
486 if (pathWhiteBlackList.isSpecificallyWhitelistedAndNotBlacklisted(relativePath)) {
487 // Reached a whitelisted path
488 return ScanSpecPathMatch.AT_WHITELISTED_PATH;
489 }
490 if (classPackagePathWhiteBlackList.isSpecificallyWhitelistedAndNotBlacklisted(relativePath)) {
491 // Reached a package containing a specifically-whitelisted class
492 return ScanSpecPathMatch.AT_WHITELISTED_CLASS_PACKAGE;
493 }
494
495 // Descendant of whitelisted path
496 if (pathPrefixWhiteBlackList.isSpecificallyWhitelisted(relativePath)) {
497 // Path prefix matches one in the whitelist
498 return ScanSpecPathMatch.HAS_WHITELISTED_PATH_PREFIX;
499 }
500
501 // Ancestor of whitelisted path
502 if (relativePath.equals("/")) {
503 // The default package is always the ancestor of whitelisted paths (need to keep recursing)
504 return ScanSpecPathMatch.ANCESTOR_OF_WHITELISTED_PATH;
505 }
506 if (pathWhiteBlackList.whitelistHasPrefix(relativePath)) {
507 // relativePath is an ancestor (prefix) of a whitelisted path
508 return ScanSpecPathMatch.ANCESTOR_OF_WHITELISTED_PATH;
509 }
510 if (classfilePathWhiteBlackList.whitelistHasPrefix(relativePath)) {
511 // relativePath is an ancestor (prefix) of a whitelisted class' parent directory
512 return ScanSpecPathMatch.ANCESTOR_OF_WHITELISTED_PATH;
513 }
514
515 // Not in whitelisted path
516 return ScanSpecPathMatch.NOT_WITHIN_WHITELISTED_PATH;
517 }
518
519 /**
520 * Returns true if the given relative path (for a classfile name, including ".class") matches a
521 * specifically-whitelisted (and non-blacklisted) classfile's relative path.
522 *
523 * @param relativePath
524 * the relative path
525 * @return true if the given relative path (for a classfile name, including ".class") matches a
526 * specifically-whitelisted (and non-blacklisted) classfile's relative path.
527 */
528 public boolean classfileIsSpecificallyWhitelisted(final String relativePath) {
529 return classfilePathWhiteBlackList.isSpecificallyWhitelistedAndNotBlacklisted(relativePath);
530 }
531
532 /**
533 * Returns true if the class is specifically blacklisted, or is within a blacklisted package.
534 *
535 * @param className
536 * the class name
537 * @return true if the class is specifically blacklisted, or is within a blacklisted package.
538 */
539 public boolean classOrPackageIsBlacklisted(final String className) {
540 return classWhiteBlackList.isBlacklisted(className) || packagePrefixWhiteBlackList.isBlacklisted(className);
541 }
542
543 // -------------------------------------------------------------------------------------------------------------
544
545 /**
546 * Write to log.
547 *
548 * @param log
549 * The {@link LogNode} to log to.
550 */
551 public void log(final LogNode log) {
552 if (log != null) {
553 final LogNode scanSpecLog = log.log("ScanSpec:");
554 for (final Field field : ScanSpec.class.getDeclaredFields()) {
555 try {
556 scanSpecLog.log(field.getName() + ": " + field.get(this));
557 } catch (final ReflectiveOperationException e) {
558 // Ignore
559 }
560 }
561 }
562 }
563 }
564