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