001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.commons.lang; 020 021import java.io.ByteArrayInputStream; 022import java.io.Closeable; 023import java.io.FileNotFoundException; 024import java.io.IOException; 025import java.io.InputStream; 026import java.net.URL; 027import java.security.PublicKey; 028import java.util.zip.ZipEntry; 029import java.util.zip.ZipFile; 030 031import lucee.commons.digest.RSA; 032import lucee.commons.io.IOUtil; 033import lucee.commons.io.res.Resource; 034import lucee.commons.io.res.util.FileWrapper; 035import lucee.runtime.type.Sizeable; 036import lucee.transformer.util.AlreadyClassException; 037 038// TODO umbauen auf ZipInputStream oder ein wrapper schreiben fuer resorces der das file interface einhaelt 039 040 041public final class ArchiveClassLoader extends ClassLoader implements Sizeable,Closeable { 042 043 private final ZipFile zip; 044 private final ClassLoader pcl; 045 046 /** 047 * constructor of the class 048 * @param file 049 * @param parent 050 * @throws IOException 051 */ 052 public ArchiveClassLoader(Resource file, ClassLoader parent) throws IOException { 053 super(parent); 054 this.pcl=parent; 055 //this.file=file; 056 057 //print.ln("archive:"+file.getPath()); 058 if(!file.exists()) 059 throw new FileNotFoundException("file "+file.getAbsolutePath()+" doesn't exist"); 060 if(!file.isFile()) 061 throw new IOException(file.getAbsolutePath()+" is not a file"); 062 if(!file.isReadable()) 063 throw new IOException("no access to "+file.getAbsolutePath()+" file"); 064 065 066 this.zip=new ZipFile(FileWrapper.toFile(file)); 067 } 068 069 /** 070 * Loads the class with the specified name. This method searches for 071 * classes in the same manner as the {@link #loadClass(String, boolean)} 072 * method. It is called by the Java virtual machine to resolve class 073 * references. Calling this method is equivalent to calling 074 * <code>loadClass(name, false)</code>. 075 * 076 * @param name the name of the class 077 * @return the resulting <code>Class</code> object 078 * @exception ClassNotFoundException if the class was not found 079 */ 080 public Class loadClass(String name) throws ClassNotFoundException { 081 return loadClass(name, false); 082 } 083 084 /** 085 * Loads the class with the specified name. The default implementation of 086 * this method searches for classes in the following order:<p> 087 * 088 * <ol> 089 * <li> Call {@link #findLoadedClass(String)} to check if the class has 090 * already been loaded. <p> 091 * <li> Call the <code>loadClass</code> method on the parent class 092 * loader. If the parent is <code>null</code> the class loader 093 * built-in to the virtual machine is used, instead. <p> 094 * <li> Call the {@link #findClass(String)} method to find the class. <p> 095 * </ol> 096 * 097 * If the class was found using the above steps, and the 098 * <code>resolve</code> flag is true, this method will then call the 099 * {@link #resolveClass(Class)} method on the resulting class object. 100 * <p> 101 * From the Java 2 SDK, v1.2, subclasses of ClassLoader are 102 * encouraged to override 103 * {@link #findClass(String)}, rather than this method.<p> 104 * 105 * @param name the name of the class 106 * @param resolve if <code>true</code> then resolve the class 107 * @return the resulting <code>Class</code> object 108 * @exception ClassNotFoundException if the class could not be found 109 */ 110 protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 111 // First, check if the class has already been loaded 112 Class c = findLoadedClass(name); 113 if (c == null) { 114 c=findClassEL(name); 115 if(c==null) { 116 c =pcl.loadClass(name); 117 } 118 } 119 if (resolve) { 120 resolveClass(c); 121 } 122 return c; 123 } 124 125 @Override 126 protected Class findClass(String name) throws ClassNotFoundException { 127 Class clazz=findClassEL(name); 128 if(clazz!=null) return clazz; 129 throw new ClassNotFoundException("class "+name+" not found"); 130 } 131 132 private Class findClassEL(String name) { 133 byte[] barr = getBytes(name.replace('.','/').concat(".class")); 134 if(barr!=null) { 135 try { 136 barr = toRaw(barr); 137 return defineClass(name,barr,0,barr.length); 138 } 139 catch(Throwable t) { 140 ExceptionUtil.rethrowIfNecessary(t); 141 } 142 } 143 return null; 144 } 145 146 @Override 147 public InputStream getResourceAsStream(String name) { 148 InputStream is = super.getResourceAsStream(name); 149 if(is!=null) return is; 150 151 byte[] barr = toRaw(getBytes(name)); 152 if(barr!=null) return new ByteArrayInputStream(barr); 153 154 return null; 155 } 156 157 158 private byte[] toRaw(byte[] bytes) { 159 if(bytes==null || !ClassUtil.isEncryptedBytecode(bytes)) return bytes; 160 161 String str = System.getenv("PUBLIC_KEY"); 162 if(StringUtil.isEmpty(str,true)) str=System.getProperty("PUBLIC_KEY"); 163 if(StringUtil.isEmpty(str,true)) throw new RuntimeException("to decrypt encrypted bytecode, you need to set PUBLIC_KEY as system property or or enviroment variable"); 164 165 try { 166 PublicKey publicKey = RSA.toPublicKey(str); 167 // first 2 bytes are just a mask to detect encrypted code, so we need to set offset 2 168 bytes=RSA.decrypt(bytes, publicKey,2); 169 } 170 catch (Exception e) { 171 throw new RuntimeException(e); 172 } 173 return bytes; 174 } 175 176 177 @Override 178 public URL getResource(String name) { 179 return null; 180 } 181 182 183 private byte[] getBytes(String name) { 184 185 ZipEntry entry= zip.getEntry(name); 186 187 if (entry == null)return null; 188 189 int size= (int) entry.getSize(); 190 InputStream is=null; 191 try { 192 is= zip.getInputStream(entry); 193 byte[] data= new byte[size]; 194 int pos= 0; 195 while (pos < size) { 196 int n= is.read(data, pos, data.length - pos); 197 pos += n; 198 } 199 return data; 200 } 201 catch(IOException ioe) {} 202 finally { 203 IOUtil.closeEL(is); 204 } 205 return null; 206 } 207 208 @Override 209 public long sizeOf() { 210 return SizeOf.size(zip); 211 } 212 213 public void close() throws IOException { 214 zip.close(); 215 } 216}