1 /* 2 * Copyright (c) 1997, 2013, 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.io.Closeable; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.net.HttpURLConnection; 35 import java.net.JarURLConnection; 36 import java.net.MalformedURLException; 37 import java.net.URL; 38 import java.net.URLConnection; 39 import java.net.URLStreamHandler; 40 import java.net.URLStreamHandlerFactory; 41 import java.security.AccessControlException; 42 import java.security.AccessController; 43 import java.security.CodeSigner; 44 import java.security.Permission; 45 import java.security.PrivilegedExceptionAction; 46 import java.security.cert.Certificate; 47 import java.util.ArrayList; 48 import java.util.Collections; 49 import java.util.Enumeration; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.LinkedList; 53 import java.util.List; 54 import java.util.NoSuchElementException; 55 import java.util.Set; 56 import java.util.Stack; 57 import java.util.StringTokenizer; 58 import java.util.jar.JarFile; 59 import java.util.zip.ZipEntry; 60 import java.util.jar.JarEntry; 61 import java.util.jar.Manifest; 62 import java.util.jar.Attributes; 63 import java.util.jar.Attributes.Name; 64 import java.util.zip.ZipFile; 65 66 import jdk.internal.misc.JavaUtilZipFileAccess; 67 import jdk.internal.misc.SharedSecrets; 68 import sun.net.util.URLUtil; 69 import sun.net.www.ParseUtil; 70 71 /** 72 * This class is used to maintain a search path of URLs for loading classes 73 * and resources from both JAR files and directories. 74 * 75 * @author David Connelly 76 */ 77 public class URLClassPath { 78 private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version"; 79 private static final String JAVA_HOME; 80 private static final String JAVA_VERSION; 81 private static final boolean DEBUG; 82 private static final boolean DISABLE_JAR_CHECKING; 83 84 static { 85 JAVA_HOME = java.security.AccessController.doPrivileged( 86 new sun.security.action.GetPropertyAction("java.home")); 87 JAVA_VERSION = java.security.AccessController.doPrivileged( 88 new sun.security.action.GetPropertyAction("java.version")); 89 DEBUG = (java.security.AccessController.doPrivileged( 90 new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.debug")) != null); 91 String p = java.security.AccessController.doPrivileged( 92 new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.disableJarChecking")); 93 DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false; 94 } 95 96 /* The original search path of URLs. */ 97 private ArrayList<URL> path = new ArrayList<>(); 98 99 /* The stack of unopened URLs */ 100 Stack<URL> urls = new Stack<>(); 101 102 /* The resulting search path of Loaders */ 103 ArrayList<Loader> loaders = new ArrayList<>(); 104 105 /* Map of each URL opened to its corresponding Loader */ 106 HashMap<String, Loader> lmap = new HashMap<>(); 107 108 /* The jar protocol handler to use when creating new URLs */ 109 private URLStreamHandler jarHandler; 110 111 /* Whether this URLClassLoader has been closed yet */ 112 private boolean closed = false; 113 114 /** 115 * Creates a new URLClassPath for the given URLs. The URLs will be 116 * searched in the order specified for classes and resources. A URL 117 * ending with a '/' is assumed to refer to a directory. Otherwise, 118 * the URL is assumed to refer to a JAR file. 119 * 120 * @param urls the directory and JAR file URLs to search for classes 121 * and resources 122 * @param factory the URLStreamHandlerFactory to use when creating new URLs 123 */ 124 public URLClassPath(URL[] urls, URLStreamHandlerFactory factory) { 125 for (int i = 0; i < urls.length; i++) { 126 path.add(urls[i]); 127 } 128 push(urls); 129 if (factory != null) { 130 jarHandler = factory.createURLStreamHandler("jar"); 131 } 132 } 133 134 public URLClassPath(URL[] urls) { 135 this(urls, null); 136 } 137 138 public synchronized List<IOException> closeLoaders() { 139 if (closed) { 140 return Collections.emptyList(); 141 } 142 List<IOException> result = new LinkedList<>(); 143 for (Loader loader : loaders) { 144 try { 145 loader.close(); 146 } catch (IOException e) { 147 result.add (e); 148 } 149 } 150 closed = true; 151 return result; 152 } 153 154 /** 155 * Appends the specified URL to the search path of directory and JAR 156 * file URLs from which to load classes and resources. 157 * <p> 158 * If the URL specified is null or is already in the list of 159 * URLs, then invoking this method has no effect. 160 */ 161 public synchronized void addURL(URL url) { 162 if (closed) 163 return; 164 synchronized (urls) { 165 if (url == null || path.contains(url)) 166 return; 167 168 urls.add(0, url); 169 path.add(url); 170 } 171 } 172 173 /** 174 * Returns the original search path of URLs. 175 */ 176 public URL[] getURLs() { 177 synchronized (urls) { 178 return path.toArray(new URL[path.size()]); 179 } 180 } 181 182 /** 183 * Finds the resource with the specified name on the URL search path 184 * or null if not found or security check fails. 185 * 186 * @param name the name of the resource 187 * @param check whether to perform a security check 188 * @return a <code>URL</code> for the resource, or <code>null</code> 189 * if the resource could not be found. 190 */ 191 public URL findResource(String name, boolean check) { 192 Loader loader; 193 for (int i = 0; (loader = getLoader(i)) != null; i++) { 194 URL url = loader.findResource(name, check); 195 if (url != null) { 196 return url; 197 } 198 } 199 return null; 200 } 201 202 /** 203 * Finds the first Resource on the URL search path which has the specified 204 * name. Returns null if no Resource could be found. 205 * 206 * @param name the name of the Resource 207 * @param check whether to perform a security check 208 * @return the Resource, or null if not found 209 */ 210 public Resource getResource(String name, boolean check) { 211 if (DEBUG) { 212 System.err.println("URLClassPath.getResource(\"" + name + "\")"); 213 } 214 215 Loader loader; 216 for (int i = 0; (loader = getLoader(i)) != null; i++) { 217 Resource res = loader.getResource(name, check); 218 if (res != null) { 219 return res; 220 } 221 } 222 return null; 223 } 224 225 /** 226 * Finds all resources on the URL search path with the given name. 227 * Returns an enumeration of the URL objects. 228 * 229 * @param name the resource name 230 * @return an Enumeration of all the urls having the specified name 231 */ 232 public Enumeration<URL> findResources(final String name, 233 final boolean check) { 234 return new Enumeration<>() { 235 private int index = 0; 236 private URL url = null; 237 238 private boolean next() { 239 if (url != null) { 240 return true; 241 } else { 242 Loader loader; 243 while ((loader = getLoader(index++)) != null) { 244 url = loader.findResource(name, check); 245 if (url != null) { 246 return true; 247 } 248 } 249 return false; 250 } 251 } 252 253 public boolean hasMoreElements() { 254 return next(); 255 } 256 257 public URL nextElement() { 258 if (!next()) { 259 throw new NoSuchElementException(); 260 } 261 URL u = url; 262 url = null; 263 return u; 264 } 265 }; 266 } 267 268 public Resource getResource(String name) { 269 return getResource(name, true); 270 } 271 272 /** 273 * Finds all resources on the URL search path with the given name. 274 * Returns an enumeration of the Resource objects. 275 * 276 * @param name the resource name 277 * @return an Enumeration of all the resources having the specified name 278 */ 279 public Enumeration<Resource> getResources(final String name, 280 final boolean check) { 281 return new Enumeration<>() { 282 private int index = 0; 283 private Resource res = null; 284 285 private boolean next() { 286 if (res != null) { 287 return true; 288 } else { 289 Loader loader; 290 while ((loader = getLoader(index++)) != null) { 291 res = loader.getResource(name, check); 292 if (res != null) { 293 return true; 294 } 295 } 296 return false; 297 } 298 } 299 300 public boolean hasMoreElements() { 301 return next(); 302 } 303 304 public Resource nextElement() { 305 if (!next()) { 306 throw new NoSuchElementException(); 307 } 308 Resource r = res; 309 res = null; 310 return r; 311 } 312 }; 313 } 314 315 public Enumeration<Resource> getResources(final String name) { 316 return getResources(name, true); 317 } 318 319 /* 320 * Returns the Loader at the specified position in the URL search 321 * path. The URLs are opened and expanded as needed. Returns null 322 * if the specified index is out of range. 323 */ 324 private synchronized Loader getLoader(int index) { 325 if (closed) { 326 return null; 327 } 328 // Expand URL search path until the request can be satisfied 329 // or the URL stack is empty. 330 while (loaders.size() < index + 1) { 331 // Pop the next URL from the URL stack 332 URL url; 333 synchronized (urls) { 334 if (urls.empty()) { 335 return null; 336 } else { 337 url = urls.pop(); 338 } 339 } 340 // Skip this URL if it already has a Loader. (Loader 341 // may be null in the case where URL has not been opened 342 // but is referenced by a JAR index.) 343 String urlNoFragString = URLUtil.urlNoFragString(url); 344 if (lmap.containsKey(urlNoFragString)) { 345 continue; 346 } 347 // Otherwise, create a new Loader for the URL. 348 Loader loader; 349 try { 350 loader = getLoader(url); 351 // If the loader defines a local class path then add the 352 // URLs to the list of URLs to be opened. 353 URL[] urls = loader.getClassPath(); 354 if (urls != null) { 355 push(urls); 356 } 357 } catch (IOException e) { 358 // Silently ignore for now... 359 continue; 360 } 361 // Finally, add the Loader to the search path. 362 loaders.add(loader); 363 lmap.put(urlNoFragString, loader); 364 } 365 return loaders.get(index); 366 } 367 368 /* 369 * Returns the Loader for the specified base URL. 370 */ 371 private Loader getLoader(final URL url) throws IOException { 372 try { 373 return java.security.AccessController.doPrivileged( 374 new java.security.PrivilegedExceptionAction<>() { 375 public Loader run() throws IOException { 376 String file = url.getFile(); 377 if (file != null && file.endsWith("/")) { 378 if ("file".equals(url.getProtocol())) { 379 return new FileLoader(url); 380 } else { 381 return new Loader(url); 382 } 383 } else { 384 return new JarLoader(url, jarHandler, lmap); 385 } 386 } 387 }); 388 } catch (java.security.PrivilegedActionException pae) { 389 throw (IOException)pae.getException(); 390 } 391 } 392 393 /* 394 * Pushes the specified URLs onto the list of unopened URLs. 395 */ 396 private void push(URL[] us) { 397 synchronized (urls) { 398 for (int i = us.length - 1; i >= 0; --i) { 399 urls.push(us[i]); 400 } 401 } 402 } 403 404 /** 405 * Convert class path specification into an array of file URLs. 406 * 407 * The path of the file is encoded before conversion into URL 408 * form so that reserved characters can safely appear in the path. 409 */ 410 public static URL[] pathToURLs(String path) { 411 StringTokenizer st = new StringTokenizer(path, File.pathSeparator); 412 URL[] urls = new URL[st.countTokens()]; 413 int count = 0; 414 while (st.hasMoreTokens()) { 415 File f = new File(st.nextToken()); 416 try { 417 f = new File(f.getCanonicalPath()); 418 } catch (IOException x) { 419 // use the non-canonicalized filename 420 } 421 try { 422 urls[count++] = ParseUtil.fileToEncodedURL(f); 423 } catch (IOException x) { } 424 } 425 426 if (urls.length != count) { 427 URL[] tmp = new URL[count]; 428 System.arraycopy(urls, 0, tmp, 0, count); 429 urls = tmp; 430 } 431 return urls; 432 } 433 434 /* 435 * Check whether the resource URL should be returned. 436 * Return null on security check failure. 437 * Called by java.net.URLClassLoader. 438 */ 439 public static URL checkURL(URL url) { 440 if (url != null) { 441 try { 442 check(url); 443 } catch (Exception e) { 444 return null; 445 } 446 } 447 return url; 448 } 449 450 /* 451 * Check whether the resource URL should be returned. 452 * Throw exception on failure. 453 * Called internally within this file. 454 */ 455 public static void check(URL url) throws IOException { 456 SecurityManager security = System.getSecurityManager(); 457 if (security != null) { 458 URLConnection urlConnection = url.openConnection(); 459 Permission perm = urlConnection.getPermission(); 460 if (perm != null) { 461 try { 462 security.checkPermission(perm); 463 } catch (SecurityException se) { 464 // fallback to checkRead/checkConnect for pre 1.2 465 // security managers 466 if ((perm instanceof java.io.FilePermission) && 467 perm.getActions().indexOf("read") != -1) { 468 security.checkRead(perm.getName()); 469 } else if ((perm instanceof 470 java.net.SocketPermission) && 471 perm.getActions().indexOf("connect") != -1) { 472 URL locUrl = url; 473 if (urlConnection instanceof JarURLConnection) { 474 locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); 475 } 476 security.checkConnect(locUrl.getHost(), 477 locUrl.getPort()); 478 } else { 479 throw se; 480 } 481 } 482 } 483 } 484 } 485 486 /** 487 * Inner class used to represent a loader of resources and classes 488 * from a base URL. 489 */ 490 private static class Loader implements Closeable { 491 private final URL base; 492 private JarFile jarfile; // if this points to a jar file 493 494 /* 495 * Creates a new Loader for the specified URL. 496 */ 497 Loader(URL url) { 498 base = url; 499 } 500 501 /* 502 * Returns the base URL for this Loader. 503 */ 504 URL getBaseURL() { 505 return base; 506 } 507 508 URL findResource(final String name, boolean check) { 509 URL url; 510 try { 511 url = new URL(base, ParseUtil.encodePath(name, false)); 512 } catch (MalformedURLException e) { 513 throw new IllegalArgumentException("name"); 514 } 515 516 try { 517 if (check) { 518 URLClassPath.check(url); 519 } 520 521 /* 522 * For a HTTP connection we use the HEAD method to 523 * check if the resource exists. 524 */ 525 URLConnection uc = url.openConnection(); 526 if (uc instanceof HttpURLConnection) { 527 HttpURLConnection hconn = (HttpURLConnection)uc; 528 hconn.setRequestMethod("HEAD"); 529 if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) { 530 return null; 531 } 532 } else { 533 // our best guess for the other cases 534 uc.setUseCaches(false); 535 InputStream is = uc.getInputStream(); 536 is.close(); 537 } 538 return url; 539 } catch (Exception e) { 540 return null; 541 } 542 } 543 544 Resource getResource(final String name, boolean check) { 545 final URL url; 546 try { 547 url = new URL(base, ParseUtil.encodePath(name, false)); 548 } catch (MalformedURLException e) { 549 throw new IllegalArgumentException("name"); 550 } 551 final URLConnection uc; 552 try { 553 if (check) { 554 URLClassPath.check(url); 555 } 556 uc = url.openConnection(); 557 InputStream in = uc.getInputStream(); 558 if (uc instanceof JarURLConnection) { 559 /* Need to remember the jar file so it can be closed 560 * in a hurry. 561 */ 562 JarURLConnection juc = (JarURLConnection)uc; 563 jarfile = JarLoader.checkJar(juc.getJarFile()); 564 } 565 } catch (Exception e) { 566 return null; 567 } 568 return new Resource() { 569 public String getName() { return name; } 570 public URL getURL() { return url; } 571 public URL getCodeSourceURL() { return base; } 572 public InputStream getInputStream() throws IOException { 573 return uc.getInputStream(); 574 } 575 public int getContentLength() throws IOException { 576 return uc.getContentLength(); 577 } 578 }; 579 } 580 581 /* 582 * Returns the Resource for the specified name, or null if not 583 * found or the caller does not have the permission to get the 584 * resource. 585 */ 586 Resource getResource(final String name) { 587 return getResource(name, true); 588 } 589 590 /* 591 * close this loader and release all resources 592 * method overridden in sub-classes 593 */ 594 public void close () throws IOException { 595 if (jarfile != null) { 596 jarfile.close(); 597 } 598 } 599 600 /* 601 * Returns the local class path for this loader, or null if none. 602 */ 603 URL[] getClassPath() throws IOException { 604 return null; 605 } 606 } 607 608 /* 609 * Inner class used to represent a Loader of resources from a JAR URL. 610 */ 611 static class JarLoader extends Loader { 612 private JarFile jar; 613 private URL csu; 614 private JarIndex index; 615 private URLStreamHandler handler; 616 private HashMap<String, Loader> lmap; 617 private boolean closed = false; 618 private static final JavaUtilZipFileAccess zipAccess = 619 SharedSecrets.getJavaUtilZipFileAccess(); 620 621 /* 622 * Creates a new JarLoader for the specified URL referring to 623 * a JAR file. 624 */ 625 JarLoader(URL url, URLStreamHandler jarHandler, 626 HashMap<String, Loader> loaderMap) 627 throws IOException 628 { 629 super(new URL("jar", "", -1, url + "!/", jarHandler)); 630 csu = url; 631 handler = jarHandler; 632 lmap = loaderMap; 633 634 ensureOpen(); 635 } 636 637 @Override 638 public void close () throws IOException { 639 // closing is synchronized at higher level 640 if (!closed) { 641 closed = true; 642 // in case not already open. 643 ensureOpen(); 644 jar.close(); 645 } 646 } 647 648 JarFile getJarFile () { 649 return jar; 650 } 651 652 private boolean isOptimizable(URL url) { 653 return "file".equals(url.getProtocol()); 654 } 655 656 private void ensureOpen() throws IOException { 657 if (jar == null) { 658 try { 659 java.security.AccessController.doPrivileged( 660 new java.security.PrivilegedExceptionAction<>() { 661 public Void run() throws IOException { 662 if (DEBUG) { 663 System.err.println("Opening " + csu); 664 Thread.dumpStack(); 665 } 666 667 jar = getJarFile(csu); 668 index = JarIndex.getJarIndex(jar); 669 if (index != null) { 670 String[] jarfiles = index.getJarFiles(); 671 // Add all the dependent URLs to the lmap so that loaders 672 // will not be created for them by URLClassPath.getLoader(int) 673 // if the same URL occurs later on the main class path. We set 674 // Loader to null here to avoid creating a Loader for each 675 // URL until we actually need to try to load something from them. 676 for(int i = 0; i < jarfiles.length; i++) { 677 try { 678 URL jarURL = new URL(csu, jarfiles[i]); 679 // If a non-null loader already exists, leave it alone. 680 String urlNoFragString = URLUtil.urlNoFragString(jarURL); 681 if (!lmap.containsKey(urlNoFragString)) { 682 lmap.put(urlNoFragString, null); 683 } 684 } catch (MalformedURLException e) { 685 continue; 686 } 687 } 688 } 689 return null; 690 } 691 } 692 ); 693 } catch (java.security.PrivilegedActionException pae) { 694 throw (IOException)pae.getException(); 695 } 696 } 697 } 698 699 /* Throws if the given jar file is does not start with the correct LOC */ 700 static JarFile checkJar(JarFile jar) throws IOException { 701 if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING 702 && !zipAccess.startsWithLocHeader(jar)) { 703 IOException x = new IOException("Invalid Jar file"); 704 try { 705 jar.close(); 706 } catch (IOException ex) { 707 x.addSuppressed(ex); 708 } 709 throw x; 710 } 711 712 return jar; 713 } 714 715 private JarFile getJarFile(URL url) throws IOException { 716 // Optimize case where url refers to a local jar file 717 if (isOptimizable(url)) { 718 FileURLMapper p = new FileURLMapper (url); 719 if (!p.exists()) { 720 throw new FileNotFoundException(p.getPath()); 721 } 722 return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ, 723 JarFile.Release.RUNTIME)); 724 } 725 URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection(); 726 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION); 727 JarFile jarFile = ((JarURLConnection)uc).getJarFile(); 728 return checkJar(jarFile); 729 } 730 731 /* 732 * Returns the index of this JarLoader if it exists. 733 */ 734 JarIndex getIndex() { 735 try { 736 ensureOpen(); 737 } catch (IOException e) { 738 throw new InternalError(e); 739 } 740 return index; 741 } 742 743 /* 744 * Creates the resource and if the check flag is set to true, checks if 745 * is its okay to return the resource. 746 */ 747 Resource checkResource(final String name, boolean check, 748 final JarEntry entry) { 749 750 final URL url; 751 try { 752 if (jar.isMultiRelease()) { 753 // add #runtime fragment to tell JarURLConnection to use 754 // runtime versioning if the underlying jar file is multi-release 755 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false) + "#runtime"); 756 } else { 757 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 758 } 759 if (check) { 760 URLClassPath.check(url); 761 } 762 } catch (MalformedURLException e) { 763 return null; 764 // throw new IllegalArgumentException("name"); 765 } catch (IOException e) { 766 return null; 767 } catch (AccessControlException e) { 768 return null; 769 } 770 771 return new Resource() { 772 public String getName() { return name; } 773 public URL getURL() { return url; } 774 public URL getCodeSourceURL() { return csu; } 775 public InputStream getInputStream() throws IOException 776 { return jar.getInputStream(entry); } 777 public int getContentLength() 778 { return (int)entry.getSize(); } 779 public Manifest getManifest() throws IOException 780 { return jar.getManifest(); }; 781 public Certificate[] getCertificates() 782 { return entry.getCertificates(); }; 783 public CodeSigner[] getCodeSigners() 784 { return entry.getCodeSigners(); }; 785 }; 786 } 787 788 789 /* 790 * Returns true iff atleast one resource in the jar file has the same 791 * package name as that of the specified resource name. 792 */ 793 boolean validIndex(final String name) { 794 String packageName = name; 795 int pos; 796 if((pos = name.lastIndexOf('/')) != -1) { 797 packageName = name.substring(0, pos); 798 } 799 800 String entryName; 801 ZipEntry entry; 802 Enumeration<JarEntry> enum_ = jar.entries(); 803 while (enum_.hasMoreElements()) { 804 entry = enum_.nextElement(); 805 entryName = entry.getName(); 806 if((pos = entryName.lastIndexOf('/')) != -1) 807 entryName = entryName.substring(0, pos); 808 if (entryName.equals(packageName)) { 809 return true; 810 } 811 } 812 return false; 813 } 814 815 /* 816 * Returns the URL for a resource with the specified name 817 */ 818 URL findResource(final String name, boolean check) { 819 Resource rsc = getResource(name, check); 820 if (rsc != null) { 821 return rsc.getURL(); 822 } 823 return null; 824 } 825 826 /* 827 * Returns the JAR Resource for the specified name. 828 */ 829 Resource getResource(final String name, boolean check) { 830 try { 831 ensureOpen(); 832 } catch (IOException e) { 833 throw new InternalError(e); 834 } 835 final JarEntry entry = jar.getJarEntry(name); 836 if (entry != null) 837 return checkResource(name, check, entry); 838 839 if (index == null) 840 return null; 841 842 HashSet<String> visited = new HashSet<>(); 843 return getResource(name, check, visited); 844 } 845 846 /* 847 * Version of getResource() that tracks the jar files that have been 848 * visited by linking through the index files. This helper method uses 849 * a HashSet to store the URLs of jar files that have been searched and 850 * uses it to avoid going into an infinite loop, looking for a 851 * non-existent resource 852 */ 853 Resource getResource(final String name, boolean check, 854 Set<String> visited) { 855 856 Resource res; 857 String[] jarFiles; 858 int count = 0; 859 LinkedList<String> jarFilesList = null; 860 861 /* If there no jar files in the index that can potential contain 862 * this resource then return immediately. 863 */ 864 if((jarFilesList = index.get(name)) == null) 865 return null; 866 867 do { 868 int size = jarFilesList.size(); 869 jarFiles = jarFilesList.toArray(new String[size]); 870 /* loop through the mapped jar file list */ 871 while(count < size) { 872 String jarName = jarFiles[count++]; 873 JarLoader newLoader; 874 final URL url; 875 876 try{ 877 url = new URL(csu, jarName); 878 String urlNoFragString = URLUtil.urlNoFragString(url); 879 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { 880 /* no loader has been set up for this jar file 881 * before 882 */ 883 newLoader = AccessController.doPrivileged( 884 new PrivilegedExceptionAction<>() { 885 public JarLoader run() throws IOException { 886 return new JarLoader(url, handler, 887 lmap); 888 } 889 }); 890 891 /* this newly opened jar file has its own index, 892 * merge it into the parent's index, taking into 893 * account the relative path. 894 */ 895 JarIndex newIndex = newLoader.getIndex(); 896 if(newIndex != null) { 897 int pos = jarName.lastIndexOf('/'); 898 newIndex.merge(this.index, (pos == -1 ? 899 null : jarName.substring(0, pos + 1))); 900 } 901 902 /* put it in the global hashtable */ 903 lmap.put(urlNoFragString, newLoader); 904 } 905 } catch (java.security.PrivilegedActionException pae) { 906 continue; 907 } catch (MalformedURLException e) { 908 continue; 909 } 910 911 912 /* Note that the addition of the url to the list of visited 913 * jars incorporates a check for presence in the hashmap 914 */ 915 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); 916 if (!visitedURL) { 917 try { 918 newLoader.ensureOpen(); 919 } catch (IOException e) { 920 throw new InternalError(e); 921 } 922 final JarEntry entry = newLoader.jar.getJarEntry(name); 923 if (entry != null) { 924 return newLoader.checkResource(name, check, entry); 925 } 926 927 /* Verify that at least one other resource with the 928 * same package name as the lookedup resource is 929 * present in the new jar 930 */ 931 if (!newLoader.validIndex(name)) { 932 /* the mapping is wrong */ 933 throw new InvalidJarIndexException("Invalid index"); 934 } 935 } 936 937 /* If newLoader is the current loader or if it is a 938 * loader that has already been searched or if the new 939 * loader does not have an index then skip it 940 * and move on to the next loader. 941 */ 942 if (visitedURL || newLoader == this || 943 newLoader.getIndex() == null) { 944 continue; 945 } 946 947 /* Process the index of the new loader 948 */ 949 if((res = newLoader.getResource(name, check, visited)) 950 != null) { 951 return res; 952 } 953 } 954 // Get the list of jar files again as the list could have grown 955 // due to merging of index files. 956 jarFilesList = index.get(name); 957 958 // If the count is unchanged, we are done. 959 } while(count < jarFilesList.size()); 960 return null; 961 } 962 963 964 /* 965 * Returns the JAR file local class path, or null if none. 966 */ 967 URL[] getClassPath() throws IOException { 968 if (index != null) { 969 return null; 970 } 971 972 ensureOpen(); 973 974 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary 975 Manifest man = jar.getManifest(); 976 if (man != null) { 977 Attributes attr = man.getMainAttributes(); 978 if (attr != null) { 979 String value = attr.getValue(Name.CLASS_PATH); 980 if (value != null) { 981 return parseClassPath(csu, value); 982 } 983 } 984 } 985 } 986 return null; 987 } 988 989 /* 990 * Parses value of the Class-Path manifest attribute and returns 991 * an array of URLs relative to the specified base URL. 992 */ 993 private URL[] parseClassPath(URL base, String value) 994 throws MalformedURLException 995 { 996 StringTokenizer st = new StringTokenizer(value); 997 URL[] urls = new URL[st.countTokens()]; 998 int i = 0; 999 while (st.hasMoreTokens()) { 1000 String path = st.nextToken(); 1001 urls[i] = new URL(base, path); 1002 i++; 1003 } 1004 return urls; 1005 } 1006 } 1007 1008 /* 1009 * Inner class used to represent a loader of classes and resources 1010 * from a file URL that refers to a directory. 1011 */ 1012 private static class FileLoader extends Loader { 1013 /* Canonicalized File */ 1014 private File dir; 1015 1016 FileLoader(URL url) throws IOException { 1017 super(url); 1018 if (!"file".equals(url.getProtocol())) { 1019 throw new IllegalArgumentException("url"); 1020 } 1021 String path = url.getFile().replace('/', File.separatorChar); 1022 path = ParseUtil.decode(path); 1023 dir = (new File(path)).getCanonicalFile(); 1024 } 1025 1026 /* 1027 * Returns the URL for a resource with the specified name 1028 */ 1029 URL findResource(final String name, boolean check) { 1030 Resource rsc = getResource(name, check); 1031 if (rsc != null) { 1032 return rsc.getURL(); 1033 } 1034 return null; 1035 } 1036 1037 Resource getResource(final String name, boolean check) { 1038 final URL url; 1039 try { 1040 URL normalizedBase = new URL(getBaseURL(), "."); 1041 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 1042 1043 if (url.getFile().startsWith(normalizedBase.getFile()) == false) { 1044 // requested resource had ../..'s in path 1045 return null; 1046 } 1047 1048 if (check) 1049 URLClassPath.check(url); 1050 1051 final File file; 1052 if (name.indexOf("..") != -1) { 1053 file = (new File(dir, name.replace('/', File.separatorChar))) 1054 .getCanonicalFile(); 1055 if ( !((file.getPath()).startsWith(dir.getPath())) ) { 1056 /* outside of base dir */ 1057 return null; 1058 } 1059 } else { 1060 file = new File(dir, name.replace('/', File.separatorChar)); 1061 } 1062 1063 if (file.exists()) { 1064 return new Resource() { 1065 public String getName() { return name; }; 1066 public URL getURL() { return url; }; 1067 public URL getCodeSourceURL() { return getBaseURL(); }; 1068 public InputStream getInputStream() throws IOException 1069 { return new FileInputStream(file); }; 1070 public int getContentLength() throws IOException 1071 { return (int)file.length(); }; 1072 }; 1073 } 1074 } catch (Exception e) { 1075 return null; 1076 } 1077 return null; 1078 } 1079 } 1080 }