-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #9511 Verify location of scanned class files #10777
Changes from all commits
f3e578b
49f2dfe
3ca2bce
0f4249e
f20425f
3bdff12
2a832fd
de7498a
90463ad
02e5ed7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.StringTokenizer; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
import org.eclipse.jetty.util.ExceptionUtil; | ||
|
@@ -452,16 +453,18 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) | |
public static class MyClassVisitor extends ClassVisitor | ||
{ | ||
final int _asmVersion; | ||
final Resource _containingResource; | ||
final Resource _containingResource; //resource containing the class to parse | ||
final Resource _classResource; //resource of the class being parsed | ||
final Set<? extends Handler> _handlers; | ||
ClassInfo _ci; | ||
|
||
public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource, int asmVersion) | ||
public MyClassVisitor(Set<? extends Handler> handlers, Resource containingResource, Resource classResource, int asmVersion) | ||
{ | ||
super(asmVersion); | ||
_asmVersion = asmVersion; | ||
_handlers = handlers; | ||
_containingResource = containingResource; | ||
_classResource = classResource; | ||
} | ||
|
||
@Override | ||
|
@@ -472,6 +475,14 @@ public void visit(final int version, | |
final String superName, | ||
final String[] interfaces) | ||
{ | ||
//Check that the named class exists in the containingResource at the correct location. | ||
//eg given the class with name "com.foo.Acme" and the containingResource "jar:file://some/place/something.jar!/" | ||
//then the file "jar:file://some/place/something.jar!/com/foo/Acme.class" must exist. | ||
if (_containingResource != null && !checkClassContainment(_containingResource, _classResource, name)) | ||
{ | ||
throw new IllegalStateException("Class " + name + " not in correct location in " + _containingResource); | ||
} | ||
|
||
_ci = new ClassInfo(_containingResource, normalize(name), version, access, signature, normalize(superName), normalize(interfaces)); | ||
for (Handler h : _handlers) | ||
{ | ||
|
@@ -520,6 +531,44 @@ public FieldVisitor visitField(final int access, | |
} | ||
} | ||
|
||
/** | ||
* Check whether the classname is located inside its containingResource at | ||
* the location that matches its package. The comparison accounts for classes | ||
* located within WEB-INF/classes. | ||
* @param containingResource the Resource that contains the class | ||
* @param classResource the Resource representing the class | ||
* @param classname the package-qualified name of the class | ||
* @return true if the containingResource contains a class file at the location matching the package name of the class | ||
*/ | ||
public static boolean checkClassContainment(Resource containingResource, Resource classResource, String classname) | ||
{ | ||
if (containingResource == null || classResource == null) | ||
return false; | ||
Path relative = containingResource.getPathTo(classResource); | ||
if (relative == null) | ||
return false; //unable to be verified | ||
|
||
StringTokenizer tokenizer = new StringTokenizer(classname, "/", false); | ||
|
||
for (int i = 0; i < relative.getNameCount(); i++) | ||
{ | ||
String s = relative.getName(i).toString(); | ||
if (i == 0 && "WEB-INF".equalsIgnoreCase(s)) | ||
continue; | ||
if (i == 1 && "classes".equalsIgnoreCase(s)) | ||
continue; | ||
if (i == relative.getNameCount() - 1) | ||
{ | ||
if ("class".equals(FileID.getExtension(s))) | ||
s = s.substring(0, s.length() - 6); | ||
} | ||
Comment on lines
+560
to
+564
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A comment saying what this is doing would be good. something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be changed to ... // strip class extensions
if (FileID.isExtension(s, "class"))
s = s.substring(0, s.length() - 6) As that will verify the extension in a case insensitive way. |
||
|
||
if (!tokenizer.nextToken().equalsIgnoreCase(s)) | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
public AnnotationParser() | ||
{ | ||
this(ASM_VERSION); | ||
|
@@ -564,7 +613,7 @@ public void parse(final Set<? extends Handler> handlers, Resource r) throws Exce | |
|
||
if (FileID.isClassFile(r.getPath())) | ||
{ | ||
parseClass(handlers, null, r.getPath()); | ||
parseClass(handlers, null, r); | ||
} | ||
|
||
if (LOG.isDebugEnabled()) | ||
|
@@ -606,7 +655,7 @@ protected void parseDir(Set<? extends Handler> handlers, Resource dirResource) t | |
|
||
try | ||
{ | ||
parseClass(handlers, dirResource, candidate.getPath()); | ||
parseClass(handlers, dirResource, candidate); | ||
} | ||
catch (Exception ex) | ||
{ | ||
|
@@ -647,29 +696,28 @@ protected void parseJar(Set<? extends Handler> handlers, Resource jarResource) t | |
* | ||
* @param handlers the handlers to look for classes in | ||
* @param containingResource the dir or jar that the class is contained within, can be null if not known | ||
* @param classFile the class file to parse | ||
* @param classResource the class file to parse | ||
* @throws IOException if unable to parse | ||
*/ | ||
protected void parseClass(Set<? extends Handler> handlers, Resource containingResource, Path classFile) throws IOException | ||
protected void parseClass(Set<? extends Handler> handlers, Resource containingResource, Resource classResource) throws IOException | ||
{ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("Parse class from {}", classFile.toUri()); | ||
|
||
URI location = classFile.toUri(); | ||
LOG.debug("Parse class from {}", classResource); | ||
|
||
try (InputStream in = Files.newInputStream(classFile)) | ||
try (InputStream in = Files.newInputStream(classResource.getPath())) | ||
{ | ||
ClassReader reader = new ClassReader(in); | ||
reader.accept(new MyClassVisitor(handlers, containingResource, _asmVersion), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); | ||
reader.accept(new MyClassVisitor(handlers, containingResource, classResource, _asmVersion), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); | ||
|
||
String classname = normalize(reader.getClassName()); | ||
URI location = classResource.getPath().toUri(); | ||
URI existing = _parsedClassNames.putIfAbsent(classname, location); | ||
if (existing != null) | ||
LOG.warn("{} scanned from multiple locations: {}, {}", classname, existing, location); | ||
} | ||
catch (IllegalArgumentException | IOException e) | ||
{ | ||
throw new IOException("Unable to parse class: " + classFile.toUri(), e); | ||
throw new IOException("Unable to parse class: " + classResource.getURI(), e); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the classname have '.' separators rather than '/'?
If it has '/' separators, then maybe we should split it with Path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The classname when referenced via a resource has slashes.