001/* 002 * Copyright (C) 2014 Jörg Prante 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.xbib.elasticsearch.plugin.jdbc.classloader.uri; 017 018import org.xbib.elasticsearch.plugin.jdbc.classloader.ResourceHandle; 019 020import java.io.File; 021import java.io.IOException; 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.net.URL; 025import java.net.URLClassLoader; 026import java.security.AccessControlContext; 027import java.security.AccessController; 028import java.security.CodeSource; 029import java.security.PrivilegedAction; 030import java.security.PrivilegedActionException; 031import java.security.PrivilegedExceptionAction; 032import java.security.cert.Certificate; 033import java.util.Enumeration; 034import java.util.jar.Attributes; 035import java.util.jar.Attributes.Name; 036import java.util.jar.Manifest; 037 038/** 039 * Equivalent of java.net.URLClassloader but without bugs related to ill-formed 040 * URLs and with customizable JAR caching policy. The standard URLClassLoader 041 * accepts URLs containing spaces and other characters which are forbidden in 042 * the URI syntax, according to the RFC 2396. As a workaround to this problem, 043 * Java escapes and un-escapes URLs in various arbitrary places; however, this 044 * is inconsistent and leads to numerous problems with URLs referring to local 045 * files with spaces in the path. SUN acknowledges the problem, but refuses to 046 * modify the behavior for compatibility reasons; see Java Bug Parade 4273532, 047 * 4466485. 048 * <p/> 049 * Additionally, the JAR caching policy used by URLClassLoader is system-wide 050 * and inflexible: once downloaded JAR files are never re-downloaded, even if 051 * one creates a fresh instance of the class loader that happens to have the 052 * same URL in its search path. In fact, that policy is a security 053 * vulnerability: it is possible to crash any URL class loader, thus affecting 054 * potentially separate part of the system, by creating URL connection to one of 055 * the URLs of that class loader search path and closing the associated JAR 056 * file. See Java Bug Parade 4405789, 4388666, 4639900. 057 * <p/> 058 * This class avoids these problems by 1) using URIs instead of URLs for the 059 * search path (thus enforcing strict syntax conformance and defining precise 060 * escaping semantics), and 2) using custom URLStreamHandler which ensures 061 * per-classloader JAR caching policy. 062 */ 063public final class URIClassLoader extends URLClassLoader { 064 065 private final URIResourceFinder finder = new URIResourceFinder(); 066 067 private final AccessControlContext acc; 068 069 /** 070 * Creates URIClassLoader 071 */ 072 public URIClassLoader() { 073 this(null); 074 } 075 076 /** 077 * Creates URIClassLoader with the specified parent class loader. 078 * 079 * @param parent the parent class loader. 080 */ 081 public URIClassLoader(ClassLoader parent) { 082 super(new URL[0], parent); 083 this.acc = AccessController.getContext(); 084 } 085 086 /** 087 * Add specified URI at the end of the search path. 088 * 089 * @param uri the URI to add 090 */ 091 public URIClassLoader addURI(URI uri) { 092 finder.addURI(uri); 093 return this; 094 } 095 096 public URI[] getURIs() { 097 return finder.getURIs(); 098 } 099 100 /** 101 * Add specified URL at the end of the search path. 102 * 103 * @param url the URL to add 104 * @deprecated use addURI 105 */ 106 @Override 107 @Deprecated 108 protected void addURL(URL url) { 109 try { 110 finder.addURI(url.toURI()); 111 } catch (URISyntaxException e) { 112 // ignore 113 } 114 } 115 116 /** 117 * Finds and loads the class with the specified name. 118 * 119 * @param name the name of the class 120 * @return the resulting class 121 * @throws ClassNotFoundException if the class could not be found 122 */ 123 @Override 124 protected Class findClass(final String name) throws ClassNotFoundException { 125 try { 126 return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { 127 @Override 128 public Object run() throws ClassNotFoundException { 129 String path = name.replace('.', '/').concat(".class"); 130 ResourceHandle h = finder.getResource(path); 131 if (h != null) { 132 try { 133 return defineClass(name, h); 134 } catch (IOException e) { 135 throw new ClassNotFoundException(name, e); 136 } 137 } else { 138 throw new ClassNotFoundException(name); 139 } 140 } 141 }, acc); 142 } catch (PrivilegedActionException pae) { 143 throw (ClassNotFoundException) pae.getException(); 144 } 145 } 146 147 /** 148 * Finds the resource with the specified name. 149 * 150 * @param name the name of the resource 151 * @return a <code>URL</code> for the resource, or <code>null</code> if the 152 * resource could not be found. 153 */ 154 @Override 155 public URL findResource(final String name) { 156 return (URL) AccessController.doPrivileged(new PrivilegedAction() { 157 public Object run() { 158 return finder.findResource(name); 159 } 160 }, acc); 161 } 162 163 /** 164 * Returns an Enumeration of URLs representing all of the resources having 165 * the specified name. 166 * 167 * @param name the resource name 168 * @return an <code>Enumeration</code> of <code>URL</code>s 169 * @throws java.io.IOException if an I/O exception occurs 170 */ 171 @Override 172 public Enumeration<URL> findResources(final String name) throws IOException { 173 return (Enumeration<URL>) AccessController.doPrivileged(new PrivilegedAction() { 174 public Object run() { 175 return finder.findResources(name); 176 } 177 }, acc); 178 } 179 180 /** 181 * Returns the absolute path name of a native library. The VM invokes this 182 * method to locate the native libraries that belong to classes loaded with 183 * this class loader. If this method returns 184 * <code>null</code>, the VM searches the library along the path specified 185 * as the 186 * <code>java.library.path</code> property. This method invoke 187 * {@link #getLibraryHandle} method to find handle of this library. If the 188 * handle is found and its URL protocol is "file", the system-dependent 189 * absolute library file path is returned. Otherwise this method returns 190 * null. <p> 191 * <p/> 192 * Subclasses can override this method to provide specific approaches in 193 * library searching. 194 * 195 * @param libname the library name 196 * @return the absolute path of the native library 197 * @see System#loadLibrary(String) 198 * @see System#mapLibraryName(String) 199 */ 200 @Override 201 protected String findLibrary(String libname) { 202 ResourceHandle md = getLibraryHandle(libname); 203 if (md == null) { 204 return null; 205 } 206 URL url = md.getUrl(); 207 if (!"file".equals(url.getProtocol())) { 208 return null; 209 } 210 return new File(URI.create(url.toString())).getPath(); 211 } 212 213 /** 214 * Finds the ResourceHandle object for the resource with the specified name. 215 * 216 * @param name the name of the resource 217 * @return the ResourceHandle of the resource 218 */ 219 private ResourceHandle getResourceHandle(final String name) { 220 return (ResourceHandle) AccessController.doPrivileged(new PrivilegedAction() { 221 public Object run() { 222 return finder.getResource(name); 223 } 224 }, acc); 225 } 226 227 /** 228 * Finds the ResourceHandle object for the native library with the specified 229 * name. The library name must be '/'-separated path. The last part of this 230 * path is substituted by its system-dependent mapping (using 231 * {@link System#mapLibraryName(String)} method). Next, the 232 * <code>ResourceFinder</code> is used to look for the library as it was 233 * ordinary resource. <p> 234 * <p/> 235 * Subclasses can override this method to provide specific approaches in 236 * library searching. 237 * 238 * @param name the name of the library 239 * @return the ResourceHandle of the library 240 */ 241 private ResourceHandle getLibraryHandle(final String name) { 242 int idx = name.lastIndexOf('/'); 243 String path; 244 String simplename; 245 if (idx == -1) { 246 path = ""; 247 simplename = name; 248 } else if (idx == name.length() - 1) { // name.endsWith('/') 249 throw new IllegalArgumentException(name); 250 } else { 251 path = name.substring(0, idx + 1); // including '/' 252 simplename = name.substring(idx + 1); 253 } 254 return getResourceHandle(path + System.mapLibraryName(simplename)); 255 } 256 257 /** 258 * Returns true if the specified package name is sealed according to the 259 * given manifest. 260 */ 261 private boolean isSealed(String name, Manifest man) { 262 String path = name.replace('.', '/').concat("/"); 263 Attributes attr = man.getAttributes(path); 264 String sealed = null; 265 if (attr != null) { 266 sealed = attr.getValue(Name.SEALED); 267 } 268 if (sealed == null) { 269 if ((attr = man.getMainAttributes()) != null) { 270 sealed = attr.getValue(Name.SEALED); 271 } 272 } 273 return "true".equalsIgnoreCase(sealed); 274 } 275 276 private Class defineClass(String name, ResourceHandle h) throws IOException { 277 int i = name.lastIndexOf('.'); 278 URL url = h.getCodeSourceUrl(); 279 if (i != -1) { // check package 280 String pkgname = name.substring(0, i); 281 // check if package already loaded 282 Package pkg = getPackage(pkgname); 283 Manifest man = h.getManifest(); 284 if (pkg != null) { 285 // package found, so check package sealing 286 boolean ok; 287 if (pkg.isSealed()) { 288 // verify that code source URLs are the same 289 ok = pkg.isSealed(url); 290 } else { 291 // make sure we are not attempting to seal the package 292 // at this code source URL 293 ok = (man == null) || !isSealed(pkgname, man); 294 } 295 if (!ok) { 296 throw new SecurityException("sealing violation: " + name); 297 } 298 } else { // package not yet defined 299 if (man != null) { 300 definePackage(pkgname, man, url); 301 } else { 302 definePackage(pkgname, null, null, null, null, null, null, null); 303 } 304 } 305 } 306 // now read the class bytes and define the class 307 byte[] b = h.getBytes(); 308 Certificate[] certs = h.getCertificates(); 309 CodeSource cs = new CodeSource(url, certs); 310 return defineClass(name, b, 0, b.length, cs); 311 } 312 313 public String toString() { 314 StringBuilder sb = new StringBuilder(); 315 sb.append("ClassLoader:"); 316 for (URI uri : getURIs()) { 317 sb.append("[").append(uri).append("]"); 318 } 319 return sb.toString(); 320 } 321}