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