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