View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.util;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  
24  import org.apache.bcel.classfile.ClassParser;
25  import org.apache.bcel.classfile.JavaClass;
26  import org.apache.bcel.classfile.Utility;
27  
28  /**
29   * This abstract class provides a logic of a loading {@link JavaClass} objects class names via {@link ClassPath}.
30   *
31   * <p>
32   * Subclasses can choose caching strategy of the objects by implementing the abstract methods (for example,
33   * {@link #storeClass(JavaClass)} and {@link #findClass(String)}).
34   * </p>
35   *
36   * @since 6.4.0
37   */
38  abstract class AbstractClassPathRepository implements Repository {
39  
40      private final ClassPath classPath;
41  
42      AbstractClassPathRepository(final ClassPath classPath) {
43          this.classPath = classPath;
44      }
45  
46      @Override
47      public abstract void clear();
48  
49      @Override
50      public abstract JavaClass findClass(String className);
51  
52      @Override
53      public ClassPath getClassPath() {
54          return classPath;
55      }
56  
57      /**
58       * Finds the JavaClass object for a runtime Class object. If a class with the same name is already in this Repository,
59       * the Repository version is returned. Otherwise, getResourceAsStream() is called on the Class object to find the
60       * class's representation. If the representation is found, it is added to the Repository.
61       *
62       * @see Class
63       * @param clazz The runtime Class object.
64       * @return JavaClass object for given runtime class.
65       * @throws ClassNotFoundException Thrown if the class is not in the Repository, and its representation could not be found
66       */
67      @Override
68      public JavaClass loadClass(final Class<?> clazz) throws ClassNotFoundException {
69          final String className = clazz.getName();
70          final JavaClass repositoryClass = findClass(className);
71          if (repositoryClass != null) {
72              return repositoryClass;
73          }
74          String name = className;
75          final int i = name.lastIndexOf('.');
76          if (i > 0) {
77              name = name.substring(i + 1);
78          }
79  
80          try (InputStream clsStream = clazz.getResourceAsStream(name + JavaClass.EXTENSION)) {
81              return loadClass(clsStream, className);
82          } catch (final IOException e) {
83              return null;
84          }
85      }
86  
87      private JavaClass loadClass(final InputStream inputStream, final String className) throws ClassNotFoundException {
88          try {
89              if (inputStream != null) {
90                  final ClassParser parser = new ClassParser(inputStream, className);
91                  final JavaClass clazz = parser.parse();
92                  // The this_class name inside the parsed bytes is attacker-controlled: caching it under its own name
93                  // would let a class file found under one name poison the repository entry for another (the JDK's
94                  // defineClass() rejects the same mismatch). Refuse to cache or return it.
95                  if (!clazz.getClassName().equals(className)) {
96                      throw new ClassNotFoundException("Class name mismatch: requested " + className + " but the class file declares " + clazz.getClassName());
97                  }
98                  storeClass(clazz);
99                  return clazz;
100             }
101         } catch (final IOException e) {
102             throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
103         }
104         throw new ClassNotFoundException("ClassRepository could not load " + className);
105     }
106 
107     /**
108      * Finds a JavaClass object by name. If it is already in this Repository, the Repository version is returned. Otherwise,
109      * the Repository's classpath is searched for the class (and it is added to the Repository if found).
110      *
111      * @param className The name of the class.
112      * @return The JavaClass object.
113      * @throws ClassNotFoundException Thrown if the class is not in the Repository, and could not be found on the classpath
114      */
115     @Override
116     public JavaClass loadClass(String className) throws ClassNotFoundException {
117         if (className == null || className.isEmpty()) {
118             throw new IllegalArgumentException("Invalid class name " + className);
119         }
120         className = Utility.pathToPackage(className); // Just in case, canonical form
121         final JavaClass clazz = findClass(className);
122         if (clazz != null) {
123             return clazz;
124         }
125         try (InputStream inputStream = classPath.getInputStream(className)) {
126             return loadClass(inputStream, className);
127         } catch (final IOException e) {
128             throw new ClassNotFoundException("Exception while looking for class " + className + ": " + e, e);
129         }
130     }
131 
132     @Override
133     public abstract void removeClass(JavaClass javaClass);
134 
135     @Override
136     public abstract void storeClass(JavaClass javaClass);
137 }