1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.classloader

File URIClassLoader.java

 

Coverage histogram

../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

34
101
30
2
473
278
51
0.5
3.37
15
1.7

Classes

Class Line # Actions
URIClassLoader 74 80 0% 38 58
0.550387655%
URIClassLoader.URIResourceFinder 404 21 0% 13 5
0.861111186.1%
 

Contributing tests

This file is covered by 83 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.classloader;
21   
22    import java.io.File;
23    import java.io.IOException;
24    import java.net.MalformedURLException;
25    import java.net.URI;
26    import java.net.URL;
27    import java.net.URLStreamHandlerFactory;
28    import java.security.AccessControlContext;
29    import java.security.AccessController;
30    import java.security.CodeSource;
31    import java.security.PrivilegedAction;
32    import java.security.PrivilegedExceptionAction;
33    import java.util.Enumeration;
34    import java.util.List;
35    import java.util.jar.Attributes;
36    import java.util.jar.Attributes.Name;
37    import java.util.jar.Manifest;
38   
39    import org.xwiki.classloader.internal.ResourceLoader;
40   
41    import edu.emory.mathcs.util.classloader.ResourceFinder;
42    import edu.emory.mathcs.util.classloader.ResourceHandle;
43   
44    /**
45    * Equivalent of java.net.URLClassloader but without bugs related to ill-formed URLs and with customizable JAR caching
46    * policy. The standard URLClassLoader accepts URLs containing spaces and other characters which are forbidden in the
47    * URI syntax, according to the RFC 2396. As a workaround to this problem, Java escapes and un-escapes URLs in various
48    * arbitrary places; however, this is inconsistent and leads to numerous problems with URLs referring to local files
49    * with spaces in the path. SUN acknowledges the problem, but refuses to modify the behavior for compatibility reasons;
50    * see Java Bug Parade 4273532, 4466485.
51    * <p>
52    * Additionally, the JAR caching policy used by URLClassLoader is system-wide and inflexible: once downloaded JAR files
53    * are never re-downloaded, even if one creates a fresh instance of the class loader that happens to have the same URL
54    * in its search path. In fact, that policy is a security vulnerability: it is possible to crash any URL class loader,
55    * thus affecting potentially separate part of the system, by creating URL connection to one of the URLs of that class
56    * loader search path and closing the associated JAR file. See Java Bug Parade 4405789, 4388666, 4639900.
57    * <p>
58    * This class avoids these problems by 1) using URIs instead of URLs for the search path (thus enforcing strict syntax
59    * conformance and defining precise escaping semantics), and 2) using custom URLStreamHandler which ensures
60    * per-classloader JAR caching policy.
61    * <p>
62    * Originally written by Dawid Kurzyniec and released to the public domain, as explained at
63    * http://creativecommons.org/licenses/publicdomain
64    * </p>
65    * <p>
66    * Source: http://dcl.mathcs.emory.edu/php/loadPage.php?content=util/features.html#classloading
67    * </p>
68    *
69    * @see java.io.File#toURL
70    * @see java.io.File#toURI
71    * @version $Id: e46894b532590398ea20788ca36328642fa25a28 $d
72    * @since 2.0.1
73    */
 
74    public class URIClassLoader extends ExtendedURLClassLoader
75    {
76    final URIResourceFinder finder;
77   
78    final AccessControlContext acc;
79   
80    /**
81    * Creates URIClassLoader with the specified search path.
82    *
83    * @param uris the search path
84    */
 
85  0 toggle public URIClassLoader(URI[] uris)
86    {
87  0 this(uris, (URLStreamHandlerFactory) null);
88    }
89   
90    /**
91    * Creates URIClassLoader with the specified search path.
92    *
93    * @param uris the search path
94    * @param handlerFactory the URLStreamHandlerFactory to use when creating URLs
95    */
 
96  1 toggle public URIClassLoader(URI[] uris, URLStreamHandlerFactory handlerFactory)
97    {
98  1 super(new URL[0]);
99  1 this.finder = new URIResourceFinder(uris, handlerFactory);
100  1 this.acc = AccessController.getContext();
101    }
102   
103    /**
104    * Creates URIClassLoader with the specified search path and parent class loader.
105    *
106    * @param uris the search path
107    * @param parent the parent class loader
108    */
 
109  13867 toggle public URIClassLoader(URI[] uris, ClassLoader parent)
110    {
111  13864 this(uris, parent, null);
112    }
113   
114    /**
115    * Creates URIClassLoader with the specified search path and parent class loader.
116    *
117    * @param uris the search path
118    * @param parent the parent class loader.
119    * @param handlerFactory the URLStreamHandlerFactory to use when creating URLs
120    */
 
121  20152 toggle public URIClassLoader(URI[] uris, ClassLoader parent, URLStreamHandlerFactory handlerFactory)
122    {
123  20151 super(new URL[0], parent);
124  20147 this.finder = new URIResourceFinder(uris, handlerFactory);
125  20107 this.acc = AccessController.getContext();
126    }
127   
128    /**
129    * Add specified URI at the end of the search path.
130    *
131    * @param uri the URI to add
132    */
 
133  0 toggle protected void addURI(URI uri)
134    {
135  0 this.finder.addURI(uri);
136    }
137   
138    /**
139    * Add specified URL at the end of the search path.
140    *
141    * @param url the URL to add
142    */
 
143  137 toggle @Override
144    public void addURL(URL url)
145    {
146  137 this.finder.addURI(URI.create(url.toExternalForm()));
147    }
148   
149    /**
150    * Add specified URLs at the end of the search path.
151    *
152    * @param urls the URLs to add
153    */
 
154  0 toggle @Override
155    public void addURLs(List<URL> urls)
156    {
157  0 for (URL url : urls) {
158  0 addURL(url);
159    }
160    }
161   
 
162  8 toggle @Override
163    public URL[] getURLs()
164    {
165  8 return this.finder.getUrls().clone();
166    }
167   
168    /**
169    * Finds and loads the class with the specified name.
170    *
171    * @param name the name of the class
172    * @return the resulting class
173    * @exception ClassNotFoundException if the class could not be found
174    */
 
175  45770 toggle @Override
176    protected Class<?> findClass(final String name) throws ClassNotFoundException
177    {
178  45770 try {
179  45770 return AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>()
180    {
 
181  45770 toggle @Override
182    public Class<?> run() throws ClassNotFoundException
183    {
184  45770 String path = name.replace('.', '/').concat(".class");
185  45770 ResourceHandle h = URIClassLoader.this.finder.getResource(path);
186  45770 if (h != null) {
187  338 try {
188  338 return defineClass(name, h);
189    } catch (IOException e) {
190  0 throw new ClassNotFoundException(name, e);
191    }
192    } else {
193  45432 throw new ClassNotFoundException(name);
194    }
195    }
196    }, this.acc);
197    } catch (java.security.PrivilegedActionException pae) {
198  45432 throw (ClassNotFoundException) pae.getException();
199    }
200    }
201   
 
202  338 toggle protected Class<?> defineClass(String name, ResourceHandle h) throws IOException
203    {
204  338 int i = name.lastIndexOf('.');
205  338 URL url = h.getCodeSourceURL();
206  338 if (i != -1) { // check package
207  336 String pkgname = name.substring(0, i);
208    // check if package already loaded
209  336 Package pkg = getPackage(pkgname);
210  336 Manifest man = h.getManifest();
211  336 if (pkg != null) {
212    // package found, so check package sealing
213  295 boolean ok;
214  295 if (pkg.isSealed()) {
215    // verify that code source URLs are the same
216  0 ok = pkg.isSealed(url);
217    } else {
218    // make sure we are not attempting to seal the package
219    // at this code source URL
220  295 ok = (man == null) || !isSealed(pkgname, man);
221    }
222  295 if (!ok) {
223  0 throw new SecurityException("sealing violation: " + name);
224    }
225    } else { // package not yet defined
226  41 if (man != null) {
227  41 definePackage(pkgname, man, url);
228    } else {
229  0 definePackage(pkgname, null, null, null, null, null, null, null);
230    }
231    }
232    }
233   
234    // now read the class bytes and define the class
235  338 byte[] b = h.getBytes();
236  338 java.security.cert.Certificate[] certs = h.getCertificates();
237  338 CodeSource cs = new CodeSource(url, certs);
238  338 return defineClass(name, b, 0, b.length, cs);
239    }
240   
241    /**
242    * returns true if the specified package name is sealed according to the given manifest.
243    */
 
244  295 toggle private boolean isSealed(String name, Manifest man)
245    {
246  295 String path = name.replace('.', '/').concat("/");
247  295 Attributes attr = man.getAttributes(path);
248  295 String sealed = null;
249  295 if (attr != null) {
250  0 sealed = attr.getValue(Name.SEALED);
251    }
252  295 if (sealed == null) {
253  ? if ((attr = man.getMainAttributes()) != null) {
254  295 sealed = attr.getValue(Name.SEALED);
255    }
256    }
257  295 return "true".equalsIgnoreCase(sealed);
258    }
259   
260    /**
261    * Finds the resource with the specified name.
262    *
263    * @param name the name of the resource
264    * @return a <code>URL</code> for the resource, or <code>null</code> if the resource could not be found.
265    */
 
266  9006 toggle @Override
267    public URL findResource(final String name)
268    {
269  9006 return AccessController.doPrivileged(new PrivilegedAction<URL>()
270    {
 
271  9006 toggle @Override
272    public URL run()
273    {
274  9006 return URIClassLoader.this.finder.findResource(name);
275    }
276    }, this.acc);
277    }
278   
279    /**
280    * Returns an Enumeration of URLs representing all of the resources having the specified name.
281    *
282    * @param name the resource name
283    * @exception IOException if an I/O exception occurs
284    * @return an <code>Enumeration</code> of <code>URL</code>s
285    */
 
286  21884 toggle @Override
287    public Enumeration<URL> findResources(final String name) throws IOException
288    {
289  21884 return AccessController.doPrivileged(new PrivilegedAction<Enumeration<URL>>()
290    {
 
291  21884 toggle @Override
292    public Enumeration<URL> run()
293    {
294  21884 return URIClassLoader.this.finder.findResources(name);
295    }
296    }, this.acc);
297    }
298   
299    /**
300    * Returns the absolute path name of a native library. The VM invokes this method to locate the native libraries
301    * that belong to classes loaded with this class loader. If this method returns <code>null</code>, the VM searches
302    * the library along the path specified as the <code>java.library.path</code> property. This method invoke
303    * {@link #getLibraryHandle} method to find handle of this library. If the handle is found and its URL protocol is
304    * "file", the system-dependent absolute library file path is returned. Otherwise this method returns null.
305    * <p>
306    * Subclasses can override this method to provide specific approaches in library searching.
307    *
308    * @param libname the library name
309    * @return the absolute path of the native library
310    * @see java.lang.System#loadLibrary(java.lang.String)
311    * @see java.lang.System#mapLibraryName(java.lang.String)
312    */
 
313  0 toggle @Override
314    protected String findLibrary(String libname)
315    {
316  0 ResourceHandle md = getLibraryHandle(libname);
317  0 if (md == null) {
318  0 return null;
319    }
320  0 URL url = md.getURL();
321  0 if (!"file".equals(url.getProtocol())) {
322  0 return null;
323    }
324  0 return new File(URI.create(url.toString())).getPath();
325    }
326   
327    /**
328    * Finds the ResourceHandle object for the class with the specified name. Unlike <code>findClass()</code>, this
329    * method does not load the class.
330    *
331    * @param name the name of the class
332    * @return the ResourceHandle of the class
333    */
 
334  0 toggle protected ResourceHandle getClassHandle(final String name)
335    {
336  0 String path = name.replace('.', '/').concat(".class");
337  0 return getResourceHandle(path);
338    }
339   
340    /**
341    * Finds the ResourceHandle object for the resource with the specified name.
342    *
343    * @param name the name of the resource
344    * @return the ResourceHandle of the resource
345    */
 
346  0 toggle protected ResourceHandle getResourceHandle(final String name)
347    {
348  0 return AccessController.doPrivileged(new PrivilegedAction<ResourceHandle>()
349    {
 
350  0 toggle @Override
351    public ResourceHandle run()
352    {
353  0 return URIClassLoader.this.finder.getResource(name);
354    }
355    }, this.acc);
356    }
357   
358    /**
359    * Finds the ResourceHandle object for the native library with the specified name. The library name must be
360    * '/'-separated path. The last part of this path is substituted by its system-dependent mapping (using
361    * {@link System#mapLibraryName(String)} method). Next, the <code>ResourceFinder</code> is used to look for the
362    * library as it was ordinary resource.
363    * <p>
364    * Subclasses can override this method to provide specific approaches in library searching.
365    *
366    * @param name the name of the library
367    * @return the ResourceHandle of the library
368    */
 
369  0 toggle protected ResourceHandle getLibraryHandle(final String name)
370    {
371  0 int idx = name.lastIndexOf('/');
372  0 String path;
373  0 String simplename;
374  0 if (idx == -1) {
375  0 path = "";
376  0 simplename = name;
377  0 } else if (idx == name.length() - 1) { // name.endsWith('/')
378  0 throw new IllegalArgumentException(name);
379    } else {
380  0 path = name.substring(0, idx + 1); // including '/'
381  0 simplename = name.substring(idx + 1);
382    }
383  0 return getResourceHandle(path + System.mapLibraryName(simplename));
384    }
385   
386    /**
387    * Returns an Enumeration of ResourceHandle objects representing all of the resources having the specified name.
388    *
389    * @param name the name of the resource
390    * @return the ResourceHandle of the resource
391    */
 
392  0 toggle protected Enumeration<ResourceHandle> getResourceHandles(final String name)
393    {
394  0 return AccessController.doPrivileged(new PrivilegedAction<Enumeration<ResourceHandle>>()
395    {
 
396  0 toggle @Override
397    public Enumeration<ResourceHandle> run()
398    {
399  0 return URIClassLoader.this.finder.getResources(name);
400    }
401    }, this.acc);
402    }
403   
 
404    private static class URIResourceFinder implements ResourceFinder
405    {
406    URL[] urls;
407   
408    final ResourceLoader loader;
409   
410    final URLStreamHandlerFactory handlerFactory;
411   
 
412  20151 toggle public URIResourceFinder(URI[] uris, URLStreamHandlerFactory handlerFactory)
413    {
414  20151 this.handlerFactory = handlerFactory;
415  20087 try {
416  20113 this.loader =
417  20122 new ResourceLoader(handlerFactory != null ? handlerFactory.createURLStreamHandler("jar") : null);
418  20107 URL[] urls = new URL[uris.length];
419  20132 for (int i = 0; i < uris.length; i++) {
420  7 urls[i] = new URL(null, uris[i].toString(),
421  7 handlerFactory != null ? handlerFactory.createURLStreamHandler(uris[i].getScheme()) : null);
422    }
423  20105 this.urls = urls;
424    } catch (MalformedURLException e) {
425  0 throw new IllegalArgumentException(e.getMessage());
426    }
427    }
428   
 
429  137 toggle public synchronized void addURI(URI uri)
430    {
431  137 try {
432  137 URL url = new URL(null, uri.toString(), this.handlerFactory != null ? this.handlerFactory
433    .createURLStreamHandler(uri.getScheme()) : null);
434  137 int len = this.urls.length;
435  137 URL[] urls = new URL[len + 1];
436  137 System.arraycopy(this.urls, 0, urls, 0, len);
437  137 urls[len] = url;
438  137 this.urls = urls;
439    } catch (MalformedURLException e) {
440  0 throw new IllegalArgumentException(e.getMessage());
441    }
442    }
443   
 
444  76668 toggle private synchronized URL[] getUrls()
445    {
446  76668 return this.urls;
447    }
448   
 
449  45770 toggle @Override
450    public ResourceHandle getResource(String name)
451    {
452  45770 return this.loader.getResource(getUrls(), name);
453    }
454   
 
455  0 toggle @Override
456    public Enumeration<ResourceHandle> getResources(String name)
457    {
458  0 return this.loader.getResources(getUrls(), name);
459    }
460   
 
461  9006 toggle @Override
462    public URL findResource(String name)
463    {
464  9006 return this.loader.findResource(getUrls(), name);
465    }
466   
 
467  21884 toggle @Override
468    public Enumeration<URL> findResources(String name)
469    {
470  21884 return this.loader.findResources(getUrls(), name);
471    }
472    }
473    }