001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.vfs2.impl; 018 019import java.io.IOException; 020import java.net.URL; 021import java.security.CodeSource; 022import java.security.Permission; 023import java.security.PermissionCollection; 024import java.security.Permissions; 025import java.security.SecureClassLoader; 026import java.security.cert.Certificate; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.Enumeration; 030import java.util.Iterator; 031import java.util.List; 032import java.util.jar.Attributes; 033import java.util.jar.Attributes.Name; 034 035import org.apache.commons.vfs2.FileObject; 036import org.apache.commons.vfs2.FileSystemException; 037import org.apache.commons.vfs2.FileSystemManager; 038import org.apache.commons.vfs2.NameScope; 039 040/** 041 * A class loader that can load classes and resources from a search path. 042 * <p> 043 * The search path can consist of VFS FileObjects referring both to folders and JAR files. Any FileObject of type 044 * FileType.FILE is assumed to be a JAR and is opened by creating a layered file system with the "jar" scheme. 045 * <p> 046 * TODO - Test this with signed Jars and a SecurityManager. 047 * 048 * @see FileSystemManager#createFileSystem 049 */ 050public class VFSClassLoader extends SecureClassLoader { 051 private final ArrayList<FileObject> resources = new ArrayList<>(); 052 053 /** 054 * Constructors a new VFSClassLoader for the given file. 055 * 056 * @param file the file to load the classes and resources from. 057 * @param manager the FileManager to use when trying create a layered Jar file system. 058 * @throws FileSystemException if an error occurs. 059 */ 060 public VFSClassLoader(final FileObject file, final FileSystemManager manager) throws FileSystemException { 061 this(new FileObject[] { file }, manager, null); 062 } 063 064 /** 065 * Constructors a new VFSClassLoader for the given file. 066 * 067 * @param file the file to load the classes and resources from. 068 * @param manager the FileManager to use when trying create a layered Jar file system. 069 * @param parent the parent class loader for delegation. 070 * @throws FileSystemException if an error occurs. 071 */ 072 public VFSClassLoader(final FileObject file, final FileSystemManager manager, final ClassLoader parent) 073 throws FileSystemException { 074 this(new FileObject[] { file }, manager, parent); 075 } 076 077 /** 078 * Constructors a new VFSClassLoader for the given files. The files will be searched in the order specified. 079 * 080 * @param files the files to load the classes and resources from. 081 * @param manager the FileManager to use when trying create a layered Jar file system. 082 * @throws FileSystemException if an error occurs. 083 */ 084 public VFSClassLoader(final FileObject[] files, final FileSystemManager manager) throws FileSystemException { 085 this(files, manager, null); 086 } 087 088 /** 089 * Constructors a new VFSClassLoader for the given FileObjects. The FileObjects will be searched in the order 090 * specified. 091 * 092 * @param files the FileObjects to load the classes and resources from. 093 * @param manager the FileManager to use when trying create a layered Jar file system. 094 * @param parent the parent class loader for delegation. 095 * @throws FileSystemException if an error occurs. 096 */ 097 public VFSClassLoader(final FileObject[] files, final FileSystemManager manager, final ClassLoader parent) 098 throws FileSystemException { 099 super(parent); 100 addFileObjects(manager, files); 101 } 102 103 /** 104 * Provide access to the file objects this class loader represents. 105 * 106 * @return An array of FileObjects. 107 * @since 2.0 108 */ 109 public FileObject[] getFileObjects() { 110 return resources.toArray(new FileObject[resources.size()]); 111 } 112 113 /** 114 * Appends the specified FileObjects to the list of FileObjects to search for classes and resources. 115 * 116 * @param manager The FileSystemManager. 117 * @param files the FileObjects to append to the search path. 118 * @throws FileSystemException if an error occurs. 119 */ 120 private void addFileObjects(final FileSystemManager manager, final FileObject[] files) throws FileSystemException { 121 for (FileObject file : files) { 122 if (!file.exists()) { 123 // Does not exist - skip 124 continue; 125 } 126 127 // TODO - use federation instead 128 if (manager.canCreateFileSystem(file)) { 129 // Use contents of the file 130 file = manager.createFileSystem(file); 131 } 132 133 resources.add(file); 134 } 135 } 136 137 /** 138 * Finds and loads the class with the specified name from the search path. 139 * 140 * @throws ClassNotFoundException if the class is not found. 141 */ 142 @Override 143 protected Class<?> findClass(final String name) throws ClassNotFoundException { 144 try { 145 final String path = name.replace('.', '/').concat(".class"); 146 final Resource res = loadResource(path); 147 if (res == null) { 148 throw new ClassNotFoundException(name); 149 } 150 return defineClass(name, res); 151 } catch (final IOException ioe) { 152 throw new ClassNotFoundException(name, ioe); 153 } 154 } 155 156 /** 157 * Loads and verifies the class with name and located with res. 158 */ 159 private Class<?> defineClass(final String name, final Resource res) throws IOException { 160 final URL url = res.getCodeSourceURL(); 161 final String pkgName = res.getPackageName(); 162 if (pkgName != null) { 163 final Package pkg = getPackage(pkgName); 164 if (pkg != null) { 165 if (pkg.isSealed()) { 166 if (!pkg.isSealed(url)) { 167 throw new FileSystemException("vfs.impl/pkg-sealed-other-url", pkgName); 168 } 169 } else { 170 if (isSealed(res)) { 171 throw new FileSystemException("vfs.impl/pkg-sealing-unsealed", pkgName); 172 } 173 } 174 } else { 175 definePackage(pkgName, res); 176 } 177 } 178 179 final byte[] bytes = res.getBytes(); 180 final Certificate[] certs = res.getFileObject().getContent().getCertificates(); 181 final CodeSource cs = new CodeSource(url, certs); 182 return defineClass(name, bytes, 0, bytes.length, cs); 183 } 184 185 /** 186 * Returns true if the we should seal the package where res resides. 187 */ 188 private boolean isSealed(final Resource res) throws FileSystemException { 189 final String sealed = res.getPackageAttribute(Attributes.Name.SEALED); 190 return "true".equalsIgnoreCase(sealed); 191 } 192 193 /** 194 * Reads attributes for the package and defines it. 195 */ 196 private Package definePackage(final String name, final Resource res) throws FileSystemException { 197 // TODO - check for MANIFEST_ATTRIBUTES capability first 198 final String specTitle = res.getPackageAttribute(Name.SPECIFICATION_TITLE); 199 final String specVendor = res.getPackageAttribute(Attributes.Name.SPECIFICATION_VENDOR); 200 final String specVersion = res.getPackageAttribute(Name.SPECIFICATION_VERSION); 201 final String implTitle = res.getPackageAttribute(Name.IMPLEMENTATION_TITLE); 202 final String implVendor = res.getPackageAttribute(Name.IMPLEMENTATION_VENDOR); 203 final String implVersion = res.getPackageAttribute(Name.IMPLEMENTATION_VERSION); 204 205 final URL sealBase; 206 if (isSealed(res)) { 207 sealBase = res.getCodeSourceURL(); 208 } else { 209 sealBase = null; 210 } 211 212 return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); 213 } 214 215 /** 216 * Calls super.getPermissions both for the code source and also adds the permissions granted to the parent layers. 217 * 218 * @param cs the CodeSource. 219 * @return The PermissionCollections. 220 */ 221 @Override 222 protected PermissionCollection getPermissions(final CodeSource cs) { 223 try { 224 final String url = cs.getLocation().toString(); 225 final FileObject file = lookupFileObject(url); 226 if (file == null) { 227 return super.getPermissions(cs); 228 } 229 230 final FileObject parentLayer = file.getFileSystem().getParentLayer(); 231 if (parentLayer == null) { 232 return super.getPermissions(cs); 233 } 234 235 final Permissions combi = new Permissions(); 236 PermissionCollection permCollect = super.getPermissions(cs); 237 copyPermissions(permCollect, combi); 238 239 for (FileObject parent = parentLayer; parent != null; parent = parent.getFileSystem().getParentLayer()) { 240 final CodeSource parentcs = new CodeSource(parent.getURL(), parent.getContent().getCertificates()); 241 permCollect = super.getPermissions(parentcs); 242 copyPermissions(permCollect, combi); 243 } 244 245 return combi; 246 } catch (final FileSystemException fse) { 247 throw new SecurityException(fse.getMessage()); 248 } 249 } 250 251 /** 252 * Copies the permissions from src to dest. 253 * 254 * @param src The source PermissionCollection. 255 * @param dest The destination PermissionCollection. 256 */ 257 protected void copyPermissions(final PermissionCollection src, final PermissionCollection dest) { 258 for (final Enumeration<Permission> elem = src.elements(); elem.hasMoreElements();) { 259 final Permission permission = elem.nextElement(); 260 dest.add(permission); 261 } 262 } 263 264 /** 265 * Does a reverse lookup to find the FileObject when we only have the URL. 266 */ 267 private FileObject lookupFileObject(final String name) { 268 final Iterator<FileObject> it = resources.iterator(); 269 while (it.hasNext()) { 270 final FileObject object = it.next(); 271 if (name.equals(object.getName().getURI())) { 272 return object; 273 } 274 } 275 return null; 276 } 277 278 /** 279 * Finds the resource with the specified name from the search path. This returns null if the resource is not found. 280 * 281 * @param name The resource name. 282 * @return The URL that matches the resource. 283 */ 284 @Override 285 protected URL findResource(final String name) { 286 try { 287 final Resource res = loadResource(name); 288 if (res != null) { 289 return res.getURL(); 290 } 291 return null; 292 } catch (final Exception ignored) { 293 return null; // TODO: report? 294 } 295 } 296 297 /** 298 * Returns an Enumeration of all the resources in the search path with the specified name. 299 * <p> 300 * Gets called from {@link ClassLoader#getResources(String)} after parent class loader was questioned. 301 * 302 * @param name The resources to find. 303 * @return An Enumeration of the resources associated with the name. 304 * @throws FileSystemException if an error occurs. 305 */ 306 @Override 307 protected Enumeration<URL> findResources(final String name) throws IOException { 308 final List<URL> result = new ArrayList<>(2); 309 310 for (final FileObject baseFile : resources) { 311 final FileObject file = baseFile.resolveFile(name, NameScope.DESCENDENT_OR_SELF); 312 if (file.exists()) { 313 result.add(new Resource(name, baseFile, file).getURL()); 314 } 315 } 316 317 return Collections.enumeration(result); 318 } 319 320 /** 321 * Searches through the search path of for the first class or resource with specified name. 322 * 323 * @param name The resource to load. 324 * @return The Resource. 325 * @throws FileSystemException if an error occurs. 326 */ 327 private Resource loadResource(final String name) throws FileSystemException { 328 for (final FileObject baseFile : resources) { 329 final FileObject file = baseFile.resolveFile(name, NameScope.DESCENDENT_OR_SELF); 330 if (file.exists()) { 331 return new Resource(name, baseFile, file); 332 } 333 } 334 return null; 335 } 336}