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-nullthis 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-nullthis 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 truedo not fetch paths from parent classloaders. */
225     public boolean ignoreParentClassLoaders;
226
227     /** If truedo 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