|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.applet; |
|
|
|
import java.lang.NullPointerException; |
|
import java.net.URL; |
|
import java.net.URLClassLoader; |
|
import java.net.SocketPermission; |
|
import java.net.URLConnection; |
|
import java.net.MalformedURLException; |
|
import java.net.InetAddress; |
|
import java.net.UnknownHostException; |
|
import java.io.EOFException; |
|
import java.io.File; |
|
import java.io.FilePermission; |
|
import java.io.IOException; |
|
import java.io.BufferedInputStream; |
|
import java.io.InputStream; |
|
import java.util.Enumeration; |
|
import java.util.HashMap; |
|
import java.util.NoSuchElementException; |
|
import java.security.AccessController; |
|
import java.security.AccessControlContext; |
|
import java.security.PrivilegedAction; |
|
import java.security.PrivilegedExceptionAction; |
|
import java.security.PrivilegedActionException; |
|
import java.security.CodeSource; |
|
import java.security.Permission; |
|
import java.security.PermissionCollection; |
|
import sun.awt.AppContext; |
|
import sun.awt.SunToolkit; |
|
import sun.misc.IOUtils; |
|
import sun.net.www.ParseUtil; |
|
import sun.security.util.SecurityConstants; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class AppletClassLoader extends URLClassLoader { |
|
private URL base; |
|
private CodeSource codesource; |
|
private AccessControlContext acc; |
|
private boolean exceptionStatus = false; |
|
|
|
private final Object threadGroupSynchronizer = new Object(); |
|
private final Object grabReleaseSynchronizer = new Object(); |
|
|
|
private boolean codebaseLookup = true; |
|
private volatile boolean allowRecursiveDirectoryRead = true; |
|
|
|
|
|
|
|
*/ |
|
protected AppletClassLoader(URL base) { |
|
super(new URL[0]); |
|
this.base = base; |
|
this.codesource = |
|
new CodeSource(base, (java.security.cert.Certificate[]) null); |
|
acc = AccessController.getContext(); |
|
} |
|
|
|
public void disableRecursiveDirectoryRead() { |
|
allowRecursiveDirectoryRead = false; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
void setCodebaseLookup(boolean codebaseLookup) { |
|
this.codebaseLookup = codebaseLookup; |
|
} |
|
|
|
|
|
|
|
*/ |
|
URL getBaseURL() { |
|
return base; |
|
} |
|
|
|
|
|
|
|
*/ |
|
public URL[] getURLs() { |
|
URL[] jars = super.getURLs(); |
|
URL[] urls = new URL[jars.length + 1]; |
|
System.arraycopy(jars, 0, urls, 0, jars.length); |
|
urls[urls.length - 1] = base; |
|
return urls; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected void addJar(String name) throws IOException { |
|
URL url; |
|
try { |
|
url = new URL(base, name); |
|
} catch (MalformedURLException e) { |
|
throw new IllegalArgumentException("name"); |
|
} |
|
addURL(url); |
|
// DEBUG |
|
//URL[] urls = getURLs(); |
|
//for (int i = 0; i < urls.length; i++) { |
|
// System.out.println("url[" + i + "] = " + urls[i]); |
|
//} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public synchronized Class loadClass(String name, boolean resolve) |
|
throws ClassNotFoundException |
|
{ |
|
// First check if we have permission to access the package. This |
|
|
|
int i = name.lastIndexOf('.'); |
|
if (i != -1) { |
|
SecurityManager sm = System.getSecurityManager(); |
|
if (sm != null) |
|
sm.checkPackageAccess(name.substring(0, i)); |
|
} |
|
try { |
|
return super.loadClass(name, resolve); |
|
} catch (ClassNotFoundException e) { |
|
|
|
throw e; |
|
} catch (RuntimeException e) { |
|
|
|
throw e; |
|
} catch (Error e) { |
|
|
|
throw e; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
protected Class findClass(String name) throws ClassNotFoundException { |
|
|
|
int index = name.indexOf(";"); |
|
String cookie = ""; |
|
if(index != -1) { |
|
cookie = name.substring(index, name.length()); |
|
name = name.substring(0, index); |
|
} |
|
|
|
|
|
try { |
|
return super.findClass(name); |
|
} catch (ClassNotFoundException e) { |
|
} |
|
|
|
// Otherwise, try loading the class from the code base URL |
|
|
|
// 4668479: Option to turn off codebase lookup in AppletClassLoader |
|
|
|
if (codebaseLookup == false) |
|
throw new ClassNotFoundException(name); |
|
|
|
|
|
String encodedName = ParseUtil.encodePath(name.replace('.', '/'), false); |
|
final String path = (new StringBuffer(encodedName)).append(".class").append(cookie).toString(); |
|
try { |
|
byte[] b = (byte[]) AccessController.doPrivileged( |
|
new PrivilegedExceptionAction() { |
|
public Object run() throws IOException { |
|
try { |
|
URL finalURL = new URL(base, path); |
|
|
|
|
|
if (base.getProtocol().equals(finalURL.getProtocol()) && |
|
base.getHost().equals(finalURL.getHost()) && |
|
base.getPort() == finalURL.getPort()) { |
|
return getBytes(finalURL); |
|
} |
|
else { |
|
return null; |
|
} |
|
} catch (Exception e) { |
|
return null; |
|
} |
|
} |
|
}, acc); |
|
|
|
if (b != null) { |
|
return defineClass(name, b, 0, b.length, codesource); |
|
} else { |
|
throw new ClassNotFoundException(name); |
|
} |
|
} catch (PrivilegedActionException e) { |
|
throw new ClassNotFoundException(name, e.getException()); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected PermissionCollection getPermissions(CodeSource codesource) |
|
{ |
|
final PermissionCollection perms = super.getPermissions(codesource); |
|
|
|
URL url = codesource.getLocation(); |
|
|
|
String path = null; |
|
Permission p; |
|
|
|
try { |
|
p = url.openConnection().getPermission(); |
|
} catch (java.io.IOException ioe) { |
|
p = null; |
|
} |
|
|
|
if (p instanceof FilePermission) { |
|
path = p.getName(); |
|
} else if ((p == null) && (url.getProtocol().equals("file"))) { |
|
path = url.getFile().replace('/', File.separatorChar); |
|
path = ParseUtil.decode(path); |
|
} |
|
|
|
if (path != null) { |
|
final String rawPath = path; |
|
if (!path.endsWith(File.separator)) { |
|
int endIndex = path.lastIndexOf(File.separatorChar); |
|
if (endIndex != -1) { |
|
path = path.substring(0, endIndex + 1) + "-"; |
|
perms.add(new FilePermission(path, |
|
SecurityConstants.FILE_READ_ACTION)); |
|
} |
|
} |
|
final File f = new File(rawPath); |
|
final boolean isDirectory = f.isDirectory(); |
|
// grant codebase recursive read permission |
|
// this should only be granted to non-UNC file URL codebase and |
|
// the codesource path must either be a directory, or a file |
|
|
|
if (allowRecursiveDirectoryRead && (isDirectory || |
|
rawPath.toLowerCase().endsWith(".jar") || |
|
rawPath.toLowerCase().endsWith(".zip"))) { |
|
|
|
Permission bperm; |
|
try { |
|
bperm = base.openConnection().getPermission(); |
|
} catch (java.io.IOException ioe) { |
|
bperm = null; |
|
} |
|
if (bperm instanceof FilePermission) { |
|
String bpath = bperm.getName(); |
|
if (bpath.endsWith(File.separator)) { |
|
bpath += "-"; |
|
} |
|
perms.add(new FilePermission(bpath, |
|
SecurityConstants.FILE_READ_ACTION)); |
|
} else if ((bperm == null) && (base.getProtocol().equals("file"))) { |
|
String bpath = base.getFile().replace('/', File.separatorChar); |
|
bpath = ParseUtil.decode(bpath); |
|
if (bpath.endsWith(File.separator)) { |
|
bpath += "-"; |
|
} |
|
perms.add(new FilePermission(bpath, SecurityConstants.FILE_READ_ACTION)); |
|
} |
|
|
|
} |
|
} |
|
return perms; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static byte[] getBytes(URL url) throws IOException { |
|
URLConnection uc = url.openConnection(); |
|
if (uc instanceof java.net.HttpURLConnection) { |
|
java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc; |
|
int code = huc.getResponseCode(); |
|
if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { |
|
throw new IOException("open HTTP connection failed."); |
|
} |
|
} |
|
int len = uc.getContentLength(); |
|
|
|
// Fixed #4507227: Slow performance to load |
|
// class and resources. [stanleyh] |
|
// |
|
|
|
InputStream in = new BufferedInputStream(uc.getInputStream()); |
|
|
|
byte[] b; |
|
try { |
|
b = IOUtils.readAllBytes(in); |
|
if (len != -1 && b.length != len) |
|
throw new EOFException("Expected:" + len + ", read:" + b.length); |
|
} finally { |
|
in.close(); |
|
} |
|
return b; |
|
} |
|
|
|
|
|
private Object syncResourceAsStream = new Object(); |
|
private Object syncResourceAsStreamFromJar = new Object(); |
|
|
|
|
|
private boolean resourceAsStreamInCall = false; |
|
private boolean resourceAsStreamFromJarInCall = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public InputStream getResourceAsStream(String name) |
|
{ |
|
|
|
if (name == null) { |
|
throw new NullPointerException("name"); |
|
} |
|
|
|
try |
|
{ |
|
InputStream is = null; |
|
|
|
// Fixed #4507227: Slow performance to load |
|
// class and resources. [stanleyh] |
|
// |
|
// The following is used to avoid calling |
|
// AppletClassLoader.findResource() in |
|
// super.getResourceAsStream(). Otherwise, |
|
// unnecessary connection will be made. |
|
|
|
synchronized(syncResourceAsStream) |
|
{ |
|
resourceAsStreamInCall = true; |
|
|
|
|
|
is = super.getResourceAsStream(name); |
|
|
|
resourceAsStreamInCall = false; |
|
} |
|
|
|
// 4668479: Option to turn off codebase lookup in AppletClassLoader |
|
|
|
if (codebaseLookup == true && is == null) |
|
{ |
|
// If resource cannot be obtained, |
|
|
|
URL url = new URL(base, ParseUtil.encodePath(name, false)); |
|
is = url.openStream(); |
|
} |
|
|
|
return is; |
|
} |
|
catch (Exception e) |
|
{ |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public InputStream getResourceAsStreamFromJar(String name) { |
|
|
|
if (name == null) { |
|
throw new NullPointerException("name"); |
|
} |
|
|
|
try { |
|
InputStream is = null; |
|
synchronized(syncResourceAsStreamFromJar) { |
|
resourceAsStreamFromJarInCall = true; |
|
|
|
is = super.getResourceAsStream(name); |
|
resourceAsStreamFromJarInCall = false; |
|
} |
|
|
|
return is; |
|
} catch (Exception e) { |
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public URL findResource(String name) { |
|
|
|
URL url = super.findResource(name); |
|
|
|
// 6215746: Disable META-INF/* lookup from codebase in |
|
|
|
if (name.startsWith("META-INF/")) |
|
return url; |
|
|
|
// 4668479: Option to turn off codebase lookup in AppletClassLoader |
|
|
|
if (codebaseLookup == false) |
|
return url; |
|
|
|
if (url == null) |
|
{ |
|
//#4805170, if it is a call from Applet.getImage() |
|
|
|
boolean insideGetResourceAsStreamFromJar = false; |
|
synchronized(syncResourceAsStreamFromJar) { |
|
insideGetResourceAsStreamFromJar = resourceAsStreamFromJarInCall; |
|
} |
|
|
|
if (insideGetResourceAsStreamFromJar) { |
|
return null; |
|
} |
|
|
|
// Fixed #4507227: Slow performance to load |
|
// class and resources. [stanleyh] |
|
// |
|
// Check if getResourceAsStream is called. |
|
|
|
boolean insideGetResourceAsStream = false; |
|
|
|
synchronized(syncResourceAsStream) |
|
{ |
|
insideGetResourceAsStream = resourceAsStreamInCall; |
|
} |
|
|
|
// If getResourceAsStream is called, don't |
|
// trigger the following code. Otherwise, |
|
// unnecessary connection will be made. |
|
|
|
if (insideGetResourceAsStream == false) |
|
{ |
|
|
|
try { |
|
url = new URL(base, ParseUtil.encodePath(name, false)); |
|
|
|
if(!resourceExists(url)) |
|
url = null; |
|
} catch (Exception e) { |
|
|
|
url = null; |
|
} |
|
} |
|
} |
|
return url; |
|
} |
|
|
|
|
|
private boolean resourceExists(URL url) { |
|
// Check if the resource exists. |
|
// It almost works to just try to do an openConnection() but |
|
// HttpURLConnection will return true on HTTP_BAD_REQUEST |
|
// when the requested name ends in ".html", ".htm", and ".txt" |
|
// and we want to be able to handle these |
|
// |
|
// Also, cannot just open a connection for things like FileURLConnection, |
|
// because they succeed when connecting to a nonexistent file. |
|
|
|
boolean ok = true; |
|
try { |
|
URLConnection conn = url.openConnection(); |
|
if (conn instanceof java.net.HttpURLConnection) { |
|
java.net.HttpURLConnection hconn = |
|
(java.net.HttpURLConnection) conn; |
|
|
|
|
|
hconn.setRequestMethod("HEAD"); |
|
|
|
int code = hconn.getResponseCode(); |
|
if (code == java.net.HttpURLConnection.HTTP_OK) { |
|
return true; |
|
} |
|
if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { |
|
return false; |
|
} |
|
} else { |
|
/** |
|
* Fix for #4182052 - stanleyh |
|
* |
|
* The same connection should be reused to avoid multiple |
|
* HTTP connections |
|
*/ |
|
|
|
|
|
InputStream is = conn.getInputStream(); |
|
is.close(); |
|
} |
|
} catch (Exception ex) { |
|
ok = false; |
|
} |
|
return ok; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Enumeration findResources(String name) throws IOException { |
|
|
|
final Enumeration e = super.findResources(name); |
|
|
|
// 6215746: Disable META-INF/* lookup from codebase in |
|
|
|
if (name.startsWith("META-INF/")) |
|
return e; |
|
|
|
// 4668479: Option to turn off codebase lookup in AppletClassLoader |
|
|
|
if (codebaseLookup == false) |
|
return e; |
|
|
|
URL u = new URL(base, ParseUtil.encodePath(name, false)); |
|
if (!resourceExists(u)) { |
|
u = null; |
|
} |
|
|
|
final URL url = u; |
|
return new Enumeration() { |
|
private boolean done; |
|
public Object nextElement() { |
|
if (!done) { |
|
if (e.hasMoreElements()) { |
|
return e.nextElement(); |
|
} |
|
done = true; |
|
if (url != null) { |
|
return url; |
|
} |
|
} |
|
throw new NoSuchElementException(); |
|
} |
|
public boolean hasMoreElements() { |
|
return !done && (e.hasMoreElements() || url != null); |
|
} |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
Class loadCode(String name) throws ClassNotFoundException { |
|
|
|
name = name.replace('/', '.'); |
|
name = name.replace(File.separatorChar, '.'); |
|
|
|
|
|
String cookie = null; |
|
int index = name.indexOf(";"); |
|
if(index != -1) { |
|
cookie = name.substring(index, name.length()); |
|
name = name.substring(0, index); |
|
} |
|
|
|
|
|
String fullName = name; |
|
|
|
if (name.endsWith(".class") || name.endsWith(".java")) { |
|
name = name.substring(0, name.lastIndexOf('.')); |
|
} |
|
try { |
|
if(cookie != null) |
|
name = (new StringBuffer(name)).append(cookie).toString(); |
|
return loadClass(name); |
|
} catch (ClassNotFoundException e) { |
|
} |
|
// then if it didn't end with .java or .class, or in the |
|
|
|
if(cookie != null) |
|
fullName = (new StringBuffer(fullName)).append(cookie).toString(); |
|
|
|
return loadClass(fullName); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private AppletThreadGroup threadGroup; |
|
private AppContext appContext; |
|
|
|
public ThreadGroup getThreadGroup() { |
|
synchronized (threadGroupSynchronizer) { |
|
if (threadGroup == null || threadGroup.isDestroyed()) { |
|
AccessController.doPrivileged(new PrivilegedAction() { |
|
public Object run() { |
|
threadGroup = new AppletThreadGroup(base + "-threadGroup"); |
|
// threadGroup.setDaemon(true); |
|
// threadGroup is now destroyed by AppContext.dispose() |
|
|
|
// Create the new AppContext from within a Thread belonging |
|
// to the newly created ThreadGroup, and wait for the |
|
|
|
AppContextCreator creatorThread = new AppContextCreator(threadGroup); |
|
|
|
// Since this thread will later be used to launch the |
|
// applet's AWT-event dispatch thread and we want the applet |
|
// code executing the AWT callbacks to use their own class |
|
// loader rather than the system class loader, explicitly |
|
|
|
creatorThread.setContextClassLoader(AppletClassLoader.this); |
|
|
|
creatorThread.start(); |
|
try { |
|
synchronized(creatorThread.syncObject) { |
|
while (!creatorThread.created) { |
|
creatorThread.syncObject.wait(); |
|
} |
|
} |
|
} catch (InterruptedException e) { } |
|
appContext = creatorThread.appContext; |
|
return null; |
|
} |
|
}); |
|
} |
|
return threadGroup; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public AppContext getAppContext() { |
|
return appContext; |
|
} |
|
|
|
int usageCount = 0; |
|
|
|
|
|
|
|
|
|
*/ |
|
public void grab() { |
|
synchronized(grabReleaseSynchronizer) { |
|
usageCount++; |
|
} |
|
getThreadGroup(); |
|
} |
|
|
|
protected void setExceptionStatus() |
|
{ |
|
exceptionStatus = true; |
|
} |
|
|
|
public boolean getExceptionStatus() |
|
{ |
|
return exceptionStatus; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected void release() { |
|
|
|
AppContext tempAppContext = null; |
|
|
|
synchronized(grabReleaseSynchronizer) { |
|
if (usageCount > 1) { |
|
--usageCount; |
|
} else { |
|
synchronized(threadGroupSynchronizer) { |
|
tempAppContext = resetAppContext(); |
|
} |
|
} |
|
} |
|
|
|
// Dispose appContext outside any sync block to |
|
|
|
if (tempAppContext != null) { |
|
try { |
|
tempAppContext.dispose(); |
|
} catch (IllegalThreadStateException e) { } |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
protected AppContext resetAppContext() { |
|
AppContext tempAppContext = null; |
|
|
|
synchronized(threadGroupSynchronizer) { |
|
|
|
tempAppContext = appContext; |
|
usageCount = 0; |
|
appContext = null; |
|
threadGroup = null; |
|
} |
|
return tempAppContext; |
|
} |
|
|
|
|
|
|
|
private HashMap jdk11AppletInfo = new HashMap(); |
|
private HashMap jdk12AppletInfo = new HashMap(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void setJDK11Target(Class clazz, boolean bool) |
|
{ |
|
jdk11AppletInfo.put(clazz.toString(), Boolean.valueOf(bool)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void setJDK12Target(Class clazz, boolean bool) |
|
{ |
|
jdk12AppletInfo.put(clazz.toString(), Boolean.valueOf(bool)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
Boolean isJDK11Target(Class clazz) |
|
{ |
|
return (Boolean) jdk11AppletInfo.get(clazz.toString()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
Boolean isJDK12Target(Class clazz) |
|
{ |
|
return (Boolean) jdk12AppletInfo.get(clazz.toString()); |
|
} |
|
|
|
private static AppletMessageHandler mh = |
|
new AppletMessageHandler("appletclassloader"); |
|
|
|
|
|
|
|
*/ |
|
private static void printError(String name, Throwable e) { |
|
String s = null; |
|
if (e == null) { |
|
s = mh.getMessage("filenotfound", name); |
|
} else if (e instanceof IOException) { |
|
s = mh.getMessage("fileioexception", name); |
|
} else if (e instanceof ClassFormatError) { |
|
s = mh.getMessage("fileformat", name); |
|
} else if (e instanceof ThreadDeath) { |
|
s = mh.getMessage("filedeath", name); |
|
} else if (e instanceof Error) { |
|
s = mh.getMessage("fileerror", e.toString(), name); |
|
} |
|
if (s != null) { |
|
System.err.println(s); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
class AppContextCreator extends Thread { |
|
Object syncObject = new Object(); |
|
AppContext appContext = null; |
|
volatile boolean created = false; |
|
|
|
AppContextCreator(ThreadGroup group) { |
|
super(group, "AppContextCreator"); |
|
} |
|
|
|
public void run() { |
|
appContext = SunToolkit.createNewAppContext(); |
|
created = true; |
|
synchronized(syncObject) { |
|
syncObject.notifyAll(); |
|
} |
|
} // run() |
|
|
|
} // class AppContextCreator |