1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.misc; 27 28 import java.util.*; 29 import java.util.jar.JarFile; 30 import sun.misc.JarIndex; 31 import sun.misc.InvalidJarIndexException; 32 import sun.net.www.ParseUtil; 33 import java.util.zip.ZipEntry; 34 import java.util.jar.JarEntry; 35 import java.util.jar.Manifest; 36 import java.util.jar.Attributes; 37 import java.util.jar.Attributes.Name; 38 import java.net.JarURLConnection; 39 import java.net.MalformedURLException; 40 import java.net.URL; 41 import java.net.URLClassLoader; 42 import java.net.URLConnection; 43 import java.net.HttpURLConnection; 44 import java.net.URLStreamHandler; 45 import java.net.URLStreamHandlerFactory; 46 import java.io.*; 47 import java.security.AccessController; 48 import java.security.AccessControlException; 49 import java.security.CodeSigner; 50 import java.security.Permission; 51 import java.security.PrivilegedAction; 52 import java.security.PrivilegedExceptionAction; 53 import java.security.cert.Certificate; 54 import sun.misc.FileURLMapper; 55 import sun.net.util.URLUtil; 56 import sun.security.action.GetPropertyAction; 57 58 /** 59 * This class is used to maintain a search path of URLs for loading classes 60 * and resources from both JAR files and directories. 61 * 62 * @author David Connelly 63 */ 64 public class URLClassPath { 65 final static String USER_AGENT_JAVA_VERSION = "UA-Java-Version"; 66 final static String JAVA_VERSION; 67 private static final boolean DEBUG; 68 private static final boolean DEBUG_LOOKUP_CACHE; 69 private static final boolean DISABLE_JAR_CHECKING; 70 71 static { 72 JAVA_VERSION = java.security.AccessController.doPrivileged( 73 new GetPropertyAction("java.version")); 74 DEBUG = (java.security.AccessController.doPrivileged( 75 new GetPropertyAction("sun.misc.URLClassPath.debug")) != null); 76 DEBUG_LOOKUP_CACHE = (java.security.AccessController.doPrivileged( 77 new GetPropertyAction("sun.misc.URLClassPath.debugLookupCache")) != null); 78 String p = java.security.AccessController.doPrivileged( 79 new GetPropertyAction("sun.misc.URLClassPath.disableJarChecking")); 80 DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false; 81 } 82 83 /* The original search path of URLs. */ 84 private ArrayList<URL> path = new ArrayList<URL>(); 85 86 /* The stack of unopened URLs */ 87 Stack<URL> urls = new Stack<URL>(); 88 89 /* The resulting search path of Loaders */ 90 ArrayList<Loader> loaders = new ArrayList<Loader>(); 91 92 /* Map of each URL opened to its corresponding Loader */ 93 HashMap<String, Loader> lmap = new HashMap<String, Loader>(); 94 95 /* The jar protocol handler to use when creating new URLs */ 96 private URLStreamHandler jarHandler; 97 98 /* Whether this URLClassLoader has been closed yet */ 99 private boolean closed = false; 100 101 /** 102 * Creates a new URLClassPath for the given URLs. The URLs will be 103 * searched in the order specified for classes and resources. A URL 104 * ending with a '/' is assumed to refer to a directory. Otherwise, 105 * the URL is assumed to refer to a JAR file. 106 * 107 * @param urls the directory and JAR file URLs to search for classes 108 * and resources 109 * @param factory the URLStreamHandlerFactory to use when creating new URLs 110 */ 111 public URLClassPath(URL[] urls, URLStreamHandlerFactory factory) { 112 for (int i = 0; i < urls.length; i++) { 113 path.add(urls[i]); 114 } 115 push(urls); 116 if (factory != null) { 117 jarHandler = factory.createURLStreamHandler("jar"); 118 } 119 } 120 121 public URLClassPath(URL[] urls) { 122 this(urls, null); 123 } 124 125 public synchronized List<IOException> closeLoaders() { 126 if (closed) { 127 return Collections.emptyList(); 128 } 129 List<IOException> result = new LinkedList<IOException>(); 130 for (Loader loader : loaders) { 131 try { 132 loader.close(); 133 } catch (IOException e) { 134 result.add (e); 135 } 136 } 137 closed = true; 138 return result; 139 } 140 141 /** 142 * Appends the specified URL to the search path of directory and JAR 143 * file URLs from which to load classes and resources. 144 * <p> 145 * If the URL specified is null or is already in the list of 146 * URLs, then invoking this method has no effect. 147 */ 148 public synchronized void addURL(URL url) { 149 if (closed) 150 return; 151 synchronized (urls) { 152 if (url == null || path.contains(url)) 153 return; 154 155 urls.add(0, url); 156 path.add(url); 157 158 if (lookupCacheURLs != null) { 159 // The lookup cache is no longer valid, since getLookupCache() 160 // does not consider the newly added url. 161 disableAllLookupCaches(); 162 } 163 } 164 } 165 166 /** 167 * Returns the original search path of URLs. 168 */ 169 public URL[] getURLs() { 170 synchronized (urls) { 171 return path.toArray(new URL[path.size()]); 172 } 173 } 174 175 /** 176 * Finds the resource with the specified name on the URL search path 177 * or null if not found or security check fails. 178 * 179 * @param name the name of the resource 180 * @param check whether to perform a security check 181 * @return a <code>URL</code> for the resource, or <code>null</code> 182 * if the resource could not be found. 183 */ 184 public URL findResource(String name, boolean check) { 185 Loader loader; 186 int[] cache = getLookupCache(name); 187 for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) { 188 URL url = loader.findResource(name, check); 189 if (url != null) { 190 return url; 191 } 192 } 193 return null; 194 } 195 196 /** 197 * Finds the first Resource on the URL search path which has the specified 198 * name. Returns null if no Resource could be found. 199 * 200 * @param name the name of the Resource 201 * @param check whether to perform a security check 202 * @return the Resource, or null if not found 203 */ 204 public Resource getResource(String name, boolean check) { 205 if (DEBUG) { 206 System.err.println("URLClassPath.getResource(\"" + name + "\")"); 207 } 208 209 Loader loader; 210 int[] cache = getLookupCache(name); 211 for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) { 212 Resource res = loader.getResource(name, check); 213 if (res != null) { 214 return res; 215 } 216 } 217 return null; 218 } 219 220 /** 221 * Finds all resources on the URL search path with the given name. 222 * Returns an enumeration of the URL objects. 223 * 224 * @param name the resource name 225 * @return an Enumeration of all the urls having the specified name 226 */ 227 public Enumeration<URL> findResources(final String name, 228 final boolean check) { 229 return new Enumeration<URL>() { 230 private int index = 0; 231 private int[] cache = getLookupCache(name); 232 private URL url = null; 233 234 private boolean next() { 235 if (url != null) { 236 return true; 237 } else { 238 Loader loader; 239 while ((loader = getNextLoader(cache, index++)) != null) { 240 url = loader.findResource(name, check); 241 if (url != null) { 242 return true; 243 } 244 } 245 return false; 246 } 247 } 248 249 public boolean hasMoreElements() { 250 return next(); 251 } 252 253 public URL nextElement() { 254 if (!next()) { 255 throw new NoSuchElementException(); 256 } 257 URL u = url; 258 url = null; 259 return u; 260 } 261 }; 262 } 263 264 public Resource getResource(String name) { 265 return getResource(name, true); 266 } 267 268 /** 269 * Finds all resources on the URL search path with the given name. 270 * Returns an enumeration of the Resource objects. 271 * 272 * @param name the resource name 273 * @return an Enumeration of all the resources having the specified name 274 */ 275 public Enumeration<Resource> getResources(final String name, 276 final boolean check) { 277 return new Enumeration<Resource>() { 278 private int index = 0; 279 private int[] cache = getLookupCache(name); 280 private Resource res = null; 281 282 private boolean next() { 283 if (res != null) { 284 return true; 285 } else { 286 Loader loader; 287 while ((loader = getNextLoader(cache, index++)) != null) { 288 res = loader.getResource(name, check); 289 if (res != null) { 290 return true; 291 } 292 } 293 return false; 294 } 295 } 296 297 public boolean hasMoreElements() { 298 return next(); 299 } 300 301 public Resource nextElement() { 302 if (!next()) { 303 throw new NoSuchElementException(); 304 } 305 Resource r = res; 306 res = null; 307 return r; 308 } 309 }; 310 } 311 312 public Enumeration<Resource> getResources(final String name) { 313 return getResources(name, true); 314 } 315 316 private static volatile boolean lookupCacheEnabled 317 = "true".equals(VM.getSavedProperty("sun.cds.enableSharedLookupCache")); 318 private URL[] lookupCacheURLs; 319 private ClassLoader lookupCacheLoader; 320 321 synchronized void initLookupCache(ClassLoader loader) { 322 if ((lookupCacheURLs = getLookupCacheURLs(loader)) != null) { 323 lookupCacheLoader = loader; 324 } else { 325 // This JVM instance does not support lookup cache. 326 disableAllLookupCaches(); 327 } 328 } 329 330 static void disableAllLookupCaches() { 331 lookupCacheEnabled = false; 332 } 333 334 private static native URL[] getLookupCacheURLs(ClassLoader loader); 335 private static native int[] getLookupCacheForClassLoader(ClassLoader loader, 336 String name); 337 private static native boolean knownToNotExist0(ClassLoader loader, 338 String className); 339 340 synchronized boolean knownToNotExist(String className) { 341 if (lookupCacheURLs != null && lookupCacheEnabled) { 342 return knownToNotExist0(lookupCacheLoader, className); 343 } 344 345 // Don't know if this class exists or not -- need to do a full search. 346 return false; 347 } 348 349 /** 350 * Returns an array of the index to lookupCacheURLs that may 351 * contain the specified resource. The values in the returned 352 * array are in strictly ascending order and must be a valid index 353 * to lookupCacheURLs array. 354 * 355 * This method returns an empty array if the specified resource 356 * cannot be found in this URLClassPath. If there is no lookup 357 * cache or it's disabled, this method returns null and the lookup 358 * should search the entire classpath. 359 * 360 * Example: if lookupCacheURLs contains {a.jar, b.jar, c.jar, d.jar} 361 * and package "foo" only exists in a.jar and c.jar, 362 * getLookupCache("foo/Bar.class") will return {0, 2} 363 * 364 * @param name the resource name 365 * @return an array of the index to lookupCacheURLs that may contain the 366 * specified resource; or null if no lookup cache is used. 367 */ 368 private synchronized int[] getLookupCache(String name) { 369 if (lookupCacheURLs == null || !lookupCacheEnabled) { 370 return null; 371 } 372 373 int[] cache = getLookupCacheForClassLoader(lookupCacheLoader, name); 374 if (cache != null && cache.length > 0) { 375 int maxindex = cache[cache.length - 1]; // cache[] is strictly ascending. 376 if (!ensureLoaderOpened(maxindex)) { 377 if (DEBUG_LOOKUP_CACHE) { 378 System.out.println("Expanded loaders FAILED " + 379 loaders.size() + " for maxindex=" + maxindex); 380 } 381 return null; 382 } 383 } 384 385 return cache; 386 } 387 388 private boolean ensureLoaderOpened(int index) { 389 if (loaders.size() <= index) { 390 // Open all Loaders up to, and including, index 391 if (getLoader(index) == null) { 392 return false; 393 } 394 if (!lookupCacheEnabled) { 395 // cache was invalidated as the result of the above call. 396 return false; 397 } 398 if (DEBUG_LOOKUP_CACHE) { 399 System.out.println("Expanded loaders " + loaders.size() + 400 " to index=" + index); 401 } 402 } 403 return true; 404 } 405 406 /* 407 * The CLASS-PATH attribute was expanded by the VM when building 408 * the resource lookup cache in the same order as the getLoader 409 * method does. This method validates if the URL from the lookup 410 * cache matches the URL of the Loader at the given index; 411 * otherwise, this method disables the lookup cache. 412 */ 413 private synchronized void validateLookupCache(int index, 414 String urlNoFragString) { 415 if (lookupCacheURLs != null && lookupCacheEnabled) { 416 if (index < lookupCacheURLs.length && 417 urlNoFragString.equals( 418 URLUtil.urlNoFragString(lookupCacheURLs[index]))) { 419 return; 420 } 421 if (DEBUG || DEBUG_LOOKUP_CACHE) { 422 System.out.println("WARNING: resource lookup cache invalidated " 423 + "for lookupCacheLoader at " + index); 424 } 425 disableAllLookupCaches(); 426 } 427 } 428 429 /** 430 * Returns the next Loader that may contain the resource to 431 * lookup. If the given cache is null, return loaders.get(index) 432 * that may be lazily created; otherwise, cache[index] is the next 433 * Loader that may contain the resource to lookup and so returns 434 * loaders.get(cache[index]). 435 * 436 * If cache is non-null, loaders.get(cache[index]) must be present. 437 * 438 * @param cache lookup cache. If null, search the entire class path 439 * @param index index to the given cache array; or to the loaders list. 440 */ 441 private synchronized Loader getNextLoader(int[] cache, int index) { 442 if (closed) { 443 return null; 444 } 445 if (cache != null) { 446 if (index < cache.length) { 447 Loader loader = loaders.get(cache[index]); 448 if (DEBUG_LOOKUP_CACHE) { 449 System.out.println("HASCACHE: Loading from : " + cache[index] 450 + " = " + loader.getBaseURL()); 451 } 452 return loader; 453 } else { 454 return null; // finished iterating over cache[] 455 } 456 } else { 457 return getLoader(index); 458 } 459 } 460 461 /* 462 * Returns the Loader at the specified position in the URL search 463 * path. The URLs are opened and expanded as needed. Returns null 464 * if the specified index is out of range. 465 */ 466 private synchronized Loader getLoader(int index) { 467 if (closed) { 468 return null; 469 } 470 // Expand URL search path until the request can be satisfied 471 // or the URL stack is empty. 472 while (loaders.size() < index + 1) { 473 // Pop the next URL from the URL stack 474 URL url; 475 synchronized (urls) { 476 if (urls.empty()) { 477 return null; 478 } else { 479 url = urls.pop(); 480 } 481 } 482 // Skip this URL if it already has a Loader. (Loader 483 // may be null in the case where URL has not been opened 484 // but is referenced by a JAR index.) 485 String urlNoFragString = URLUtil.urlNoFragString(url); 486 if (lmap.containsKey(urlNoFragString)) { 487 continue; 488 } 489 // Otherwise, create a new Loader for the URL. 490 Loader loader; 491 try { 492 loader = getLoader(url); 493 // If the loader defines a local class path then add the 494 // URLs to the list of URLs to be opened. 495 URL[] urls = loader.getClassPath(); 496 if (urls != null) { 497 push(urls); 498 } 499 } catch (IOException e) { 500 // Silently ignore for now... 501 continue; 502 } 503 // Finally, add the Loader to the search path. 504 validateLookupCache(loaders.size(), urlNoFragString); 505 loaders.add(loader); 506 lmap.put(urlNoFragString, loader); 507 } 508 if (DEBUG_LOOKUP_CACHE) { 509 System.out.println("NOCACHE: Loading from : " + index ); 510 } 511 return loaders.get(index); 512 } 513 514 /* 515 * Returns the Loader for the specified base URL. 516 */ 517 private Loader getLoader(final URL url) throws IOException { 518 try { 519 return java.security.AccessController.doPrivileged( 520 new java.security.PrivilegedExceptionAction<Loader>() { 521 public Loader run() throws IOException { 522 String file = url.getFile(); 523 if (file != null && file.endsWith("/")) { 524 if ("file".equals(url.getProtocol())) { 525 return new FileLoader(url); 526 } else { 527 return new Loader(url); 528 } 529 } else { 530 return new JarLoader(url, jarHandler, lmap); 531 } 532 } 533 }); 534 } catch (java.security.PrivilegedActionException pae) { 535 throw (IOException)pae.getException(); 536 } 537 } 538 539 /* 540 * Pushes the specified URLs onto the list of unopened URLs. 541 */ 542 private void push(URL[] us) { 543 synchronized (urls) { 544 for (int i = us.length - 1; i >= 0; --i) { 545 urls.push(us[i]); 546 } 547 } 548 } 549 550 /** 551 * Convert class path specification into an array of file URLs. 552 * 553 * The path of the file is encoded before conversion into URL 554 * form so that reserved characters can safely appear in the path. 555 */ 556 public static URL[] pathToURLs(String path) { 557 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); 558 URL[] urls = new URL[st.countTokens()]; 559 int count = 0; 560 while (st.hasMoreTokens()) { 561 File f = new File(st.nextToken()); 562 try { 563 f = new File(f.getCanonicalPath()); 564 } catch (IOException x) { 565 // use the non-canonicalized filename 566 } 567 try { 568 urls[count++] = ParseUtil.fileToEncodedURL(f); 569 } catch (IOException x) { } 570 } 571 572 if (urls.length != count) { 573 URL[] tmp = new URL[count]; 574 System.arraycopy(urls, 0, tmp, 0, count); 575 urls = tmp; 576 } 577 return urls; 578 } 579 580 /* 581 * Check whether the resource URL should be returned. 582 * Return null on security check failure. 583 * Called by java.net.URLClassLoader. 584 */ 585 public URL checkURL(URL url) { 586 try { 587 check(url); 588 } catch (Exception e) { 589 return null; 590 } 591 592 return url; 593 } 594 595 /* 596 * Check whether the resource URL should be returned. 597 * Throw exception on failure. 598 * Called internally within this file. 599 */ 600 static void check(URL url) throws IOException { 601 SecurityManager security = System.getSecurityManager(); 602 if (security != null) { 603 URLConnection urlConnection = url.openConnection(); 604 Permission perm = urlConnection.getPermission(); 605 if (perm != null) { 606 try { 607 security.checkPermission(perm); 608 } catch (SecurityException se) { 609 // fallback to checkRead/checkConnect for pre 1.2 610 // security managers 611 if ((perm instanceof java.io.FilePermission) && 612 perm.getActions().indexOf("read") != -1) { 613 security.checkRead(perm.getName()); 614 } else if ((perm instanceof 615 java.net.SocketPermission) && 616 perm.getActions().indexOf("connect") != -1) { 617 URL locUrl = url; 618 if (urlConnection instanceof JarURLConnection) { 619 locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); 620 } 621 security.checkConnect(locUrl.getHost(), 622 locUrl.getPort()); 623 } else { 624 throw se; 625 } 626 } 627 } 628 } 629 } 630 631 /** 632 * Inner class used to represent a loader of resources and classes 633 * from a base URL. 634 */ 635 private static class Loader implements Closeable { 636 private final URL base; 637 private JarFile jarfile; // if this points to a jar file 638 639 /* 640 * Creates a new Loader for the specified URL. 641 */ 642 Loader(URL url) { 643 base = url; 644 } 645 646 /* 647 * Returns the base URL for this Loader. 648 */ 649 URL getBaseURL() { 650 return base; 651 } 652 653 URL findResource(final String name, boolean check) { 654 URL url; 655 try { 656 url = new URL(base, ParseUtil.encodePath(name, false)); 657 } catch (MalformedURLException e) { 658 throw new IllegalArgumentException("name"); 659 } 660 661 try { 662 if (check) { 663 URLClassPath.check(url); 664 } 665 666 /* 667 * For a HTTP connection we use the HEAD method to 668 * check if the resource exists. 669 */ 670 URLConnection uc = url.openConnection(); 671 if (uc instanceof HttpURLConnection) { 672 HttpURLConnection hconn = (HttpURLConnection)uc; 673 hconn.setRequestMethod("HEAD"); 674 if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) { 675 return null; 676 } 677 } else { 678 // our best guess for the other cases 679 uc.setUseCaches(false); 680 InputStream is = uc.getInputStream(); 681 is.close(); 682 } 683 return url; 684 } catch (Exception e) { 685 return null; 686 } 687 } 688 689 Resource getResource(final String name, boolean check) { 690 final URL url; 691 try { 692 url = new URL(base, ParseUtil.encodePath(name, false)); 693 } catch (MalformedURLException e) { 694 throw new IllegalArgumentException("name"); 695 } 696 final URLConnection uc; 697 try { 698 if (check) { 699 URLClassPath.check(url); 700 } 701 uc = url.openConnection(); 702 InputStream in = uc.getInputStream(); 703 if (uc instanceof JarURLConnection) { 704 /* Need to remember the jar file so it can be closed 705 * in a hurry. 706 */ 707 JarURLConnection juc = (JarURLConnection)uc; 708 jarfile = JarLoader.checkJar(juc.getJarFile()); 709 } 710 } catch (Exception e) { 711 return null; 712 } 713 return new Resource() { 714 public String getName() { return name; } 715 public URL getURL() { return url; } 716 public URL getCodeSourceURL() { return base; } 717 public InputStream getInputStream() throws IOException { 718 return uc.getInputStream(); 719 } 720 public int getContentLength() throws IOException { 721 return uc.getContentLength(); 722 } 723 }; 724 } 725 726 /* 727 * Returns the Resource for the specified name, or null if not 728 * found or the caller does not have the permission to get the 729 * resource. 730 */ 731 Resource getResource(final String name) { 732 return getResource(name, true); 733 } 734 735 /* 736 * close this loader and release all resources 737 * method overridden in sub-classes 738 */ 739 public void close () throws IOException { 740 if (jarfile != null) { 741 jarfile.close(); 742 } 743 } 744 745 /* 746 * Returns the local class path for this loader, or null if none. 747 */ 748 URL[] getClassPath() throws IOException { 749 return null; 750 } 751 } 752 753 /* 754 * Inner class used to represent a Loader of resources from a JAR URL. 755 */ 756 static class JarLoader extends Loader { 757 private JarFile jar; 758 private URL csu; 759 private JarIndex index; 760 private MetaIndex metaIndex; 761 private URLStreamHandler handler; 762 private HashMap<String, Loader> lmap; 763 private boolean closed = false; 764 private static final sun.misc.JavaUtilZipFileAccess zipAccess = 765 sun.misc.SharedSecrets.getJavaUtilZipFileAccess(); 766 767 /* 768 * Creates a new JarLoader for the specified URL referring to 769 * a JAR file. 770 */ 771 JarLoader(URL url, URLStreamHandler jarHandler, 772 HashMap<String, Loader> loaderMap) 773 throws IOException 774 { 775 super(new URL("jar", "", -1, url + "!/", jarHandler)); 776 csu = url; 777 handler = jarHandler; 778 lmap = loaderMap; 779 780 if (!isOptimizable(url)) { 781 ensureOpen(); 782 } else { 783 String fileName = url.getFile(); 784 if (fileName != null) { 785 fileName = ParseUtil.decode(fileName); 786 File f = new File(fileName); 787 metaIndex = MetaIndex.forJar(f); 788 // If the meta index is found but the file is not 789 // installed, set metaIndex to null. A typical 790 // senario is charsets.jar which won't be installed 791 // when the user is running in certain locale environment. 792 // The side effect of null metaIndex will cause 793 // ensureOpen get called so that IOException is thrown. 794 if (metaIndex != null && !f.exists()) { 795 metaIndex = null; 796 } 797 } 798 799 // metaIndex is null when either there is no such jar file 800 // entry recorded in meta-index file or such jar file is 801 // missing in JRE. See bug 6340399. 802 if (metaIndex == null) { 803 ensureOpen(); 804 } 805 } 806 } 807 808 @Override 809 public void close () throws IOException { 810 // closing is synchronized at higher level 811 if (!closed) { 812 closed = true; 813 // in case not already open. 814 ensureOpen(); 815 jar.close(); 816 } 817 } 818 819 JarFile getJarFile () { 820 return jar; 821 } 822 823 private boolean isOptimizable(URL url) { 824 return "file".equals(url.getProtocol()); 825 } 826 827 private void ensureOpen() throws IOException { 828 if (jar == null) { 829 try { 830 java.security.AccessController.doPrivileged( 831 new java.security.PrivilegedExceptionAction<Void>() { 832 public Void run() throws IOException { 833 if (DEBUG) { 834 System.err.println("Opening " + csu); 835 Thread.dumpStack(); 836 } 837 838 jar = getJarFile(csu); 839 index = JarIndex.getJarIndex(jar, metaIndex); 840 if (index != null) { 841 String[] jarfiles = index.getJarFiles(); 842 // Add all the dependent URLs to the lmap so that loaders 843 // will not be created for them by URLClassPath.getLoader(int) 844 // if the same URL occurs later on the main class path. We set 845 // Loader to null here to avoid creating a Loader for each 846 // URL until we actually need to try to load something from them. 847 for(int i = 0; i < jarfiles.length; i++) { 848 try { 849 URL jarURL = new URL(csu, jarfiles[i]); 850 // If a non-null loader already exists, leave it alone. 851 String urlNoFragString = URLUtil.urlNoFragString(jarURL); 852 if (!lmap.containsKey(urlNoFragString)) { 853 lmap.put(urlNoFragString, null); 854 } 855 } catch (MalformedURLException e) { 856 continue; 857 } 858 } 859 } 860 return null; 861 } 862 } 863 ); 864 } catch (java.security.PrivilegedActionException pae) { 865 throw (IOException)pae.getException(); 866 } 867 } 868 } 869 870 /* Throws if the given jar file is does not start with the correct LOC */ 871 static JarFile checkJar(JarFile jar) throws IOException { 872 if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING 873 && !zipAccess.startsWithLocHeader(jar)) { 874 IOException x = new IOException("Invalid Jar file"); 875 try { 876 jar.close(); 877 } catch (IOException ex) { 878 x.addSuppressed(ex); 879 } 880 throw x; 881 } 882 883 return jar; 884 } 885 886 private JarFile getJarFile(URL url) throws IOException { 887 // Optimize case where url refers to a local jar file 888 if (isOptimizable(url)) { 889 FileURLMapper p = new FileURLMapper (url); 890 if (!p.exists()) { 891 throw new FileNotFoundException(p.getPath()); 892 } 893 return checkJar(new JarFile(p.getPath())); 894 } 895 URLConnection uc = getBaseURL().openConnection(); 896 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION); 897 JarFile jarFile = ((JarURLConnection)uc).getJarFile(); 898 return checkJar(jarFile); 899 } 900 901 /* 902 * Returns the index of this JarLoader if it exists. 903 */ 904 JarIndex getIndex() { 905 try { 906 ensureOpen(); 907 } catch (IOException e) { 908 throw new InternalError(e); 909 } 910 return index; 911 } 912 913 /* 914 * Creates the resource and if the check flag is set to true, checks if 915 * is its okay to return the resource. 916 */ 917 Resource checkResource(final String name, boolean check, 918 final JarEntry entry) { 919 920 final URL url; 921 try { 922 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 923 if (check) { 924 URLClassPath.check(url); 925 } 926 } catch (MalformedURLException e) { 927 return null; 928 // throw new IllegalArgumentException("name"); 929 } catch (IOException e) { 930 return null; 931 } catch (AccessControlException e) { 932 return null; 933 } 934 935 return new Resource() { 936 public String getName() { return name; } 937 public URL getURL() { return url; } 938 public URL getCodeSourceURL() { return csu; } 939 public InputStream getInputStream() throws IOException 940 { return jar.getInputStream(entry); } 941 public int getContentLength() 942 { return (int)entry.getSize(); } 943 public Manifest getManifest() throws IOException 944 { return jar.getManifest(); }; 945 public Certificate[] getCertificates() 946 { return entry.getCertificates(); }; 947 public CodeSigner[] getCodeSigners() 948 { return entry.getCodeSigners(); }; 949 }; 950 } 951 952 953 /* 954 * Returns true iff atleast one resource in the jar file has the same 955 * package name as that of the specified resource name. 956 */ 957 boolean validIndex(final String name) { 958 String packageName = name; 959 int pos; 960 if((pos = name.lastIndexOf("/")) != -1) { 961 packageName = name.substring(0, pos); 962 } 963 964 String entryName; 965 ZipEntry entry; 966 Enumeration<JarEntry> enum_ = jar.entries(); 967 while (enum_.hasMoreElements()) { 968 entry = enum_.nextElement(); 969 entryName = entry.getName(); 970 if((pos = entryName.lastIndexOf("/")) != -1) 971 entryName = entryName.substring(0, pos); 972 if (entryName.equals(packageName)) { 973 return true; 974 } 975 } 976 return false; 977 } 978 979 /* 980 * Returns the URL for a resource with the specified name 981 */ 982 URL findResource(final String name, boolean check) { 983 Resource rsc = getResource(name, check); 984 if (rsc != null) { 985 return rsc.getURL(); 986 } 987 return null; 988 } 989 990 /* 991 * Returns the JAR Resource for the specified name. 992 */ 993 Resource getResource(final String name, boolean check) { 994 if (metaIndex != null) { 995 if (!metaIndex.mayContain(name)) { 996 return null; 997 } 998 } 999 1000 try { 1001 ensureOpen(); 1002 } catch (IOException e) { 1003 throw new InternalError(e); 1004 } 1005 final JarEntry entry = jar.getJarEntry(name); 1006 if (entry != null) 1007 return checkResource(name, check, entry); 1008 1009 if (index == null) 1010 return null; 1011 1012 HashSet<String> visited = new HashSet<String>(); 1013 return getResource(name, check, visited); 1014 } 1015 1016 /* 1017 * Version of getResource() that tracks the jar files that have been 1018 * visited by linking through the index files. This helper method uses 1019 * a HashSet to store the URLs of jar files that have been searched and 1020 * uses it to avoid going into an infinite loop, looking for a 1021 * non-existent resource 1022 */ 1023 Resource getResource(final String name, boolean check, 1024 Set<String> visited) { 1025 1026 Resource res; 1027 String[] jarFiles; 1028 int count = 0; 1029 LinkedList<String> jarFilesList = null; 1030 1031 /* If there no jar files in the index that can potential contain 1032 * this resource then return immediately. 1033 */ 1034 if((jarFilesList = index.get(name)) == null) 1035 return null; 1036 1037 do { 1038 int size = jarFilesList.size(); 1039 jarFiles = jarFilesList.toArray(new String[size]); 1040 /* loop through the mapped jar file list */ 1041 while(count < size) { 1042 String jarName = jarFiles[count++]; 1043 JarLoader newLoader; 1044 final URL url; 1045 1046 try{ 1047 url = new URL(csu, jarName); 1048 String urlNoFragString = URLUtil.urlNoFragString(url); 1049 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { 1050 /* no loader has been set up for this jar file 1051 * before 1052 */ 1053 newLoader = AccessController.doPrivileged( 1054 new PrivilegedExceptionAction<JarLoader>() { 1055 public JarLoader run() throws IOException { 1056 return new JarLoader(url, handler, 1057 lmap); 1058 } 1059 }); 1060 1061 /* this newly opened jar file has its own index, 1062 * merge it into the parent's index, taking into 1063 * account the relative path. 1064 */ 1065 JarIndex newIndex = newLoader.getIndex(); 1066 if(newIndex != null) { 1067 int pos = jarName.lastIndexOf("/"); 1068 newIndex.merge(this.index, (pos == -1 ? 1069 null : jarName.substring(0, pos + 1))); 1070 } 1071 1072 /* put it in the global hashtable */ 1073 lmap.put(urlNoFragString, newLoader); 1074 } 1075 } catch (java.security.PrivilegedActionException pae) { 1076 continue; 1077 } catch (MalformedURLException e) { 1078 continue; 1079 } 1080 1081 1082 /* Note that the addition of the url to the list of visited 1083 * jars incorporates a check for presence in the hashmap 1084 */ 1085 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); 1086 if (!visitedURL) { 1087 try { 1088 newLoader.ensureOpen(); 1089 } catch (IOException e) { 1090 throw new InternalError(e); 1091 } 1092 final JarEntry entry = newLoader.jar.getJarEntry(name); 1093 if (entry != null) { 1094 return newLoader.checkResource(name, check, entry); 1095 } 1096 1097 /* Verify that at least one other resource with the 1098 * same package name as the lookedup resource is 1099 * present in the new jar 1100 */ 1101 if (!newLoader.validIndex(name)) { 1102 /* the mapping is wrong */ 1103 throw new InvalidJarIndexException("Invalid index"); 1104 } 1105 } 1106 1107 /* If newLoader is the current loader or if it is a 1108 * loader that has already been searched or if the new 1109 * loader does not have an index then skip it 1110 * and move on to the next loader. 1111 */ 1112 if (visitedURL || newLoader == this || 1113 newLoader.getIndex() == null) { 1114 continue; 1115 } 1116 1117 /* Process the index of the new loader 1118 */ 1119 if((res = newLoader.getResource(name, check, visited)) 1120 != null) { 1121 return res; 1122 } 1123 } 1124 // Get the list of jar files again as the list could have grown 1125 // due to merging of index files. 1126 jarFilesList = index.get(name); 1127 1128 // If the count is unchanged, we are done. 1129 } while(count < jarFilesList.size()); 1130 return null; 1131 } 1132 1133 1134 /* 1135 * Returns the JAR file local class path, or null if none. 1136 */ 1137 URL[] getClassPath() throws IOException { 1138 if (index != null) { 1139 return null; 1140 } 1141 1142 if (metaIndex != null) { 1143 return null; 1144 } 1145 1146 ensureOpen(); 1147 parseExtensionsDependencies(); 1148 1149 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary 1150 Manifest man = jar.getManifest(); 1151 if (man != null) { 1152 Attributes attr = man.getMainAttributes(); 1153 if (attr != null) { 1154 String value = attr.getValue(Name.CLASS_PATH); 1155 if (value != null) { 1156 return parseClassPath(csu, value); 1157 } 1158 } 1159 } 1160 } 1161 return null; 1162 } 1163 1164 /* 1165 * parse the standard extension dependencies 1166 */ 1167 private void parseExtensionsDependencies() throws IOException { 1168 ExtensionDependency.checkExtensionsDependencies(jar); 1169 } 1170 1171 /* 1172 * Parses value of the Class-Path manifest attribute and returns 1173 * an array of URLs relative to the specified base URL. 1174 */ 1175 private URL[] parseClassPath(URL base, String value) 1176 throws MalformedURLException 1177 { 1178 StringTokenizer st = new StringTokenizer(value); 1179 URL[] urls = new URL[st.countTokens()]; 1180 int i = 0; 1181 while (st.hasMoreTokens()) { 1182 String path = st.nextToken(); 1183 urls[i] = new URL(base, path); 1184 i++; 1185 } 1186 return urls; 1187 } 1188 } 1189 1190 /* 1191 * Inner class used to represent a loader of classes and resources 1192 * from a file URL that refers to a directory. 1193 */ 1194 private static class FileLoader extends Loader { 1195 /* Canonicalized File */ 1196 private File dir; 1197 1198 FileLoader(URL url) throws IOException { 1199 super(url); 1200 if (!"file".equals(url.getProtocol())) { 1201 throw new IllegalArgumentException("url"); 1202 } 1203 String path = url.getFile().replace('/', File.separatorChar); 1204 path = ParseUtil.decode(path); 1205 dir = (new File(path)).getCanonicalFile(); 1206 } 1207 1208 /* 1209 * Returns the URL for a resource with the specified name 1210 */ 1211 URL findResource(final String name, boolean check) { 1212 Resource rsc = getResource(name, check); 1213 if (rsc != null) { 1214 return rsc.getURL(); 1215 } 1216 return null; 1217 } 1218 1219 Resource getResource(final String name, boolean check) { 1220 final URL url; 1221 try { 1222 URL normalizedBase = new URL(getBaseURL(), "."); 1223 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 1224 1225 if (url.getFile().startsWith(normalizedBase.getFile()) == false) { 1226 // requested resource had ../..'s in path 1227 return null; 1228 } 1229 1230 if (check) 1231 URLClassPath.check(url); 1232 1233 final File file; 1234 if (name.indexOf("..") != -1) { 1235 file = (new File(dir, name.replace('/', File.separatorChar))) 1236 .getCanonicalFile(); 1237 if ( !((file.getPath()).startsWith(dir.getPath())) ) { 1238 /* outside of base dir */ 1239 return null; 1240 } 1241 } else { 1242 file = new File(dir, name.replace('/', File.separatorChar)); 1243 } 1244 1245 if (file.exists()) { 1246 return new Resource() { 1247 public String getName() { return name; }; 1248 public URL getURL() { return url; }; 1249 public URL getCodeSourceURL() { return getBaseURL(); }; 1250 public InputStream getInputStream() throws IOException 1251 { return new FileInputStream(file); }; 1252 public int getContentLength() throws IOException 1253 { return (int)file.length(); }; 1254 }; 1255 } 1256 } catch (Exception e) { 1257 return null; 1258 } 1259 return null; 1260 } 1261 } 1262 }