--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.services.reflect.JarLoader\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.services.reflect;\r
+\r
+import org.apache.derby.iapi.services.stream.HeaderPrintWriter;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.File;\r
+import java.io.FileNotFoundException;\r
+import java.io.InputStream;\r
+import java.io.IOException;\r
+\r
+import java.security.AccessController;\r
+import java.security.CodeSource;\r
+import java.security.GeneralSecurityException;\r
+import java.security.PrivilegedActionException;\r
+import java.security.SecureClassLoader;\r
+import java.security.cert.Certificate;\r
+import java.security.cert.X509Certificate;\r
+import java.util.jar.JarEntry;\r
+import java.util.jar.JarFile;\r
+import java.util.jar.JarInputStream;\r
+\r
+import org.apache.derby.iapi.services.io.AccessibleByteArrayOutputStream;\r
+import org.apache.derby.iapi.services.io.InputStreamUtil;\r
+import org.apache.derby.iapi.services.io.LimitInputStream;\r
+import org.apache.derby.iapi.util.IdUtil;\r
+\r
+import org.apache.derby.iapi.reference.MessageId;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.io.StorageFile;\r
+\r
+\r
+final class JarLoader extends SecureClassLoader {\r
+ \r
+ /**\r
+ * Two part name for the jar file.\r
+ */\r
+ private final String[] name;\r
+ \r
+ /**\r
+ * Handle to the installed jar file.\r
+ */\r
+ private StorageFile installedJar;\r
+ \r
+ /**\r
+ * When the jar file can be manipulated as a java.util.JarFile\r
+ * this holds the reference to the open jar. When the jar can\r
+ * only be manipulated as an InputStream (because the jar is itself\r
+ * in a database jar) then this will be null.\r
+ */\r
+ private JarFile jar;\r
+ \r
+ /**\r
+ * True if the jar can only be accessed using a stream, because\r
+ * the jar is itself in a database jar. When fals the jar is accessed\r
+ * using the jar field.\r
+ */\r
+ private boolean isStream;\r
+\r
+ private UpdateLoader updateLoader;\r
+ private HeaderPrintWriter vs;\r
+\r
+ JarLoader(UpdateLoader updateLoader, String[] name, HeaderPrintWriter vs) {\r
+\r
+ this.updateLoader = updateLoader;\r
+ this.name = name;\r
+ this.vs = vs;\r
+ }\r
+\r
+ /**\r
+ * Initialize the class loader so it knows if it\r
+ * is loading from a ZipFile or an InputStream\r
+ */\r
+ void initialize() {\r
+\r
+ String schemaName = name[IdUtil.DBCP_SCHEMA_NAME];\r
+ String sqlName = name[IdUtil.DBCP_SQL_JAR_NAME];\r
+\r
+ Exception e;\r
+ try {\r
+ installedJar =\r
+ updateLoader.getJarReader().getJarFile(\r
+ schemaName, sqlName);\r
+\r
+ if (installedJar instanceof File) {\r
+ try {\r
+ jar = (JarFile) AccessController.doPrivileged\r
+ (new java.security.PrivilegedExceptionAction(){\r
+\r
+ public Object run() throws IOException {\r
+ return new JarFile((File) installedJar);\r
+\r
+ }\r
+\r
+ }\r
+ );\r
+ } catch (PrivilegedActionException pae) {\r
+ throw (IOException) pae.getException();\r
+ }\r
+ return;\r
+ }\r
+\r
+ // Jar is only accessible as an InputStream,\r
+ // which means we need to re-open the stream for\r
+ // each access.\r
+\r
+ isStream = true;\r
+ return;\r
+\r
+ } catch (IOException ioe) {\r
+ e = ioe;\r
+ } catch (StandardException se) {\r
+ e = se;\r
+ }\r
+\r
+ if (vs != null)\r
+ vs.println(MessageService.getTextMessage(\r
+ MessageId.CM_LOAD_JAR_EXCEPTION, getJarName(), e));\r
+\r
+ // No such zip.\r
+ setInvalid();\r
+ }\r
+\r
+ /**\r
+ * Handle all requests to the top-level loader.\r
+ * \r
+ * @exception ClassNotFoundException\r
+ * Class can not be found\r
+ */\r
+ protected Class loadClass(String className, boolean resolve) \r
+ throws ClassNotFoundException {\r
+ \r
+ // Classes in installed jars cannot reference\r
+ // Derby internal code. This is to avoid\r
+ // code in installed jars bypassing SQL\r
+ // authorization by calling Derby's internal methods.\r
+ //\r
+ // Any classes in the org.apache.derby.jdbc package\r
+ // are allowed as it allows routines to make JDBC\r
+ // connections to other databases. This does expose\r
+ // public classes in that package that are not part\r
+ // of the public api to attacks. One could attempt\r
+ // further limiting allowed classes to those starting\r
+ // with Embedded (and Client) but when fetching the\r
+ // default connection in a routine (jdbc:default:connection)\r
+ // the DriverManager attempts a load of the already loaded\r
+ // AutoloadDriver, I think to establish the calling class\r
+ // has access to the driver.\r
+ //\r
+ // This check in addition to the one in UpdateLoader\r
+ // that prevents restricted classes from being loaded\r
+ // from installed jars. The checks should be seen as\r
+ // independent, ie. the restricted load check should\r
+ // not make assumptions about this check reducing the\r
+ // number of classes it has to check for.\r
+ if (className.startsWith("org.apache.derby.")\r
+ && !className.startsWith("org.apache.derby.jdbc."))\r
+ {\r
+ ClassNotFoundException cnfe = new ClassNotFoundException(className);\r
+ //cnfe.printStackTrace(System.out);\r
+ throw cnfe;\r
+ }\r
+\r
+ // we attempt the system class load even if we\r
+ // are stale because otherwise we will fail\r
+ // to load java.* classes which confuses some VMs\r
+ try {\r
+ return Class.forName(className);\r
+ //Added by Jeff Huang\r
+ //TODO: FIXIT\r
+ } catch (ClassNotFoundException cnfe) {\r
+\r
+ if (updateLoader == null)\r
+ throw new ClassNotFoundException(MessageService.getTextMessage(MessageId.CM_STALE_LOADER, className));\r
+\r
+ Class c = updateLoader.loadClass(className, resolve);\r
+ if (c == null)\r
+ throw cnfe;\r
+ return c;\r
+ }\r
+ }\r
+\r
+ /**\r
+ \r
+ */\r
+ public InputStream getResourceAsStream(String name) {\r
+ if (updateLoader == null)\r
+ return null;\r
+ return updateLoader.getResourceAsStream(name);\r
+ }\r
+\r
+ /**\r
+ * Return the SQL name for the installed jar.\r
+ * Used for error and informational messages.\r
+ */\r
+ final String getJarName() {\r
+ return IdUtil.mkQualifiedName(name);\r
+ }\r
+\r
+ Class loadClassData(String className, String jvmClassName, boolean resolve) {\r
+\r
+ if (updateLoader == null)\r
+ return null;\r
+\r
+ try {\r
+ if (jar != null)\r
+ return loadClassDataFromJar(className, jvmClassName, resolve);\r
+\r
+ if (isStream) {\r
+ // have to use a new stream each time\r
+ return loadClassData(installedJar.getInputStream(),\r
+ className, jvmClassName, resolve);\r
+ }\r
+\r
+ return null;\r
+ } catch (FileNotFoundException fnfe) {\r
+ // No such entry.\r
+ return null;\r
+ } catch (IOException ioe) {\r
+ if (vs != null)\r
+ vs.println(MessageService.getTextMessage(MessageId.CM_CLASS_LOAD_EXCEPTION, className, getJarName(), ioe));\r
+ return null;\r
+ } \r
+ }\r
+\r
+ /**\r
+ Get an InputStream for the given resource.\r
+ */\r
+ InputStream getStream(String name) {\r
+\r
+ if (updateLoader == null)\r
+ return null;\r
+ \r
+ if (jar != null)\r
+ return getRawStream(name);\r
+\r
+ if (isStream) {\r
+ try {\r
+ return getRawStream(installedJar.getInputStream(), name);\r
+ } catch (FileNotFoundException e) {\r
+ // no such entry\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+\r
+ /*\r
+ ** Private api\r
+ */\r
+\r
+\r
+ /**\r
+ * Load the class data when the installed jar is accessible\r
+ * as a java.util.jarFile.\r
+ */\r
+ private Class loadClassDataFromJar(\r
+ String className, String jvmClassName, boolean resolve) \r
+ throws IOException {\r
+\r
+ JarEntry e = jar.getJarEntry(jvmClassName);\r
+ if (e == null)\r
+ return null;\r
+\r
+ InputStream in = jar.getInputStream(e);\r
+\r
+ try {\r
+ return loadClassData(e, in, className, resolve);\r
+ } finally {\r
+ in.close();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Load the class data when the installed jar is accessible\r
+ * only as an input stream (the jar is itself in a database jar).\r
+ */\r
+ private Class loadClassData(\r
+ InputStream in, String className, String jvmClassName, boolean resolve) \r
+ throws IOException {\r
+\r
+ JarInputStream jarIn = new JarInputStream(in);\r
+\r
+ for (;;) {\r
+\r
+ JarEntry e = jarIn.getNextJarEntry();\r
+ if (e == null) {\r
+ jarIn.close();\r
+ return null;\r
+ }\r
+\r
+ if (e.getName().equals(jvmClassName)) {\r
+ Class c = loadClassData(e, jarIn, className, resolve);\r
+ jarIn.close();\r
+ return c;\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ /**\r
+ * Load and optionally resolve the class given its\r
+ * JarEntry and an InputStream to the class fiel format.\r
+ * This is common code for when the jar is accessed\r
+ * directly using JarFile or through InputStream.\r
+ */\r
+ private Class loadClassData(JarEntry e, InputStream in,\r
+ String className, boolean resolve) throws IOException {\r
+\r
+ byte[] data = readData(e, in, className);\r
+\r
+ Certificate[] signers = getSigners(className, e);\r
+\r
+ synchronized (updateLoader) {\r
+ // see if someone else loaded it while we\r
+ // were getting the bytes ...\r
+ Class c = updateLoader.checkLoaded(className, resolve);\r
+ if (c == null) {\r
+ c = defineClass(className, data, 0, data.length, (CodeSource) null);\r
+ if (signers != null) {\r
+ setSigners(c, signers);\r
+ }\r
+ if (resolve)\r
+ resolveClass(c);\r
+ }\r
+ return c;\r
+\r
+ }\r
+ }\r
+\r
+ Class checkLoaded(String className, boolean resolve) {\r
+ if (updateLoader == null)\r
+ return null;\r
+\r
+ Class c = findLoadedClass(className);\r
+ if ((c != null) && resolve)\r
+ resolveClass(c);\r
+ return c;\r
+ }\r
+\r
+ /**\r
+ * Set this loader to be invaid so that it will not\r
+ * resolve any classes or resources.\r
+ *\r
+ */\r
+ void setInvalid() {\r
+ updateLoader = null;\r
+ if (jar != null) {\r
+ try {\r
+ jar.close();\r
+ } catch (IOException ioe) {\r
+ }\r
+ jar = null;\r
+\r
+ }\r
+ isStream = false;\r
+ }\r
+\r
+ /*\r
+ ** Routines to get an InputStream for a namedResource\r
+ */\r
+\r
+ /**\r
+ Get a stream for a resource directly from a JarFile.\r
+ In this case we can safely return the stream directly.\r
+ It's a new stream set up by the zip code to read just\r
+ the contents of this entry.\r
+ */\r
+ private InputStream getRawStream(String name) {\r
+\r
+ try {\r
+ JarEntry e = jar.getJarEntry(name);\r
+ if (e == null)\r
+ return null;\r
+\r
+ return jar.getInputStream(e);\r
+ } catch (IOException ioe) {\r
+ return null;\r
+ }\r
+ }\r
+\r
+ /**\r
+ Get a stream from a zip file that is itself a stream.\r
+ We copy to the contents to a byte array and return a\r
+ stream around that to the caller. Though a copy is\r
+ involved it has the benefit of:\r
+ <UL>\r
+ <LI> Isolating the application from the JarInputStream, thus\r
+ denying any possibility of the application reading more of the\r
+ jar that it should be allowed to. E.g. the contents class files are not\r
+ exposed through getResource.\r
+ <LI> Avoids any possibility of the application holding onto\r
+ the open stream beyond shutdown of the database, thus leading\r
+ to leaked file descriptors or inability to remove the jar.\r
+ </UL>\r
+ */\r
+ private InputStream getRawStream(InputStream in, String name) { \r
+\r
+ JarInputStream jarIn = null;\r
+ try {\r
+ jarIn = new JarInputStream(in);\r
+\r
+ JarEntry e;\r
+ while ((e = jarIn.getNextJarEntry()) != null) {\r
+\r
+ if (e.getName().equals(name)) {\r
+ int size = (int) e.getSize();\r
+ if (size == -1)\r
+ {\r
+ // unknown size so just pick a good buffer size.\r
+ size = 8192;\r
+ }\r
+ return AccessibleByteArrayOutputStream.copyStream(jarIn, size);\r
+ }\r
+ }\r
+\r
+ } catch (IOException ioe) {\r
+ // can't read the jar file just assume it doesn't exist.\r
+ }\r
+ finally {\r
+ if (jarIn != null) {\r
+ try {\r
+ jarIn.close();\r
+ } catch (IOException ioe2) {\r
+ }\r
+ } \r
+ }\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * Read the raw data for the class file format\r
+ * into a byte array that can be used for loading the class.\r
+ * If this is a signed class and it has been compromised then\r
+ * a SecurityException will be thrown.\r
+ */\r
+ byte[] readData(JarEntry ze, InputStream in, String className)\r
+ throws IOException {\r
+\r
+ try {\r
+ int size = (int) ze.getSize();\r
+\r
+ if (size != -1) {\r
+ byte[] data = new byte[size];\r
+\r
+ InputStreamUtil.readFully(in, data, 0, size);\r
+\r
+ return data;\r
+ }\r
+\r
+ // unknown size\r
+ byte[] data = new byte[1024];\r
+ ByteArrayOutputStream os = new ByteArrayOutputStream(1024);\r
+ int r;\r
+ while ((r = in.read(data)) != -1) {\r
+ os.write(data, 0, r);\r
+ }\r
+\r
+ data = os.toByteArray();\r
+ return data;\r
+ } catch (SecurityException se) {\r
+ throw handleException(se, className);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Validate the security certificates (signers) for the class data.\r
+ */\r
+ private Certificate[] getSigners(String className, JarEntry je) throws IOException {\r
+\r
+ try {\r
+ Certificate[] list = je.getCertificates();\r
+ if ((list == null) || (list.length == 0)) {\r
+ return null;\r
+ }\r
+\r
+ for (int i = 0; i < list.length; i++) {\r
+ if (!(list[i] instanceof X509Certificate)) {\r
+ String msg = MessageService.getTextMessage(\r
+ MessageId.CM_UNKNOWN_CERTIFICATE, className,\r
+ getJarName());\r
+\r
+ throw new SecurityException(msg);\r
+ }\r
+\r
+ X509Certificate cert = (X509Certificate) list[i];\r
+\r
+ cert.checkValidity();\r
+ }\r
+\r
+ return list;\r
+\r
+ } catch (GeneralSecurityException gse) {\r
+ // convert this into an unchecked security\r
+ // exception. Unchecked as eventually it has\r
+ // to pass through a method that's only throwing\r
+ // ClassNotFoundException\r
+ throw handleException(gse, className);\r
+ }\r
+ \r
+ }\r
+\r
+ /**\r
+ * Provide a SecurityManager with information about the class name\r
+ * and the jar file.\r
+ */\r
+ private SecurityException handleException(Exception e, String className) {\r
+ String msg = MessageService.getTextMessage(\r
+ MessageId.CM_SECURITY_EXCEPTION, className, getJarName(), e\r
+ .getLocalizedMessage());\r
+ return new SecurityException(msg);\r
+ }\r
+ \r
+ /**\r
+ * Return the jar name if toString() is called\r
+ * on this class loader.\r
+ */\r
+ public String toString()\r
+ {\r
+ return getJarName() + ":" + super.toString();\r
+ }\r
+}\r