001package cpw.mods.fml.relauncher;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.io.InputStream;
006import java.net.JarURLConnection;
007import java.net.URL;
008import java.net.URLClassLoader;
009import java.net.URLConnection;
010import java.security.CodeSigner;
011import java.security.CodeSource;
012import java.util.ArrayList;
013import java.util.Arrays;
014import java.util.Collections;
015import java.util.HashMap;
016import java.util.HashSet;
017import java.util.List;
018import java.util.Locale;
019import java.util.Map;
020import java.util.Set;
021import java.util.jar.Attributes.Name;
022import java.util.jar.Attributes;
023import java.util.jar.JarEntry;
024import java.util.jar.JarFile;
025import java.util.jar.Manifest;
026import java.util.logging.Level;
027
028import cpw.mods.fml.common.FMLLog;
029
030public class RelaunchClassLoader extends URLClassLoader
031{
032    private List<URL> sources;
033    private ClassLoader parent;
034
035    private List<IClassTransformer> transformers;
036    private Map<String, Class> cachedClasses;
037    private Set<String> invalidClasses;
038
039    private Set<String> classLoaderExceptions = new HashSet<String>();
040    private Set<String> transformerExceptions = new HashSet<String>();
041    private Map<Package,Manifest> packageManifests = new HashMap<Package,Manifest>();
042
043    private static Manifest EMPTY = new Manifest();
044
045    private static final String[] RESERVED = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
046
047    private static final boolean DEBUG_CLASSLOADING = Boolean.parseBoolean(System.getProperty("fml.debugClassLoading", "false"));
048
049    public RelaunchClassLoader(URL[] sources)
050    {
051        super(sources, null);
052        this.sources = new ArrayList<URL>(Arrays.asList(sources));
053        this.parent = getClass().getClassLoader();
054        this.cachedClasses = new HashMap<String,Class>(1000);
055        this.invalidClasses = new HashSet<String>(1000);
056        this.transformers = new ArrayList<IClassTransformer>(2);
057//        ReflectionHelper.setPrivateValue(ClassLoader.class, null, this, "scl");
058        Thread.currentThread().setContextClassLoader(this);
059
060        // standard classloader exclusions
061        addClassLoaderExclusion("java.");
062        addClassLoaderExclusion("sun.");
063        addClassLoaderExclusion("org.lwjgl.");
064        addClassLoaderExclusion("cpw.mods.fml.relauncher.");
065        addClassLoaderExclusion("net.minecraftforge.classloading.");
066
067        // standard transformer exclusions
068        addTransformerExclusion("javax.");
069        addTransformerExclusion("org.objectweb.asm.");
070        addTransformerExclusion("com.google.common.");
071    }
072
073    public void registerTransformer(String transformerClassName)
074    {
075        try
076        {
077            transformers.add((IClassTransformer) loadClass(transformerClassName).newInstance());
078        }
079        catch (Exception e)
080        {
081            FMLRelaunchLog.log(Level.SEVERE, e, "A critical problem occured registering the ASM transformer class %s", transformerClassName);
082        }
083    }
084    @Override
085    public Class<?> findClass(String name) throws ClassNotFoundException
086    {
087        if (invalidClasses.contains(name))
088        {
089            throw new ClassNotFoundException(name);
090        }
091        for (String st : classLoaderExceptions)
092        {
093            if (name.startsWith(st))
094            {
095                return parent.loadClass(name);
096            }
097        }
098
099        if (cachedClasses.containsKey(name))
100        {
101            return cachedClasses.get(name);
102        }
103
104        for (String st : transformerExceptions)
105        {
106            if (name.startsWith(st))
107            {
108                try
109                {
110                    Class<?> cl = super.findClass(name);
111                    cachedClasses.put(name, cl);
112                    return cl;
113                }
114                catch (ClassNotFoundException e)
115                {
116                    invalidClasses.add(name);
117                    throw e;
118                }
119            }
120        }
121
122        try
123        {
124            CodeSigner[] signers = null;
125            int lastDot = name.lastIndexOf('.');
126            String pkgname = lastDot == -1 ? "" : name.substring(0, lastDot);
127            String fName = name.replace('.', '/').concat(".class");
128            String pkgPath = pkgname.replace('.', '/');
129            URLConnection urlConnection = findCodeSourceConnectionFor(fName);
130            if (urlConnection instanceof JarURLConnection && lastDot > -1)
131            {
132                JarURLConnection jarUrlConn = (JarURLConnection)urlConnection;
133                JarFile jf = jarUrlConn.getJarFile();
134                if (jf != null && jf.getManifest() != null)
135                {
136                    Manifest mf = jf.getManifest();
137                    JarEntry ent = jf.getJarEntry(fName);
138                    Package pkg = getPackage(pkgname);
139                    getClassBytes(name);
140                    signers = ent.getCodeSigners();
141                    if (pkg == null)
142                    {
143                        pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL());
144                        packageManifests.put(pkg, mf);
145                    }
146                    else
147                    {
148                        if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL()))
149                        {
150                            FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname);
151                        }
152                        else if (isSealed(pkgname, mf))
153                        {
154                            FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname);
155                        }
156                    }
157                }
158            }
159            else if (lastDot > -1)
160            {
161                Package pkg = getPackage(pkgname);
162                if (pkg == null)
163                {
164                    pkg = definePackage(pkgname, null, null, null, null, null, null, null);
165                    packageManifests.put(pkg, EMPTY);
166                }
167                else if (pkg.isSealed())
168                {
169                    FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname);
170                }
171            }
172            byte[] basicClass = getClassBytes(name);
173            byte[] transformedClass = runTransformers(name, basicClass);
174            Class<?> cl = defineClass(name, transformedClass, 0, transformedClass.length, new CodeSource(urlConnection.getURL(), signers));
175            cachedClasses.put(name, cl);
176            return cl;
177        }
178        catch (Throwable e)
179        {
180            invalidClasses.add(name);
181            if (DEBUG_CLASSLOADING)
182            {
183                FMLLog.log(Level.FINEST, e, "Exception encountered attempting classloading of %s", name);
184            }
185            throw new ClassNotFoundException(name, e);
186        }
187    }
188
189    private boolean isSealed(String path, Manifest man)
190    {
191        Attributes attr = man.getAttributes(path);
192        String sealed = null;
193        if (attr != null) {
194            sealed = attr.getValue(Name.SEALED);
195        }
196        if (sealed == null) {
197            if ((attr = man.getMainAttributes()) != null) {
198                sealed = attr.getValue(Name.SEALED);
199            }
200        }
201        return "true".equalsIgnoreCase(sealed);
202    }
203
204    private URLConnection findCodeSourceConnectionFor(String name)
205    {
206        URL res = findResource(name);
207        if (res != null)
208        {
209            try
210            {
211                return res.openConnection();
212            }
213            catch (IOException e)
214            {
215                throw new RuntimeException(e);
216            }
217        }
218        else
219        {
220            return null;
221        }
222    }
223
224    private byte[] runTransformers(String name, byte[] basicClass)
225    {
226        for (IClassTransformer transformer : transformers)
227        {
228            basicClass = transformer.transform(name, basicClass);
229        }
230        return basicClass;
231    }
232
233    @Override
234    public void addURL(URL url)
235    {
236        super.addURL(url);
237        sources.add(url);
238    }
239
240    public List<URL> getSources()
241    {
242        return sources;
243    }
244
245
246    private byte[] readFully(InputStream stream)
247    {
248        try
249        {
250            ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available());
251            int r;
252            while ((r = stream.read()) != -1)
253            {
254                bos.write(r);
255            }
256
257            return bos.toByteArray();
258        }
259        catch (Throwable t)
260        {
261            FMLLog.log(Level.WARNING, t, "Problem loading class");
262            return new byte[0];
263        }
264    }
265
266    public List<IClassTransformer> getTransformers()
267    {
268        return Collections.unmodifiableList(transformers);
269    }
270
271    private void addClassLoaderExclusion(String toExclude)
272    {
273        classLoaderExceptions.add(toExclude);
274    }
275
276    void addTransformerExclusion(String toExclude)
277    {
278        transformerExceptions.add(toExclude);
279    }
280
281    public byte[] getClassBytes(String name) throws IOException
282    {
283        if (name.indexOf('.') == -1)
284        {
285            for (String res : RESERVED)
286            {
287                if (name.toUpperCase(Locale.ENGLISH).startsWith(res))
288                {
289                    byte[] data = getClassBytes("_" + name);
290                    if (data != null)
291                    {
292                        return data;
293                    }
294                }
295            }
296        }
297
298        InputStream classStream = null;
299        try
300        {
301            URL classResource = findResource(name.replace('.', '/').concat(".class"));
302            if (classResource == null)
303            {
304                if (DEBUG_CLASSLOADING)
305                {
306                    FMLLog.finest("Failed to find class resource %s", name.replace('.', '/').concat(".class"));
307                }
308                return null;
309            }
310            classStream = classResource.openStream();
311            if (DEBUG_CLASSLOADING)
312            {
313                FMLLog.finest("Loading class %s from resource %s", name, classResource.toString());
314            }
315            return readFully(classStream);
316        }
317        finally
318        {
319            if (classStream != null)
320            {
321                try
322                {
323                    classStream.close();
324                }
325                catch (IOException e)
326                {
327                    // Swallow the close exception
328                }
329            }
330        }
331    }
332}