org.wildfly.glow.DeploymentScanner Maven / Gradle / Ivy
The newest version!
/*
* JBoss, Home of Professional Open Source.
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.glow;
import aQute.bnd.classfile.Attribute;
import aQute.bnd.classfile.ClassFile;
import aQute.bnd.classfile.CodeAttribute;
import aQute.bnd.classfile.ConstantPool;
import aQute.bnd.classfile.FieldInfo;
import aQute.bnd.classfile.LocalVariableTableAttribute;
import aQute.bnd.classfile.LocalVariableTypeTableAttribute;
import aQute.bnd.classfile.SignatureAttribute;
import aQute.lib.io.ByteBufferDataInput;
import org.jboss.galleon.util.IoUtils;
import org.jboss.galleon.util.ZipUtils;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.JarIndexer;
import org.jboss.jandex.MethodInfo;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.wildfly.glow.DeploymentFileRuleInspector.ParsedRule;
import org.wildfly.glow.DeploymentFileRuleInspector.PatternOrValue;
import org.wildfly.glow.error.ErrorIdentificationSession;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.objectweb.asm.Opcodes.ASM9;
import org.wildfly.glow.error.ErrorLevel;
import org.wildfly.glow.error.IdentifiedError;
public class DeploymentScanner implements AutoCloseable {
private final Path binary;
private final Path tempDirectory;
private boolean verbose;
private final Set excludeArchivesFromScan;
private ArchiveType archiveType;
private DeploymentScanner parent;
private final boolean isArchive;
public DeploymentScanner(Path binary, boolean verbose, Set excludeArchivesFromScan) throws IOException {
this(null, binary, verbose, excludeArchivesFromScan);
}
private DeploymentScanner(DeploymentScanner parent, Path binary, boolean verbose, Set excludeArchivesFromScan) throws IOException {
this.parent = parent;
this.tempDirectory = parent == null ? Files.createTempDirectory("glow") : parent.tempDirectory;
this.verbose = verbose;
this.excludeArchivesFromScan = excludeArchivesFromScan;
if (!Files.exists(binary)) {
throw new IllegalArgumentException(binary.normalize().toAbsolutePath() + " is not an archive");
}
isArchive = !Files.isDirectory(binary);
FileNameParts fileNameParts = FileNameParts.parse(binary);
this.archiveType = fileNameParts.archiveType;
if (parent == null) {
this.binary = binary;
} else {
if (isArchive) {
// We need to copy the nested archive out of the containing archive
// The binary argument comes from the Jar filesystem, while the tempDirectory is in the default filesystem
this.binary = Files.createTempFile(tempDirectory, fileNameParts.coreName, fileNameParts.archiveType.suffix);
Files.delete(this.binary);
Files.copy(binary, this.binary);
} else {
this.binary = binary;
}
}
}
@Override
public void close() {
if (parent != null && binary != null) {
try {
if (isArchive) {
Files.delete(binary);
}
} catch (IOException ignore) {
}
}
if(parent == null) {
IoUtils.recursiveDelete(tempDirectory);
}
}
public void scan(LayerMapping mapping, Set layers, Map all, ErrorIdentificationSession errorSession) throws Exception {
Set discoveredLayers = new LinkedHashSet<>();
DeploymentScanContext ctx = new DeploymentScanContext(mapping, discoveredLayers, all, errorSession);
scan(ctx);
for (Layer l : discoveredLayers) {
if (!l.isBanned()) {
layers.add(l);
}
}
errorSession.collectEndOfScanErrors(verbose, ctx.resourceInjectionJndiInfos, ctx.contextLookupInfos, ctx.allClasses);
}
private void scan(DeploymentScanContext ctx) throws Exception {
scanAnnotations(ctx);
FileSystem fs = isArchive ? ZipUtils.newFileSystem(binary) : binary.getFileSystem();
try {
Path rootPath = isArchive ? fs.getPath("/") : binary;
scanTypesAndChildren(rootPath, ctx);
ctx.layers.addAll(inspectDeployment(rootPath, ctx));
} finally {
if (isArchive) {
fs.close();
}
}
}
private void scanAnnotations(DeploymentScanContext ctx) throws IOException {
Indexer indexer = new Indexer();
Index index = isArchive ? JarIndexer.createJarIndex(binary.toFile(),
indexer, false, true, false).getIndex()
: DirectoryIndexer.indexDirectory(binary.toFile(), indexer);
for (ClassInfo ci : index.getKnownClasses()) {
//System.out.println(ci.name());
for (AnnotationInstance ai : ci.annotations()) {
handleResourceInjectionAnnotations(ai, ctx);
//System.out.println(" " + ai.name().packagePrefix());
Set l = ctx.mapping.getAnnotations().get(ai.name().toString());
if (l != null) {
ctx.layers.addAll(l);
LayerMapping.addRule(LayerMapping.RULE.ANNOTATION,l, ai.name().toString());
//System.out.println("Find an annotation " + ai.name().toString() + " layer being " + l);
} else {
l = ctx.mapping.getAnnotations().get(ai.name().packagePrefix());
if (l != null) {
ctx.layers.addAll(l);
//System.out.println("Find an annotation " + ai.name().packagePrefix() + " layer being " + l);
LayerMapping.addRule(LayerMapping.RULE.ANNOTATION, l, ai.name().packagePrefix() + ".*");
} else {
// Pattern?
for (String s : ctx.mapping.getAnnotations().keySet()) {
if (Utils.isPattern(s)) {
Pattern p = Pattern.compile(s);
if (p.matcher(ai.name().toString()).matches()) {
Set layers = ctx.mapping.getAnnotations().get(s);
if (layers != null) {
LayerMapping.addRule(LayerMapping.RULE.ANNOTATION, layers, s);
ctx.layers.addAll(layers);
}
}
}
}
}
}
}
}
int i = binary.toFile().getName().lastIndexOf(".");
String ext = binary.toFile().getName().substring(i + 1);
String name = binary.toFile().getName().substring(0, i) + "-jandex";
Path parent = binary.getParent();
Path jd = parent == null ? Paths.get(name + "." + ext) : parent.resolve(name + "." + ext);
if (Files.exists(jd)) {
Files.delete(jd);
}
}
private void handleResourceInjectionAnnotations(AnnotationInstance annotationInstance, DeploymentScanContext ctx) {
if (annotationInstance.name().toString().equals("jakarta.annotation.Resource")) {
AnnotationTarget tgt = annotationInstance.target();
AnnotationValue typeFromAnnotation = annotationInstance.value("type");
String resourceClassName = null;
if (typeFromAnnotation != null) {
resourceClassName = typeFromAnnotation.asClass().toString();
} else {
if (tgt.kind() == AnnotationTarget.Kind.FIELD) {
resourceClassName = tgt.asField().type().toString();
} else if (tgt.kind() == AnnotationTarget.Kind.METHOD) {
MethodInfo mi = tgt.asMethod();
if (isSetter(mi)) {
resourceClassName = mi.parameterTypes().get(0).toString();
}
} else if (tgt.kind() == AnnotationTarget.Kind.CLASS) {
// We cannot infer a type and the default for the type value is Object
resourceClassName = Object.class.getName();
}
}
String injectionPoint = null;
if (tgt.kind() == AnnotationTarget.Kind.CLASS) {
injectionPoint = tgt.asClass().toString();
} else if (tgt.kind() == AnnotationTarget.Kind.FIELD) {
injectionPoint = tgt.asField().declaringClass().toString() + "." + tgt.asField().name();
} else if (tgt.kind() == AnnotationTarget.Kind.METHOD && isSetter(tgt.asMethod())) {
// For now, I don't want to include the parameters here since I didn't want to include the field type above
injectionPoint = tgt.asMethod().declaringClass().toString() + "." + tgt.asMethod().name() + "()";
}
Set resourceLayers = new HashSet<>();
String jndiName = findJndiName(annotationInstance);
if (jndiName != null) {
// A layer can bring the referenced resource.
Layer l = lookupJndi(jndiName, ctx);
if (l != null) {
resourceLayers.add(l);
}
}
if (resourceClassName != null) {
Set layer = lookup(resourceClassName, ctx);
if (layer != null) {
resourceLayers.addAll(layer);
}
ResourceInjectionJndiInfo info = new ResourceInjectionJndiInfo(resourceLayers, resourceClassName, injectionPoint, jndiName);
//System.out.println(info);
ctx.resourceInjectionJndiInfos.put(resourceClassName, info);
}
}
}
private boolean isSetter(MethodInfo methodInfo) {
return methodInfo.name().startsWith("set") && methodInfo.parameterTypes().size() == 1;
}
private String findJndiName(AnnotationInstance annotationInstance) {
String lookup = getAnnotationValue(annotationInstance, "mappedName");
if (lookup != null) {
return lookup;
}
lookup = getAnnotationValue(annotationInstance, "lookup");
if (lookup != null) {
return lookup;
}
return null;
}
private String getAnnotationValue(AnnotationInstance instance, String name) {
AnnotationValue value = instance.value(name);
if (value == null) {
return null;
}
return value.asString();
}
private void scanTypesAndChildren(Path archiveContentRoot, DeploymentScanContext ctx) throws Exception {
Files.walkFileTree(archiveContentRoot, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
new NestedWarOrExplodedArchiveFileVisitor(archiveContentRoot, isArchive) {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
String fileName = file.getFileName().toString();
if (fileName.endsWith(".class")) {
if (archiveType != ArchiveType.EAR) {
scanClass(file, ctx);
}
} else if (ArchiveType.isArchiveName(file)) {
Path relativeFile = archiveContentRoot.relativize(file);
if (archiveType.isValidArchiveLocation(relativeFile)) {
scanWithNestedScanner(file, ctx);
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
FileVisitResult result = super.preVisitDirectory(dir, attrs);
if (result == FileVisitResult.CONTINUE) {
return FileVisitResult.CONTINUE;
}
Path relativeFile = archiveContentRoot.relativize(dir);
if (archiveType.isValidArchiveLocation(relativeFile)) {
scanWithNestedScanner(dir, ctx);
}
return result;
}
});
}
private void scanWithNestedScanner(Path file, DeploymentScanContext ctx) throws IOException {
// Check it is not an excluded archive
for (Pattern exclude : excludeArchivesFromScan) {
if (exclude.matcher(file.getFileName().toString()).matches()) {
return;
}
}
try (DeploymentScanner nestedScanner = new DeploymentScanner(DeploymentScanner.this, file, verbose, excludeArchivesFromScan)) {
try {
nestedScanner.scan(ctx);
} catch (RuntimeException | IOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private void scanClass(Path file, DeploymentScanContext ctx) throws IOException {
byte[] content = Files.readAllBytes(file);
DataInput in = ByteBufferDataInput.wrap(content);
ClassFile clazz = ClassFile.parseClassFile(in);
ctx.allClasses.add(clazz.this_class.replaceAll("/", "."));
for (int i = 0; i < clazz.constant_pool.size(); i++) {
Object obj = clazz.constant_pool.entry(i);
if (obj instanceof ConstantPool.ClassInfo) {
ConstantPool.ClassInfo ci = (ConstantPool.ClassInfo) obj;
String className = (String) clazz.constant_pool.entry(ci.class_index);
//System.out.println(className);
className = className.replaceAll("/", ".");
lookup(className, ctx);
} else {
if (obj instanceof ConstantPool.FieldrefInfo) {
ConstantPool.FieldrefInfo info = (ConstantPool.FieldrefInfo) obj;
ConstantPool.NameAndTypeInfo ntinfo = (ConstantPool.NameAndTypeInfo) clazz.constant_pool.entry(info.name_and_type_index);
String className = formatClassName((String) clazz.constant_pool.entry(ntinfo.descriptor_index));
//System.out.println("CLASSNAME " + className);
lookup(className, ctx);
}
}
}
for (int i = 0; i < clazz.fields.length; i++) {
FieldInfo fi = clazz.fields[i];
String descriptor = formatClassName(fi.descriptor);
lookup(descriptor, ctx);
for (String type : extractClassesFromFieldSignatureAttribute(fi)) {
lookup(type, ctx);
}
}
for (int i = 0; i < clazz.methods.length; i++) {
aQute.bnd.classfile.MethodInfo mi = clazz.methods[i];
//System.out.println("Method descriptor " + mi.descriptor);
for (String type : parseMethodDescriptor(mi.descriptor)) {
lookup(type, ctx);
}
for (String type : extractTypeVariablesFromMethodSignatureAttribute(mi)) {
lookup(type, ctx);
}
for (String type : parseLocalVariableAndLocalVariableTypeTables(mi)) {
lookup(type, ctx);
}
for (String type : parseLocalVariableTypeTable(mi)) {
lookup(type, ctx);
}
}
lookForContextLookups(content, ctx);
}
private String trimArrayDimensionsFromDescriptor(String descriptor) {
//'[' at the start of the descriptor means it is an array. Trim those
for (int j = 0; j < descriptor.length(); j++) {
if (descriptor.charAt(j) != '[') {
if (j > 0) {
descriptor = descriptor.substring(j);
}
break;
}
}
return descriptor;
}
private String formatClassName(String className) {
className = trimArrayDimensionsFromDescriptor(className);
if (className.startsWith("L")) {
// class descriptor L;
className = className.substring(1, className.length() - 1);
}
className = className.replaceAll("/", ".");
return className;
}
Set parseMethodDescriptor(String descriptor) {
Set types = new HashSet<>();
StringBuilder builder = null;
for (char c : descriptor.toCharArray()) {
if (c == 'L') {
builder = new StringBuilder();
builder.append(c);
} else {
if (c == ';') {
builder.append(c);
types.add(formatClassName(builder.toString()));
builder = null;
} else {
if (builder != null) {
builder.append(c);
}
}
}
}
return types;
}
private Set extractTypeVariablesFromMethodSignatureAttribute(aQute.bnd.classfile.MethodInfo mi) {
String signature = getSignatureAttributeSignature(mi.attributes);
if (signature == null) {
return Collections.emptySet();
}
String[] parts = signature.split("\\(|\\)", 0);
List list = Arrays.stream(parts)
.map(v -> v.trim())
.filter(v -> v.length() > 0)
.map(v -> trimArrayDimensionsFromDescriptor(v))
.filter(v -> v.startsWith("L"))
.collect(Collectors.toList());
Set types = new HashSet<>();
for (String current : list) {
types.addAll(extractClassesSignatureForMethod(current));
}
return types;
}
private String getSignatureAttributeSignature(Attribute[] attributes) {
for (Attribute attribute : attributes) {
if (attribute instanceof SignatureAttribute) {
return ((SignatureAttribute) attribute).signature;
}
}
return null;
}
private Set extractClassesFromFieldSignatureAttribute(FieldInfo fieldInfo) {
String signature = getSignatureAttributeSignature(fieldInfo.attributes);
if (signature == null) {
return Collections.emptySet();
}
return extractClassesSignatureForField(signature);
}
private Set extractClassesSignatureForField(String signature) {
String[] parts = signature.split("<|>|,");
Set set =
Arrays.stream(parts)
.map(v -> v.trim())
.filter(v -> v.length() > 0)
.map(v -> formatClassName(v))
.collect(Collectors.toSet());
return set;
}
private Set extractClassesSignatureForMethod(String signature) {
String[] parts = signature.split("<|>|,|;");
Set types = new HashSet<>();
for (String part : parts) {
part = part.trim();
if (part.length() > 0) {
part = trimArrayDimensionsFromDescriptor(part);
if (part.startsWith("L")) {
// The regexp got rid of this and it is expected for formatClassName()
part = part + ";";
types.add(formatClassName(part));
}
}
}
return types;
}
private Set parseLocalVariableAndLocalVariableTypeTables(aQute.bnd.classfile.MethodInfo mi) {
Set types = new HashSet<>();
for (Attribute attr : mi.attributes) {
if (attr instanceof CodeAttribute) {
CodeAttribute codeAttribute = (CodeAttribute) attr;
for (Attribute attribute : codeAttribute.attributes) {
if (attribute instanceof LocalVariableTableAttribute) {
LocalVariableTableAttribute localVariableTableAttribute = (LocalVariableTableAttribute) attribute;
for (LocalVariableTableAttribute.LocalVariable lv : localVariableTableAttribute.local_variable_table) {
String desc = lv.descriptor;
types.add(formatClassName(desc));
}
} else if (attribute instanceof LocalVariableTypeTableAttribute) {
LocalVariableTypeTableAttribute localVariableTypeTableAttribute = (LocalVariableTypeTableAttribute) attribute;
for (LocalVariableTypeTableAttribute.LocalVariableType lv : localVariableTypeTableAttribute.local_variable_type_table) {
String desc = lv.signature;
types.addAll(extractClassesSignatureForMethod(desc));
}
}
}
}
}
return types;
}
private Set parseLocalVariableTypeTable(aQute.bnd.classfile.MethodInfo mi) {
Set types = new HashSet<>();
for (Attribute attr : mi.attributes) {
if (attr instanceof CodeAttribute) {
CodeAttribute codeAttribute = (CodeAttribute) attr;
for (Attribute attribute : codeAttribute.attributes) {
if (attribute instanceof LocalVariableTableAttribute) {
LocalVariableTableAttribute localVariableTableAttribute = (LocalVariableTableAttribute) attribute;
for (LocalVariableTableAttribute.LocalVariable lv : localVariableTableAttribute.local_variable_table) {
String desc = lv.descriptor;
types.add(formatClassName(desc));
}
}
}
}
}
return types;
}
private void lookForContextLookups(byte[] classBytes, DeploymentScanContext ctx) {
ClassReader cr = new ClassReader(classBytes);
ClassVisitor classVisitor = new ContextLookupClassVisitor(ctx);
cr.accept(classVisitor, 0);
}
private Layer lookupJndi(String jndiName, DeploymentScanContext ctx) {
Layer layer = null;
for (Layer l : ctx.allLayers.values()) {
// Datasources jndi name.
if (l.getBringDatasources().contains(jndiName)) {
layer = l;
// System.out.print("Layer " + l.getName() + " is included by JNDI name " + jndiName);
ctx.layers.add(l);
LayerMapping.addRule(LayerMapping.RULE.BRING_DATASOURCE, l, jndiName);
}
// TODO, add the rule to layers that bring a jndi resource (eg: mail).
}
return layer;
}
private Set lookup(String className, DeploymentScanContext ctx) {
Map> map = ctx.mapping.getConstantPoolClassInfos();
Set l = map.get(className);
if (l == null) {
int index = className.lastIndexOf(".");
if (index != -1) {
String pkgPrefix = className.substring(0, index);
l = map.get(pkgPrefix);
if (l != null) {
LayerMapping.addRule(LayerMapping.RULE.JAVA_TYPE,l, pkgPrefix + ".*");
}
}
if (l == null) {
// Pattern?
for (String s : map.keySet()) {
if (Utils.isPattern(s)) {
Pattern p = Pattern.compile(s);
if (p.matcher(className).matches()) {
l = map.get(s);
}
if (l != null) {
LayerMapping.addRule(LayerMapping.RULE.JAVA_TYPE,l, s);
}
}
}
}
} else {
LayerMapping.addRule(LayerMapping.RULE.JAVA_TYPE, l, className);
}
if (l != null) {
ctx.layers.addAll(l);
}
return l;
}
Set inspectDeployment(Path rootPath,
DeploymentScanContext ctx) throws Exception {
DeploymentFileRuleInspector inspector = new DeploymentFileRuleInspector(rootPath, isArchive);
Set set = new TreeSet<>();
for (String layer : ctx.allLayers.keySet()) {
Layer l = ctx.allLayers.get(layer);
for (String k : l.getProperties().keySet()) {
List matchingRule = new ArrayList<>();
matchingRule.add(Boolean.FALSE);
final boolean isCondition = LayerMapping.isCondition(k);
final Consumer consumer = isCondition ? (ll) -> {
matchingRule.set(0, Boolean.TRUE);
} : (ll) -> {
set.add(ll);
matchingRule.set(0, Boolean.TRUE);
};
final String originalKey = k;
k = LayerMapping.cleanupKey(k);
final String val = l.getProperties().get(originalKey);
if (k.startsWith(LayerMetadata.XML_PATH)) {
ParsedRule rule = inspector.extractParsedRule(val);
rule.iterateMatchedPaths((path, values) -> {
try {
Utils.applyXPath(path, values.get(0).getValue(), values.size() == 1 ? null : values.get(1).getValue(), consumer, l);
} catch(Exception ex) {
String id = "invalidXML" + path;
boolean allreadySet = false;
for (IdentifiedError err : ctx.errorSession.getErrors()) {
if (id.equals(err.getId())) {
allreadySet = true;
}
}
if (!allreadySet) {
ctx.errorSession.addError(new IdentifiedError(id, "Exception parsing " + path + ": " + ex, ErrorLevel.WARN));
}
}
});
} else if (k.startsWith(LayerMetadata.PROPERTIES_FILE_MATCH)) {
ParsedRule parsedRule = inspector.extractParsedRule(val);
parsedRule.iterateMatchedPaths((path, values) -> {
Properties props = new Properties();
try (InputStream reader = Files.newInputStream(path)) {
props.load(reader);
for (String prop : props.stringPropertyNames()) {
if (parsedRule.getValueParts().size() >= 1) {
PatternOrValue key = parsedRule.getValueParts().get(0);
// Check matches key
boolean match = key.equalsOrMatches(prop);
PatternOrValue value = null;
if (match && parsedRule.getValueParts().size() == 2) {
value = parsedRule.getValueParts().get(1);
if (value != null) {
match = value.equalsOrMatches(props.getProperty(prop));
}
}
if (match) {
consumer.accept(l);
LayerMapping.addRule(LayerMapping.RULE.PROPERTIES_FILE, l,
path.toString() + "==>" + prop + (value != null ? "==" + props.getProperty(prop) : ""));
}
}
}
}
});
} else if (k.startsWith(LayerMetadata.EXPECTED_FILE)) {
ParsedRule parsedRule = inspector.extractParsedRule(val);
parsedRule.iterateMatchedPaths((path, values) -> {
consumer.accept(l);
LayerMapping.addRule(LayerMapping.RULE.EXPECTED_FILE, l, path.toString());
});
} else if (k.startsWith(LayerMetadata.NOT_EXPECTED_FILE)) {
ParsedRule parsedRule = inspector.extractParsedRule(val);
List paths = parsedRule.getMatchedPaths();
if (paths.size() == 0) {
LayerMapping.addRule(LayerMapping.RULE.NOT_EXPECTED_FILE, l, val);
consumer.accept(l);
}
}
if (isCondition && matchingRule.get(0)) {
String condition = ctx.mapping.getNoConfigurationConditions().get(l);
if (originalKey.equals(condition)) {
//System.out.println("Remove all configurations from this layer");
l.getConfiguration().clear();
}
String hiddenCondition = ctx.mapping.getHiddenConditions().get(l);
if (originalKey.equals(hiddenCondition)) {
//System.out.println("condition " + originalKey + " makes layer " + l.getName() + " banned.");
l.setBanned(true);
}
}
}
}
ctx.errorSession.collectErrors(rootPath);
return set;
}
private String pathRelativeToRoot(String path) {
if (path.startsWith("/")) {
return path.substring(1);
}
return path;
}
private String adjustPatternInputRelativeToRoot(Path rootPath, String pathPattern) {
if (rootPath.toString().endsWith("/")) {
return rootPath + pathRelativeToRoot(pathPattern);
} else {
return rootPath + pathPattern;
}
}
private static class FileNameParts {
private final String coreName;
private final ArchiveType archiveType;
public FileNameParts(String coreName, ArchiveType archiveType) {
this.coreName = coreName;
this.archiveType = archiveType;
}
static FileNameParts parse(Path binary) {
String filename = binary.getFileName().toString();
int index = filename.lastIndexOf(".");
String suffix = filename.substring(index + 1);
String core = filename.substring(0, index);
return new FileNameParts(core, ArchiveType.parse(suffix));
}
}
enum ArchiveType {
EAR(".ear") {
@Override
public boolean isValidArchiveLocation(Path pathInArchive) {
// Accept all war and jar files no matter the location
return ArchiveType.isJar(pathInArchive) || ArchiveType.isWar(pathInArchive) || ArchiveType.isRar(pathInArchive) || ArchiveType.isSar(pathInArchive);
}
},
WAR(".war") {
@Override
public boolean isValidArchiveLocation(Path pathInArchive) {
// Only /WEB-INF/lib/*.jar is allowed
if (!ArchiveType.isJar(pathInArchive)) {
return false;
}
if (pathInArchive.getNameCount() != 3) {
return false;
}
if (!pathInArchive.getName(0).toString().equals("WEB-INF") || !pathInArchive.getName(1).toString().equals("lib")) {
return false;
}
return true;
}
},
JAR(".jar"),
RAR(".rar") {
@Override
public boolean isValidArchiveLocation(Path pathInArchive) {
return pathInArchive.getNameCount() == 1 && ArchiveType.isJar(pathInArchive);
}
},
SAR(".sar");
private final String suffix;
ArchiveType(String suffix) {
this.suffix = suffix;
}
public boolean isValidArchiveLocation(Path pathInArchive) {
return false;
}
static ArchiveType parse(String s) {
switch (s) {
case "ear":
return EAR;
case "war":
return WAR;
case "jar":
return JAR;
case "rar":
return RAR;
case "sar":
return SAR;
default:
throw new IllegalArgumentException(s);
}
}
private static boolean isJar(Path pathInArchive) {
return hasSuffix(pathInArchive, JAR.suffix);
}
private static boolean isWar(Path pathInArchive) {
return hasSuffix(pathInArchive, WAR.suffix);
}
private static boolean isRar(Path pathInArchive) {
return hasSuffix(pathInArchive, RAR.suffix);
}
private static boolean isSar(Path pathInArchive) {
return hasSuffix(pathInArchive, SAR.suffix);
}
private static boolean hasSuffix(Path pathInArchive, String suffix) {
return pathInArchive.getFileName().toString().endsWith(suffix);
}
static boolean isArchiveName(Path path) {
for (ArchiveType type : ArchiveType.values()) {
if (path.getFileName().toString().endsWith(type.suffix)) {
return true;
}
}
return false;
}
}
static class DeploymentScanContext {
private final LayerMapping mapping;
private final Set layers;
private final Map allLayers;
private final ErrorIdentificationSession errorSession;
private final Set allClasses = new HashSet<>();
private final Map resourceInjectionJndiInfos = new HashMap<>();
public Set contextLookupInfos = new HashSet<>();
private DeploymentScanContext(LayerMapping mapping, Set layers, Map allLayers, ErrorIdentificationSession errorSession) {
this.mapping = mapping;
this.layers = layers;
this.allLayers = allLayers;
this.errorSession = errorSession;
}
}
private class ContextLookupClassVisitor extends ClassVisitor {
private DeploymentScanContext ctx;
private String clazz;
public ContextLookupClassVisitor(DeploymentScanContext ctx) {
super(ASM9);
this.ctx = ctx;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
clazz = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public void visitEnd() {
clazz = null;
super.visitEnd();
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor visitor = new ContextLookupMethodVisitor(clazz, name, ctx);
return visitor;
}
}
private class ContextLookupMethodVisitor extends MethodVisitor {
private DeploymentScanContext ctx;
private String clazz;
private String method;
public ContextLookupMethodVisitor(String clazz, String method, DeploymentScanContext ctx) {
super(ASM9);
this.clazz = clazz;
this.method = method;
this.ctx = ctx;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (!"lookup".equals(name)) {
return;
}
if ("javax/naming/Context".equals(owner) || "javax/naming/InitialContext".equals(owner)) {
// Makes sure the naming layer gets added
String lookupClass = owner.replace('/', '.');
lookup(lookupClass, ctx);
String method = clazz.replace('/', '.') + "." + this.method + "()";
ctx.contextLookupInfos.add(new ContextLookupInfo(method));
}
}
}
}