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