Mercurial > hg > release > icedtea6-1.4.1
view rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @ 1319:d95ddc227d01
2009-01-20 Lillian Angel <langel@redhat.com>
* rt/net/sourceforge/jnlp/DefaultLaunchHandler.java: Removed debug
lines.
* rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: Likewise.
* rt/net/sourceforge/jnlp/security/AccessWarningPane.java: Updated
imports.
* rt/net/sourceforge/jnlp/security/AppletWarningPane.java: Updated
imports.
* rt/net/sourceforge/jnlp/security/CertWarningPane.java: Updated
imports, added certVerifier global variable.
(CertWarningPane): Initialized certVerifier.
(installComponents): Added checks to determine if certificate is for
an https site, and set the name/publisher/from variables
appropriately. Also, customized warning pane label for https site.
* rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java:
(getDetails): Implemented.
(addToDetails): Likewise.
(R): Likewise.
(getPublisher): Likewise.
(getRoot): Likewise.
(getRootInCacerts): Likewise.
(hasSigningIssues): Likewise.
(noSigningIssues): Likewise.
* rt/net/sourceforge/jnlp/security/MoreInfoPane.java: Fixed imports.
* rt/net/sourceforge/jnlp/security/SecurityDialogUI.java: Fixed
imports.
* rt/net/sourceforge/jnlp/security/SecurityWarningDialog.java: Fixed
imports.
* rt/net/sourceforge/jnlp/security/SingleCertInfoPane.java: Fixed
imports.
* rt/net/sourceforge/jnlp/security/viewer/CertificatePane.java: Fixed
imports.
* rt/net/sourceforge/jnlp/tools/KeyTool.java: Removed debug lines.
* rt/net/sourceforge/jnlp/security/CertVerifier.java: Moved file
below, here.
* rt/net/sourceforge/jnlp/tools/CertVerifier.java: Removed.
* rt/net/sourceforge/jnlp/security/VariableX509TrustManager.java:
Removed debug lines.
author | Lillian Angel <langel@redhat.com> |
---|---|
date | Tue, 20 Jan 2009 18:37:40 -0500 |
parents | c63cc0eff68b |
children | 15437352b69c |
line wrap: on
line source
// // 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.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.AccessControlContext; import java.security.AccessController; 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.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.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:jmaxwell@users.sourceforge.net">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! /** number of times a classloader with native code is created */ private static int nativeCounter = 0; /** the directory for native code */ private File nativeDir = null; // if set, some native code exists /** 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(); /** * 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() { /** * 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, file.getCodeBase().getHost()); } else { this.security = new SecurityDesc(file, SecurityDesc.SANDBOX_PERMISSIONS, file.getCodeBase().getHost()); } } else { //regular jnlp file /** * If the application is signed, then we set the SecurityDesc to the * <security> tag in the jnlp file. Note that if an application is * signed, but there is no <security> tag in the jnlp file, the * application will get sandbox permissions. * If the application is unsigned, we ignore the <security> tag and * use a sandbox instead. */ if (signing == true) { this.security = file.getSecurity(); } else { this.security = new SecurityDesc(file, SecurityDesc.SANDBOX_PERMISSIONS, file.getCodeBase().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 loader = null; URL location = file.getFileLocation(); if (location != null) loader = (JNLPClassLoader) urlToLoader.get(location); try { if (loader == null) loader = new JNLPClassLoader(file, policy); } catch (LaunchException e) { throw e; } if (file.getInformation().isSharingAllowed()) urlToLoader.put(location, loader); return loader; } /** * Returns a JNLP classloader for the JNLP file at the specified * location. * * @param location the file's location * @param policy the update policy to use when downloading resources */ public static JNLPClassLoader getInstance(URL location, UpdatePolicy policy) throws IOException, ParseException, LaunchException { JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(location); if (loader == null) loader = getInstance(new JNLPFile(location, 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 { JNLPClassLoader loader = getInstance(ext[i].getLocation(), 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; //user does not trust this publisher if (!js.getAlreadyTrustPublisher()) { 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"), ""); } } 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 } } activateJars(initialJars); } /** * 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) { Enumeration e = security.getPermissions().elements(); while (e.hasMoreElements()) result.add((Permission) 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()) jarEntries.add(((JarEntry) e.nextElement()).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(); } if (jar.isNative()) activateNative(jar); } return null; } }; AccessController.doPrivileged(activate, acc); } /** * Enable the 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(); try { JarFile jarFile = new JarFile(localFile, false); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry e = (JarEntry) entries.nextElement(); if (e.isDirectory() || e.getName().indexOf('/') != -1) continue; File outFile = new File(nativeDir, e.getName()); 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 return nativeDir; } /** * Return the absolute path to the native library. */ protected String findLibrary(String lib) { if (nativeDir == null) return null; String syslib = System.mapLibraryName(lib); File target = new File(nativeDir, syslib); if (target.exists()) return target.toString(); else { 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) { } } 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; } }