diff options
Diffstat (limited to 'netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java')
-rw-r--r-- | netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java | 1198 |
1 files changed, 1198 insertions, 0 deletions
diff --git a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java new file mode 100644 index 0000000..972eb25 --- /dev/null +++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @@ -0,0 +1,1198 @@ + +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +package net.sourceforge.jnlp.runtime; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.AllPermission; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TreeSet; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import net.sourceforge.jnlp.ExtensionDesc; +import net.sourceforge.jnlp.JARDesc; +import net.sourceforge.jnlp.JNLPFile; +import net.sourceforge.jnlp.LaunchException; +import net.sourceforge.jnlp.ParseException; +import net.sourceforge.jnlp.PluginBridge; +import net.sourceforge.jnlp.ResourcesDesc; +import net.sourceforge.jnlp.SecurityDesc; +import net.sourceforge.jnlp.Version; +import net.sourceforge.jnlp.cache.CacheUtil; +import net.sourceforge.jnlp.cache.ResourceTracker; +import net.sourceforge.jnlp.cache.UpdatePolicy; +import net.sourceforge.jnlp.security.SecurityWarningDialog; +import net.sourceforge.jnlp.tools.JarSigner; +import sun.misc.JarIndex; + +/** + * Classloader that takes it's resources from a JNLP file. If the + * JNLP file defines extensions, separate classloaders for these + * will be created automatically. Classes are loaded with the + * security context when the classloader was created. + * + * @author <a href="mailto:[email protected]">Jon A. Maxwell (JAM)</a> - initial author + * @version $Revision: 1.20 $ + */ +public class JNLPClassLoader extends URLClassLoader { + + // todo: initializePermissions should get the permissions from + // extension classes too so that main file classes can load + // resources in an extension. + + /** shortcut for resources */ + private static String R(String key) { return JNLPRuntime.getMessage(key); } + + /** map from JNLPFile url to shared classloader */ + private static Map urlToLoader = new HashMap(); // never garbage collected! + + /** the directory for native code */ + private File nativeDir = null; // if set, some native code exists + + /** a list of directories that contain native libraries */ + private List<File> nativeDirectories = Collections.synchronizedList(new LinkedList<File>()); + + /** security context */ + private AccessControlContext acc = AccessController.getContext(); + + /** the permissions for the cached jar files */ + private List resourcePermissions; + + /** the app */ + private ApplicationInstance app = null; // here for faster lookup in security manager + + /** list of this, local and global loaders this loader uses */ + private JNLPClassLoader loaders[] = null; // ..[0]==this + + /** whether to strictly adhere to the spec or not */ + private boolean strict = true; + + /** loads the resources */ + private ResourceTracker tracker = new ResourceTracker(true); // prefetch + + /** the update policy for resources */ + private UpdatePolicy updatePolicy; + + /** the JNLP file */ + private JNLPFile file; + + /** the resources section */ + private ResourcesDesc resources; + + /** the security section */ + private SecurityDesc security; + + /** Permissions granted by the user during runtime. */ + private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>(); + + /** all jars not yet part of classloader or active */ + private List available = new ArrayList(); + + /** all of the jar files that were verified */ + private ArrayList<String> verifiedJars = null; + + /** all of the jar files that were not verified */ + private ArrayList<String> unverifiedJars = null; + + /** the jarsigner tool to verify our jars */ + private JarSigner js = null; + + private boolean signing = false; + + /** ArrayList containing jar indexes for various jars available to this classloader */ + private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>(); + + /** File entries in the jar files available to this classloader */ + private TreeSet jarEntries = new TreeSet(); + + /** Map of specific codesources to securitydesc */ + private HashMap<URL, SecurityDesc> jarLocationSecurityMap = new HashMap<URL, SecurityDesc>(); + + /** + * Create a new JNLPClassLoader from the specified file. + * + * @param file the JNLP file + */ + protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException { + super(new URL[0], JNLPClassLoader.class.getClassLoader()); + + if (JNLPRuntime.isDebug()) + System.out.println("New classloader: "+file.getFileLocation()); + + this.file = file; + this.updatePolicy = policy; + this.resources = file.getResources(); + + // initialize extensions + initializeExtensions(); + + // initialize permissions + initializePermissions(); + + initializeResources(); + + setSecurity(); + + } + + private void setSecurity() throws LaunchException { + + URL codebase = null; + + if (file.getCodeBase() != null) { + codebase = file.getCodeBase(); + } else { + //Fixme: codebase should be the codebase of the Main Jar not + //the location. Although, it still works in the current state. + codebase = file.getResources().getMainJAR().getLocation(); + } + + /** + * When we're trying to load an applet, file.getSecurity() will return + * null since there is no jnlp file to specify permissions. We + * determine security settings here, after trying to verify jars. + */ + if (file instanceof PluginBridge) { + if (signing == true) { + this.security = new SecurityDesc(file, + SecurityDesc.ALL_PERMISSIONS, + codebase.getHost()); + } else { + this.security = new SecurityDesc(file, + SecurityDesc.SANDBOX_PERMISSIONS, + codebase.getHost()); + } + } else { //regular jnlp file + + /* + * Various combinations of the jars being signed and <security> tags being + * present are possible. They are treated as follows + * + * Jars JNLP File Result + * + * Signed <security> Appropriate Permissions + * Signed no <security> Sandbox + * Unsigned <security> Error + * Unsigned no <security> Sandbox + * + */ + if (!file.getSecurity().getSecurityType().equals(SecurityDesc.SANDBOX_PERMISSIONS) && !signing) { + throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LUnsignedJarWithSecurity"), R("LUnsignedJarWithSecurityInfo")); + } + else if (signing == true) { + this.security = file.getSecurity(); + } else { + this.security = new SecurityDesc(file, + SecurityDesc.SANDBOX_PERMISSIONS, + codebase.getHost()); + } + } + } + + /** + * Returns a JNLP classloader for the specified JNLP file. + * + * @param file the file to load classes for + * @param policy the update policy to use when downloading resources + */ + public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException { + JNLPClassLoader baseLoader = null; + JNLPClassLoader loader = null; + String uniqueKey = file.getUniqueKey(); + + if (uniqueKey != null) + baseLoader = (JNLPClassLoader) urlToLoader.get(uniqueKey); + + try { + + // If base loader is null, or the baseloader's file and this + // file is different, initialize a new loader + if (baseLoader == null || + !baseLoader.getJNLPFile().getFileLocation().equals(file.getFileLocation())) { + + loader = new JNLPClassLoader(file, policy); + + // New loader init may have caused extentions to create a + // loader for this unique key. Check. + JNLPClassLoader extLoader = (JNLPClassLoader) urlToLoader.get(uniqueKey); + + if (extLoader != null && extLoader != loader) { + if (loader.signing && !extLoader.signing) + if (!SecurityWarningDialog.showNotAllSignedWarningDialog(file)) + throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LSignedAppJarUsingUnsignedJar"), R("LSignedAppJarUsingUnsignedJarInfo")); + + loader.merge(extLoader); + } + + // loader is now current + ext. But we also need to think of + // the baseLoader + if (baseLoader != null && baseLoader != loader) { + loader.merge(baseLoader); + } + + } else { + // if key is same and locations match, this is the loader we want + loader = baseLoader; + } + + } catch (LaunchException e) { + throw e; + } + + // loaders are mapped to a unique key. Only extensions and parent + // share a key, so it is safe to always share based on it + urlToLoader.put(uniqueKey, loader); + + return loader; + } + + /** + * Returns a JNLP classloader for the JNLP file at the specified + * location. + * + * @param location the file's location + * @param version the file's version + * @param policy the update policy to use when downloading resources + */ + public static JNLPClassLoader getInstance(URL location, String uniqueKey, Version version, UpdatePolicy policy) + throws IOException, ParseException, LaunchException { + JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(uniqueKey); + + if (loader == null || !location.equals(loader.getJNLPFile().getFileLocation())) + loader = getInstance(new JNLPFile(location, uniqueKey, version, false, policy), policy); + + return loader; + } + + /** + * Load the extensions specified in the JNLP file. + */ + void initializeExtensions() { + ExtensionDesc[] ext = resources.getExtensions(); + + List loaderList = new ArrayList(); + + loaderList.add(this); + + //if (ext != null) { + for (int i=0; i < ext.length; i++) { + try { + String uniqueKey = this.getJNLPFile().getUniqueKey(); + JNLPClassLoader loader = getInstance(ext[i].getLocation(), uniqueKey, ext[i].getVersion(), updatePolicy); + loaderList.add(loader); + } + catch (Exception ex) { + ex.printStackTrace(); + } + } + //} + + loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]); + } + + /** + * Make permission objects for the classpath. + */ + void initializePermissions() { + resourcePermissions = new ArrayList(); + + JARDesc jars[] = resources.getJARs(); + for (int i=0; i < jars.length; i++) { + Permission p = CacheUtil.getReadPermission(jars[i].getLocation(), + jars[i].getVersion()); + + if (JNLPRuntime.isDebug()) { + if (p == null) + System.out.println("Unable to add permission for " + jars[i].getLocation()); + else + System.out.println("Permission added: " + p.toString()); + } + if (p != null) + resourcePermissions.add(p); + } + } + + /** + * Load all of the JARs used in this JNLP file into the + * ResourceTracker for downloading. + */ + void initializeResources() throws LaunchException { + JARDesc jars[] = resources.getJARs(); + if (jars == null || jars.length == 0) + return; + /* + if (jars == null || jars.length == 0) { + throw new LaunchException(null, null, R("LSFatal"), + R("LCInit"), R("LFatalVerification"), "No jars!"); + } + */ + List initialJars = new ArrayList(); + + for (int i=0; i < jars.length; i++) { + + available.add(jars[i]); + + if (jars[i].isEager()) + initialJars.add(jars[i]); // regardless of part + + tracker.addResource(jars[i].getLocation(), + jars[i].getVersion(), + jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE + ); + } + + if (strict) + fillInPartJars(initialJars); // add in each initial part's lazy jars + + if (JNLPRuntime.isVerifying()) { + + JarSigner js; + waitForJars(initialJars); //download the jars first. + + try { + js = verifyJars(initialJars); + } catch (Exception e) { + //we caught an Exception from the JarSigner class. + //Note: one of these exceptions could be from not being able + //to read the cacerts or trusted.certs files. + e.printStackTrace(); + throw new LaunchException(null, null, R("LSFatal"), + R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo")); + } + + //Case when at least one jar has some signing + if (js.anyJarsSigned()){ + signing = true; + + if (!js.allJarsSigned() && + !SecurityWarningDialog.showNotAllSignedWarningDialog(file)) + throw new LaunchException(file, null, R("LSFatal"), R("LCClient"), R("LSignedAppJarUsingUnsignedJar"), R("LSignedAppJarUsingUnsignedJarInfo")); + + + //user does not trust this publisher + if (!js.getAlreadyTrustPublisher()) { + checkTrustWithUser(js); + } else { + /** + * If the user trusts this publisher (i.e. the publisher's certificate + * is in the user's trusted.certs file), we do not show any dialogs. + */ + } + } else { + + signing = false; + //otherwise this jar is simply unsigned -- make sure to ask + //for permission on certain actions + } + } + + for (JARDesc jarDesc: file.getResources().getJARs()) { + try { + URL location = tracker.getCacheFile(jarDesc.getLocation()).toURI().toURL(); + SecurityDesc jarSecurity = file.getSecurity(); + + if (file instanceof PluginBridge) { + + URL codebase = null; + + if (file.getCodeBase() != null) { + codebase = file.getCodeBase(); + } else { + //Fixme: codebase should be the codebase of the Main Jar not + //the location. Although, it still works in the current state. + codebase = file.getResources().getMainJAR().getLocation(); + } + + jarSecurity = new SecurityDesc(file, + SecurityDesc.ALL_PERMISSIONS, + codebase.getHost()); + } + + jarLocationSecurityMap.put(location, jarSecurity); + } catch (MalformedURLException mfe) { + System.err.println(mfe.getMessage()); + } + } + + activateJars(initialJars); + } + + private void checkTrustWithUser(JarSigner js) throws LaunchException { + if (!js.getRootInCacerts()) { //root cert is not in cacerts + boolean b = SecurityWarningDialog.showCertWarningDialog( + SecurityWarningDialog.AccessType.UNVERIFIED, file, js); + if (!b) + throw new LaunchException(null, null, R("LSFatal"), + R("LCLaunching"), R("LNotVerified"), ""); + } else if (js.getRootInCacerts()) { //root cert is in cacerts + boolean b = false; + if (js.noSigningIssues()) + b = SecurityWarningDialog.showCertWarningDialog( + SecurityWarningDialog.AccessType.VERIFIED, file, js); + else if (!js.noSigningIssues()) + b = SecurityWarningDialog.showCertWarningDialog( + SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js); + if (!b) + throw new LaunchException(null, null, R("LSFatal"), + R("LCLaunching"), R("LCancelOnUserRequest"), ""); + } + } + + /** + * Add applet's codebase URL. This allows compatibility with + * applets that load resources from their codebase instead of + * through JARs, but can slow down resource loading. Resources + * loaded from the codebase are not cached. + */ + public void enableCodeBase() { + addURL( file.getCodeBase() ); // nothing happens if called more that once? + } + + /** + * Sets the JNLP app this group is for; can only be called once. + */ + public void setApplication(ApplicationInstance app) { + if (this.app != null) { + if (JNLPRuntime.isDebug()) { + Exception ex = new IllegalStateException("Application can only be set once"); + ex.printStackTrace(); + } + return; + } + + this.app = app; + } + + /** + * Returns the JNLP app for this classloader + */ + public ApplicationInstance getApplication() { + return app; + } + + /** + * Returns the JNLP file the classloader was created from. + */ + public JNLPFile getJNLPFile() { + return file; + } + + /** + * Returns the permissions for the CodeSource. + */ + protected PermissionCollection getPermissions(CodeSource cs) { + Permissions result = new Permissions(); + + // should check for extensions or boot, automatically give all + // access w/o security dialog once we actually check certificates. + + // copy security permissions from SecurityDesc element + if (security != null) { + // Security desc. is used only to track security settings for the + // application. However, an application may comprise of multiple + // jars, and as such, security must be evaluated on a per jar basis. + + // set default perms + PermissionCollection permissions = security.getSandBoxPermissions(); + + // If more than default is needed: + // 1. Code must be signed + // 2. ALL or J2EE permissions must be requested (note: plugin requests ALL automatically) + if (cs.getCodeSigners() != null && + (getCodeSourceSecurity(cs.getLocation()).getSecurityType().equals(SecurityDesc.ALL_PERMISSIONS) || + getCodeSourceSecurity(cs.getLocation()).getSecurityType().equals(SecurityDesc.J2EE_PERMISSIONS)) + ) { + + permissions = getCodeSourceSecurity(cs.getLocation()).getPermissions(); + } + + Enumeration<Permission> e = permissions.elements(); + while (e.hasMoreElements()) + result.add(e.nextElement()); + } + + // add in permission to read the cached JAR files + for (int i=0; i < resourcePermissions.size(); i++) + result.add((Permission) resourcePermissions.get(i)); + + // add in the permissions that the user granted. + for (int i=0; i < runtimePermissions.size(); i++) + result.add(runtimePermissions.get(i)); + + return result; + } + + protected void addPermission(Permission p) { + runtimePermissions.add(p); + } + + /** + * Adds to the specified list of JARS any other JARs that need + * to be loaded at the same time as the JARs specified (ie, are + * in the same part). + */ + protected void fillInPartJars(List jars) { + for (int i=0; i < jars.size(); i++) { + String part = ((JARDesc) jars.get(i)).getPart(); + + for (int a=0; a < available.size(); a++) { + JARDesc jar = (JARDesc) available.get(a); + + if (part != null && part.equals(jar.getPart())) + if (!jars.contains(jar)) + jars.add(jar); + } + } + } + + /** + * Ensures that the list of jars have all been transferred, and + * makes them available to the classloader. If a jar contains + * native code, the libraries will be extracted and placed in + * the path. + * + * @param jars the list of jars to load + */ + protected void activateJars(final List jars) { + PrivilegedAction activate = new PrivilegedAction() { + + public Object run() { + // transfer the Jars + waitForJars(jars); + + for (int i=0; i < jars.size(); i++) { + JARDesc jar = (JARDesc) jars.get(i); + + available.remove(jar); + + // add jar + File localFile = tracker.getCacheFile(jar.getLocation()); + try { + URL location = jar.getLocation(); // non-cacheable, use source location + if (localFile != null) { + location = localFile.toURL(); // cached file + + // This is really not the best way.. but we need some way for + // PluginAppletViewer::getCachedImageRef() to check if the image + // is available locally, and it cannot use getResources() because + // that prefetches the resource, which confuses MediaTracker.waitForAll() + // which does a wait(), waiting for notification (presumably + // thrown after a resource is fetched). This bug manifests itself + // particularly when using The FileManager applet from Webmin. + + JarFile jarFile = new JarFile(localFile); + Enumeration e = jarFile.entries(); + while (e.hasMoreElements()) { + + JarEntry je = (JarEntry) e.nextElement(); + + // another jar in my jar? it is more likely than you think + if (je.getName().endsWith(".jar")) { + // We need to extract that jar so that it can be loaded + // (inline loading with "jar:..!/..." path will not work + // with standard classloader methods) + + String extractedJarLocation = localFile.getParent() + "/" + je.getName(); + File parentDir = new File(extractedJarLocation).getParentFile(); + if (!parentDir.isDirectory() && !parentDir.mkdirs()) { + throw new RuntimeException(R("RNestedJarExtration")); + } + FileOutputStream extractedJar = new FileOutputStream(extractedJarLocation); + InputStream is = jarFile.getInputStream(je); + + byte[] bytes = new byte[1024]; + int read = is.read(bytes); + int fileSize = read; + while (read > 0) { + extractedJar.write(bytes, 0, read); + read = is.read(bytes); + fileSize += read; + } + + is.close(); + extractedJar.close(); + + // 0 byte file? skip + if (fileSize <= 0) { + continue; + } + + JarSigner signer = new JarSigner(); + signer.verifyJar(extractedJarLocation); + + if (signer.anyJarsSigned() && !signer.getAlreadyTrustPublisher()) { + checkTrustWithUser(signer); + } + + try { + URL fileURL = new URL("file://" + extractedJarLocation); + addURL(fileURL); + + SecurityDesc jarSecurity = file.getSecurity(); + + if (file instanceof PluginBridge) { + + URL codebase = null; + + if (file.getCodeBase() != null) { + codebase = file.getCodeBase(); + } else { + //Fixme: codebase should be the codebase of the Main Jar not + //the location. Although, it still works in the current state. + codebase = file.getResources().getMainJAR().getLocation(); + } + + jarSecurity = new SecurityDesc(file, + SecurityDesc.ALL_PERMISSIONS, + codebase.getHost()); + } + + jarLocationSecurityMap.put(fileURL, jarSecurity); + + } catch (MalformedURLException mfue) { + if (JNLPRuntime.isDebug()) + System.err.println("Unable to add extracted nested jar to classpath"); + + mfue.printStackTrace(); + } + } + + jarEntries.add(je.getName()); + } + + } + + addURL(location); + + // there is currently no mechanism to cache files per + // instance.. so only index cached files + if (localFile != null) { + JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null); + + if (index != null) + jarIndexes.add(index); + } + + if (JNLPRuntime.isDebug()) + System.err.println("Activate jar: "+location); + } + catch (Exception ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + + // some programs place a native library in any jar + activateNative(jar); + } + + return null; + } + }; + + AccessController.doPrivileged(activate, acc); + } + + /** + * Search for and enable any native code contained in a JAR by copying the + * native files into the filesystem. Called in the security context of the + * classloader. + */ + protected void activateNative(JARDesc jar) { + if (JNLPRuntime.isDebug()) + System.out.println("Activate native: "+jar.getLocation()); + + File localFile = tracker.getCacheFile(jar.getLocation()); + if (localFile == null) + return; + + if (nativeDir == null) + nativeDir = getNativeDir(); + + String[] librarySuffixes = { ".so", ".dylib", ".jnilib", ".framework", ".dll" }; + + try { + JarFile jarFile = new JarFile(localFile, false); + Enumeration<JarEntry> entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry e = entries.nextElement(); + + if (e.isDirectory()) { + continue; + } + + String name = new File(e.getName()).getName(); + boolean isLibrary = false; + + for (String suffix: librarySuffixes) { + if (name.endsWith(suffix)) { + isLibrary = true; + break; + } + } + if (!isLibrary) { + continue; + } + + File outFile = new File(nativeDir, name); + + CacheUtil.streamCopy(jarFile.getInputStream(e), + new FileOutputStream(outFile)); + } + } + catch (IOException ex) { + if (JNLPRuntime.isDebug()) + ex.printStackTrace(); + } + } + + /** + * Return the base directory to store native code files in. + * This method does not need to return the same directory across + * calls. + */ + protected File getNativeDir() { + nativeDir = new File(System.getProperty("java.io.tmpdir") + + File.separator + "netx-native-" + + (new Random().nextInt() & 0xFFFF)); + + if (!nativeDir.mkdirs()) + return null; + else { + // add this new native directory to the search path + addNativeDirectory(nativeDir); + return nativeDir; + } + } + + /** + * Adds the {@link File} to the search path of this {@link JNLPClassLoader} + * when trying to find a native library + */ + protected void addNativeDirectory(File nativeDirectory) { + nativeDirectories.add(nativeDirectory); + } + + /** + * Returns a list of all directories in the search path of the current classloader + * when it tires to find a native library. + * @return a list of directories in the search path for native libraries + */ + protected List<File> getNativeDirectories() { + return nativeDirectories; + } + + /** + * Return the absolute path to the native library. + */ + protected String findLibrary(String lib) { + String syslib = System.mapLibraryName(lib); + + for (File dir: getNativeDirectories()) { + File target = new File(dir, syslib); + if (target.exists()) + return target.toString(); + } + + String result = super.findLibrary(lib); + if (result != null) + return result; + + return findLibraryExt(lib); + } + + /** + * Try to find the library path from another peer classloader. + */ + protected String findLibraryExt(String lib) { + for (int i=0; i < loaders.length; i++) { + String result = null; + + if (loaders[i] != this) + result = loaders[i].findLibrary(lib); + + if (result != null) + return result; + } + + return null; + } + + /** + * Wait for a group of JARs, and send download events if there + * is a download listener or display a progress window otherwise. + * + * @param jars the jars + */ + private void waitForJars(List jars) { + URL urls[] = new URL[jars.size()]; + + for (int i=0; i < jars.size(); i++) { + JARDesc jar = (JARDesc) jars.get(i); + + urls[i] = jar.getLocation(); + } + + CacheUtil.waitForResources(app, tracker, urls, file.getTitle()); + } + + /** + * Verifies code signing of jars to be used. + * + * @param jars the jars to be verified. + */ + private JarSigner verifyJars(List<JARDesc> jars) throws Exception { + + js = new JarSigner(); + js.verifyJars(jars, tracker); + return js; + } + + /** + * Find the loaded class in this loader or any of its extension loaders. + */ + protected Class findLoadedClassAll(String name) { + for (int i=0; i < loaders.length; i++) { + Class result = null; + + if (loaders[i] == this) + result = super.findLoadedClass(name); + else + result = loaders[i].findLoadedClassAll(name); + + if (result != null) + return result; + } + + return null; + } + + /** + * Find a JAR in the shared 'extension' classloaders, this + * classloader, or one of the classloaders for the JNLP file's + * extensions. + */ + public Class loadClass(String name) throws ClassNotFoundException { + + Class result = findLoadedClassAll(name); + + // try parent classloader + if (result == null) { + try { + ClassLoader parent = getParent(); + if (parent == null) + parent = ClassLoader.getSystemClassLoader(); + + return parent.loadClass(name); + } + catch (ClassNotFoundException ex) { } + } + + // filter out 'bad' package names like java, javax + // validPackage(name); + + // search this and the extension loaders + if (result == null) + try { + result = loadClassExt(name); + } catch (ClassNotFoundException cnfe) { + + // Not found in external loader either. As a last resort, look in any available indexes + + // Currently this loads jars directly from the site. We cannot cache it because this + // call is initiated from within the applet, which does not have disk read/write permissions + for (JarIndex index: jarIndexes) { + LinkedList<String> jarList = index.get(name.replace('.', '/')); + + if (jarList != null) { + for (String jarName: jarList) { + JARDesc desc; + try { + desc = new JARDesc(new URL(file.getCodeBase(), jarName), + null, null, false, true, false, true); + } catch (MalformedURLException mfe) { + throw new ClassNotFoundException(name); + } + + available.add(desc); + + tracker.addResource(desc.getLocation(), + desc.getVersion(), + JNLPRuntime.getDefaultUpdatePolicy() + ); + + URL remoteURL; + try { + remoteURL = new URL(file.getCodeBase() + jarName); + } catch (MalformedURLException mfe) { + throw new ClassNotFoundException(name); + } + + URL u; + + try { + u = tracker.getCacheURL(remoteURL); + } catch (Exception e) { + throw new ClassNotFoundException(name); + } + + if (u != null) + addURL(u); + + } + + // If it still fails, let it error out + result = loadClassExt(name); + } + } + } + + return result; + } + + /** + * Find the class in this loader or any of its extension loaders. + */ + protected Class findClass(String name) throws ClassNotFoundException { + for (int i=0; i < loaders.length; i++) { + try { + if (loaders[i] == this) + return super.findClass(name); + else + return loaders[i].findClass(name); + } + catch(ClassNotFoundException ex) { } + catch(ClassFormatError cfe) {} + } + + throw new ClassNotFoundException(name); + } + + /** + * Search for the class by incrementally adding resources to the + * classloader and its extension classloaders until the resource + * is found. + */ + private Class loadClassExt(String name) throws ClassNotFoundException { + // make recursive + addAvailable(); + + // find it + try { + return findClass(name); + } + catch(ClassNotFoundException ex) { + } + + // add resources until found + while (true) { + JNLPClassLoader addedTo = addNextResource(); + + if (addedTo == null) + throw new ClassNotFoundException(name); + + try { + return addedTo.findClass(name); + } + catch(ClassNotFoundException ex) { + } + } + } + + /** + * Finds the resource in this, the parent, or the extension + * class loaders. + */ + public URL getResource(String name) { + URL result = super.getResource(name); + + for (int i=1; i < loaders.length; i++) + if (result == null) + result = loaders[i].getResource(name); + + return result; + } + + /** + * Finds the resource in this, the parent, or the extension + * class loaders. + */ + public Enumeration findResources(String name) throws IOException { + Vector resources = new Vector(); + + for (int i=0; i < loaders.length; i++) { + Enumeration e; + + if (loaders[i] == this) + e = super.findResources(name); + else + e = loaders[i].findResources(name); + + while (e.hasMoreElements()) + resources.add(e.nextElement()); + } + + return resources.elements(); + } + + /** + * Returns if the specified resource is available locally from a cached jar + * + * @param s The name of the resource + * @return Whether or not the resource is available locally + */ + public boolean resourceAvailableLocally(String s) { + return jarEntries.contains(s); + } + + /** + * Adds whatever resources have already been downloaded in the + * background. + */ + protected void addAvailable() { + // go through available, check tracker for it and all of its + // part brothers being available immediately, add them. + + for (int i=1; i < loaders.length; i++) { + loaders[i].addAvailable(); + } + } + + /** + * Adds the next unused resource to the classloader. That + * resource and all those in the same part will be downloaded + * and added to the classloader before returning. If there are + * no more resources to add, the method returns immediately. + * + * @return the classloader that resources were added to, or null + */ + protected JNLPClassLoader addNextResource() { + if (available.size() == 0) { + for (int i=1; i < loaders.length; i++) { + JNLPClassLoader result = loaders[i].addNextResource(); + + if (result != null) + return result; + } + return null; + } + + // add jar + List jars = new ArrayList(); + jars.add(available.get(0)); + + fillInPartJars(jars); + + + activateJars(jars); + + return this; + } + + // this part compatibility with previous classloader + /** + * @deprecated + */ + public String getExtensionName() { + String result = file.getInformation().getTitle(); + + if (result == null) + result = file.getInformation().getDescription(); + if (result == null && file.getFileLocation() != null) + result = file.getFileLocation().toString(); + if (result == null && file.getCodeBase() != null) + result = file.getCodeBase().toString(); + + return result; + } + + /** + * @deprecated + */ + public String getExtensionHREF() { + return file.getFileLocation().toString(); + } + + public boolean getSigning() { + return signing; + } + + protected SecurityDesc getSecurity() { + return security; + } + + /** + * Returns the security descriptor for given code source URL + * + * @param source The code source + * @return The SecurityDescriptor for that source + */ + + protected SecurityDesc getCodeSourceSecurity(URL source) { + return jarLocationSecurityMap.get(source); + } + + /** + * Merges the code source/security descriptor mapping from another loader + * + * @param extLoader The loader form which to merge + * @throws SecurityException if the code is called from an untrusted source + */ + private void merge(JNLPClassLoader extLoader) { + + try { + System.getSecurityManager().checkPermission(new AllPermission()); + } catch (SecurityException se) { + throw new SecurityException("JNLPClassLoader() may only be called from trusted sources!"); + } + + // jars + for (URL u : extLoader.getURLs()) + addURL(u); + + // native search paths + for (File nativeDirectory: extLoader.getNativeDirectories()) + addNativeDirectory(nativeDirectory); + + // security descriptors + for (URL key: extLoader.jarLocationSecurityMap.keySet()) { + jarLocationSecurityMap.put(key, extLoader.jarLocationSecurityMap.get(key)); + } + } +} |