/* Copyright 2012 Jan Ove Saltvedt This file is part of KBot. KBot is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. KBot is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with KBot. If not, see <http://www.gnu.org/licenses/>. */ package com.kbotpro.various; import com.kbotpro.scriptsystem.runnable.Script; import com.sun.org.apache.bcel.internal.classfile.ClassParser; import com.sun.org.apache.bcel.internal.classfile.JavaClass; import org.apache.log4j.Logger; import javax.sound.sampled.AudioPermission; import java.awt.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FilePermission; import java.io.IOException; import java.net.SocketPermission; import java.security.*; import java.security.cert.Certificate; import java.util.*; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.zip.ZipInputStream; /** * Created by IntelliJ IDEA. * User: Jan Ove Saltvedt * Date: Nov 3, 2009 * Time: 7:42:01 PM * To change this template use File | Settings | File Templates. */ public class ScriptClassLoader extends ClassLoader { private ZipInputStream file; private List<Permission> permissionExceptions; private HashMap<String, Class> classes = new HashMap<String, Class>(); private HashMap<String, Object[]> classData = new HashMap<String, Object[]>(); private Class mainClass; private ProtectionDomain protectionDomain; /** * Creates a new class loader using the <tt>ClassLoader</tt> returned by * the method {@link #getSystemClassLoader() * <tt>getSystemClassLoader()</tt>} as the parent class loader. * <p/> * <p> If there is a security manager, its {@link * SecurityManager#checkCreateClassLoader() * <tt>checkCreateClassLoader</tt>} method is invoked. This may result in * a security exception. </p> * * @param file JarFile * @param loadedFromServer * @param permissionExceptions * @throws SecurityException If a security manager exists and its * <tt>checkCreateClassLoader</tt> method doesn't allow creation * of a new class loader. */ public ScriptClassLoader(JarInputStream file, boolean loadedFromServer, List<Permission> permissionExceptions) { this.file = file; this.permissionExceptions = permissionExceptions; try { JarEntry entry = null; while ((entry = file.getNextJarEntry()) != null) { if (entry.getName().endsWith(".class")) { String name = entry.getName(); byte[] data; final int length = (int) entry.getSize(); if (length == -1) { final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int offset = 0; int ret; do { ret = file.read(buffer, 0, 1024); offset += ret; if (ret > 0) { byteArrayOutputStream.write(buffer, 0, ret); } } while (ret > 0); data = byteArrayOutputStream.toByteArray(); } else { int offset = 0; data = new byte[length]; int ret; do { ret = file.read(data, offset, length - offset); offset += ret; } while (ret > 0); } final JavaClass javaClass = new ClassParser(new ByteArrayInputStream(data), entry.getName().replaceAll(".class", "")).parse(); String shortName = name.replaceAll("\\.class", ""); byte[] bytes = javaClass.getBytes(); String validName = shortName.replaceAll("/", "."); validName = validName.replaceAll("\\\\", "."); classData.put(validName, new Object[]{validName, bytes}); } } } catch (IOException e) { Logger.getRootLogger().error("Exception: ", e); } for (Object[] data : this.classData.values()) { String name = (String) data[0]; if (classes.containsKey(name)) { continue; } byte[] bytes = (byte[]) data[1]; Class<?> klass = ourDefineClass(name, bytes, 0, bytes.length); if (Script.class.isAssignableFrom(klass)) { mainClass = klass; } if (com.kbot2.scriptable.Script.class.isAssignableFrom(klass)) { mainClass = klass; } classes.put(name, klass); } } public Class loadMainClass() { return mainClass; } /** * Finds the class with the specified <a href="#name">binary name</a>. * This method should be overridden by class loader implementations that * follow the delegation model for loading classes, and will be invoked by * the {@link #loadClass <tt>loadClass</tt>} method after checking the * parent class loader for the requested class. The default implementation * throws a <tt>ClassNotFoundException</tt>. </p> * * @param name The <a href="#name">binary name</a> of the class * @return The resulting <tt>Class</tt> object * @throws ClassNotFoundException If the class could not be found * @since 1.2 */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (classes.containsKey(name)) { return classes.get(name); } else if (classData.containsKey(name)) { Object[] data = classData.get(name); byte[] bytes = (byte[]) data[1]; Class<?> aClass = ourDefineClass(name, bytes, 0, bytes.length); if (Script.class.isAssignableFrom(aClass)) { mainClass = aClass; } if (com.kbot2.scriptable.Script.class.isAssignableFrom(aClass)) { mainClass = aClass; } classes.put(name, aClass); return aClass; } return super.findClass(name); //To change body of overridden methods use File | Settings | File Templates. } private Class<?> ourDefineClass(String name, byte[] b, int off, int len) { ProtectionDomain domain = getProtectionDomain(); return defineClass(name, b, off, len, domain); } public Class[] getAllLoadedClasses() { return classes.values().toArray(new Class[classes.size()]); } private ProtectionDomain getProtectionDomain() { if (protectionDomain == null) { CodeSource codeSource = new CodeSource(null, (Certificate[]) null); PermissionCollection permissionCollection = createPermissions();/*new OurPermissionCollection();*//*new Permissions(); permissionCollection.add(new AllPermission()); permissionCollection.add(new ScriptPermission()); permissionCollection.setReadOnly(); */ protectionDomain = new ProtectionDomain(codeSource, permissionCollection); } return protectionDomain; } private PermissionCollection createPermissions() { PermissionCollection p = new Permissions(); p.add(new PropertyPermission("*", "read")); p.add(new FilePermission("-", "read,write,delete")); p.add(new SocketPermission("services.runescape.com:80", "connect,resolve")); p.add(new SocketPermission("*.runescape.com:80", "connect,resolve")); p.add(new SocketPermission("runescape.com:80", "connect,resolve")); p.add(new SocketPermission("pastie.org:80", "connect,resolve")); p.add(new RuntimePermission("getClassLoader")); p.add(new RuntimePermission("stopThread")); p.add(new RuntimePermission("modifyThreadGroup")); p.add(new RuntimePermission("modifyThread")); p.add(new RuntimePermission("getClassLoader")); p.add(new RuntimePermission("getStackTrace")); p.add(new AudioPermission("play")); p.add(new AWTPermission("accessClipboard")); p.add(new AWTPermission("accessEventQueue")); p.add(new AWTPermission("setWindowAlwaysOnTop")); p.add(new AWTPermission("showWindowWithoutWarningBanner")); for(Permission permission: permissionExceptions){ p.add(permission); } return p; } }