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 * Check whether the resource URL should be returned. 406 * Return null on security check failure. 407 * Called by java.net.URLClassLoader. 408 */ 409 public static URL checkURL(URL url) { 410 if (url != null) { 411 try { 412 check(url); 413 } catch (Exception e) { 414 return null; 415 } 416 } 417 return url; 418 } 419 420 /* 421 * Check whether the resource URL should be returned. 422 * Throw exception on failure. 423 * Called internally within this file. 424 */ 425 public static void check(URL url) throws IOException { 426 SecurityManager security = System.getSecurityManager(); 427 if (security != null) { 428 URLConnection urlConnection = url.openConnection(); 429 Permission perm = urlConnection.getPermission(); 430 if (perm != null) { 431 try { 432 security.checkPermission(perm); 433 } catch (SecurityException se) { 434 // fallback to checkRead/checkConnect for pre 1.2 435 // security managers 436 if ((perm instanceof java.io.FilePermission) && 437 perm.getActions().indexOf("read") != -1) { 438 security.checkRead(perm.getName()); 439 } else if ((perm instanceof 440 java.net.SocketPermission) && 441 perm.getActions().indexOf("connect") != -1) { 442 URL locUrl = url; 443 if (urlConnection instanceof JarURLConnection) { 444 locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); 445 } 446 security.checkConnect(locUrl.getHost(), 447 locUrl.getPort()); 448 } else { 449 throw se; 450 } 451 } 452 } 453 } 454 } 455 456 /** 457 * Inner class used to represent a loader of resources and classes 458 * from a base URL. 459 */ 460 private static class Loader implements Closeable { 461 private final URL base; 462 private JarFile jarfile; // if this points to a jar file 463 464 /* 465 * Creates a new Loader for the specified URL. 466 */ 467 Loader(URL url) { 468 base = url; 469 } 470 471 /* 472 * Returns the base URL for this Loader. 473 */ 474 URL getBaseURL() { 475 return base; 476 } 477 478 URL findResource(final String name, boolean check) { 479 URL url; 480 try { 481 url = new URL(base, ParseUtil.encodePath(name, false)); 482 } catch (MalformedURLException e) { 483 throw new IllegalArgumentException("name"); 484 } 485 486 try { 487 if (check) { 488 URLClassPath.check(url); 489 } 490 491 /* 492 * For a HTTP connection we use the HEAD method to 493 * check if the resource exists. 494 */ 495 URLConnection uc = url.openConnection(); 496 if (uc instanceof HttpURLConnection) { 497 HttpURLConnection hconn = (HttpURLConnection)uc; 498 hconn.setRequestMethod("HEAD"); 499 if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) { 500 return null; 501 } 502 } else { 503 // our best guess for the other cases 504 uc.setUseCaches(false); 505 InputStream is = uc.getInputStream(); 506 is.close(); 507 } 508 return url; 509 } catch (Exception e) { 510 return null; 511 } 512 } 513 514 Resource getResource(final String name, boolean check) { 515 final URL url; 516 try { 517 url = new URL(base, ParseUtil.encodePath(name, false)); 518 } catch (MalformedURLException e) { 519 throw new IllegalArgumentException("name"); 520 } 521 final URLConnection uc; 522 try { 523 if (check) { 524 URLClassPath.check(url); 525 } 526 uc = url.openConnection(); 527 InputStream in = uc.getInputStream(); 528 if (uc instanceof JarURLConnection) { 529 /* Need to remember the jar file so it can be closed 530 * in a hurry. 531 */ 532 JarURLConnection juc = (JarURLConnection)uc; 533 jarfile = JarLoader.checkJar(juc.getJarFile()); 534 } 535 } catch (Exception e) { 536 return null; 537 } 538 return new Resource() { 539 public String getName() { return name; } 540 public URL getURL() { return url; } 541 public URL getCodeSourceURL() { return base; } 542 public InputStream getInputStream() throws IOException { 543 return uc.getInputStream(); 544 } 545 public int getContentLength() throws IOException { 546 return uc.getContentLength(); 547 } 548 }; 549 } 550 551 /* 552 * Returns the Resource for the specified name, or null if not 553 * found or the caller does not have the permission to get the 554 * resource. 555 */ 556 Resource getResource(final String name) { 557 return getResource(name, true); 558 } 559 560 /* 561 * close this loader and release all resources 562 * method overridden in sub-classes 563 */ 564 public void close () throws IOException { 565 if (jarfile != null) { 566 jarfile.close(); 567 } 568 } 569 570 /* 571 * Returns the local class path for this loader, or null if none. 572 */ 573 URL[] getClassPath() throws IOException { 574 return null; 575 } 576 } 577 578 /* 579 * Inner class used to represent a Loader of resources from a JAR URL. 580 */ 581 static class JarLoader extends Loader { 582 private JarFile jar; 583 private URL csu; 584 private JarIndex index; 585 private URLStreamHandler handler; 586 private HashMap<String, Loader> lmap; 587 private boolean closed = false; 588 private static final JavaUtilZipFileAccess zipAccess = 589 SharedSecrets.getJavaUtilZipFileAccess(); 590 591 /* 592 * Creates a new JarLoader for the specified URL referring to 593 * a JAR file. 594 */ 595 JarLoader(URL url, URLStreamHandler jarHandler, 596 HashMap<String, Loader> loaderMap) 597 throws IOException 598 { 599 super(new URL("jar", "", -1, url + "!/", jarHandler)); 600 csu = url; 601 handler = jarHandler; 602 lmap = loaderMap; 603 604 ensureOpen(); 605 } 606 607 @Override 608 public void close () throws IOException { 609 // closing is synchronized at higher level 610 if (!closed) { 611 closed = true; 612 // in case not already open. 613 ensureOpen(); 614 jar.close(); 615 } 616 } 617 618 JarFile getJarFile () { 619 return jar; 620 } 621 622 private boolean isOptimizable(URL url) { 623 return "file".equals(url.getProtocol()); 624 } 625 626 private void ensureOpen() throws IOException { 627 if (jar == null) { 628 try { 629 java.security.AccessController.doPrivileged( 630 new java.security.PrivilegedExceptionAction<>() { 631 public Void run() throws IOException { 632 if (DEBUG) { 633 System.err.println("Opening " + csu); 634 Thread.dumpStack(); 635 } 636 637 jar = getJarFile(csu); 638 index = JarIndex.getJarIndex(jar); 639 if (index != null) { 640 String[] jarfiles = index.getJarFiles(); 641 // Add all the dependent URLs to the lmap so that loaders 642 // will not be created for them by URLClassPath.getLoader(int) 643 // if the same URL occurs later on the main class path. We set 644 // Loader to null here to avoid creating a Loader for each 645 // URL until we actually need to try to load something from them. 646 for(int i = 0; i < jarfiles.length; i++) { 647 try { 648 URL jarURL = new URL(csu, jarfiles[i]); 649 // If a non-null loader already exists, leave it alone. 650 String urlNoFragString = URLUtil.urlNoFragString(jarURL); 651 if (!lmap.containsKey(urlNoFragString)) { 652 lmap.put(urlNoFragString, null); 653 } 654 } catch (MalformedURLException e) { 655 continue; 656 } 657 } 658 } 659 return null; 660 } 661 } 662 ); 663 } catch (java.security.PrivilegedActionException pae) { 664 throw (IOException)pae.getException(); 665 } 666 } 667 } 668 669 /* Throws if the given jar file is does not start with the correct LOC */ 670 static JarFile checkJar(JarFile jar) throws IOException { 671 if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING 672 && !zipAccess.startsWithLocHeader(jar)) { 673 IOException x = new IOException("Invalid Jar file"); 674 try { 675 jar.close(); 676 } catch (IOException ex) { 677 x.addSuppressed(ex); 678 } 679 throw x; 680 } 681 682 return jar; 683 } 684 685 private JarFile getJarFile(URL url) throws IOException { 686 // Optimize case where url refers to a local jar file 687 if (isOptimizable(url)) { 688 FileURLMapper p = new FileURLMapper (url); 689 if (!p.exists()) { 690 throw new FileNotFoundException(p.getPath()); 691 } 692 return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ, 693 JarFile.Release.RUNTIME)); 694 } 695 URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection(); 696 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION); 697 JarFile jarFile = ((JarURLConnection)uc).getJarFile(); 698 return checkJar(jarFile); 699 } 700 701 /* 702 * Returns the index of this JarLoader if it exists. 703 */ 704 JarIndex getIndex() { 705 try { 706 ensureOpen(); 707 } catch (IOException e) { 708 throw new InternalError(e); 709 } 710 return index; 711 } 712 713 /* 714 * Creates the resource and if the check flag is set to true, checks if 715 * is its okay to return the resource. 716 */ 717 Resource checkResource(final String name, boolean check, 718 final JarEntry entry) { 719 720 final URL url; 721 try { 722 if (jar.isMultiRelease()) { 723 // add #runtime fragment to tell JarURLConnection to use 724 // runtime versioning if the underlying jar file is multi-release 725 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false) + "#runtime"); 726 } else { 727 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 728 } 729 if (check) { 730 URLClassPath.check(url); 731 } 732 } catch (MalformedURLException e) { 733 return null; 734 // throw new IllegalArgumentException("name"); 735 } catch (IOException e) { 736 return null; 737 } catch (AccessControlException e) { 738 return null; 739 } 740 741 return new Resource() { 742 public String getName() { return name; } 743 public URL getURL() { return url; } 744 public URL getCodeSourceURL() { return csu; } 745 public InputStream getInputStream() throws IOException 746 { return jar.getInputStream(entry); } 747 public int getContentLength() 748 { return (int)entry.getSize(); } 749 public Manifest getManifest() throws IOException 750 { return jar.getManifest(); }; 751 public Certificate[] getCertificates() 752 { return entry.getCertificates(); }; 753 public CodeSigner[] getCodeSigners() 754 { return entry.getCodeSigners(); }; 755 }; 756 } 757 758 759 /* 760 * Returns true iff atleast one resource in the jar file has the same 761 * package name as that of the specified resource name. 762 */ 763 boolean validIndex(final String name) { 764 String packageName = name; 765 int pos; 766 if((pos = name.lastIndexOf('/')) != -1) { 767 packageName = name.substring(0, pos); 768 } 769 770 String entryName; 771 ZipEntry entry; 772 Enumeration<JarEntry> enum_ = jar.entries(); 773 while (enum_.hasMoreElements()) { 774 entry = enum_.nextElement(); 775 entryName = entry.getName(); 776 if((pos = entryName.lastIndexOf('/')) != -1) 777 entryName = entryName.substring(0, pos); 778 if (entryName.equals(packageName)) { 779 return true; 780 } 781 } 782 return false; 783 } 784 785 /* 786 * Returns the URL for a resource with the specified name 787 */ 788 URL findResource(final String name, boolean check) { 789 Resource rsc = getResource(name, check); 790 if (rsc != null) { 791 return rsc.getURL(); 792 } 793 return null; 794 } 795 796 /* 797 * Returns the JAR Resource for the specified name. 798 */ 799 Resource getResource(final String name, boolean check) { 800 try { 801 ensureOpen(); 802 } catch (IOException e) { 803 throw new InternalError(e); 804 } 805 final JarEntry entry = jar.getJarEntry(name); 806 if (entry != null) 807 return checkResource(name, check, entry); 808 809 if (index == null) 810 return null; 811 812 HashSet<String> visited = new HashSet<>(); 813 return getResource(name, check, visited); 814 } 815 816 /* 817 * Version of getResource() that tracks the jar files that have been 818 * visited by linking through the index files. This helper method uses 819 * a HashSet to store the URLs of jar files that have been searched and 820 * uses it to avoid going into an infinite loop, looking for a 821 * non-existent resource 822 */ 823 Resource getResource(final String name, boolean check, 824 Set<String> visited) { 825 826 Resource res; 827 String[] jarFiles; 828 int count = 0; 829 LinkedList<String> jarFilesList = null; 830 831 /* If there no jar files in the index that can potential contain 832 * this resource then return immediately. 833 */ 834 if((jarFilesList = index.get(name)) == null) 835 return null; 836 837 do { 838 int size = jarFilesList.size(); 839 jarFiles = jarFilesList.toArray(new String[size]); 840 /* loop through the mapped jar file list */ 841 while(count < size) { 842 String jarName = jarFiles[count++]; 843 JarLoader newLoader; 844 final URL url; 845 846 try{ 847 url = new URL(csu, jarName); 848 String urlNoFragString = URLUtil.urlNoFragString(url); 849 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { 850 /* no loader has been set up for this jar file 851 * before 852 */ 853 newLoader = AccessController.doPrivileged( 854 new PrivilegedExceptionAction<>() { 855 public JarLoader run() throws IOException { 856 return new JarLoader(url, handler, 857 lmap); 858 } 859 }); 860 861 /* this newly opened jar file has its own index, 862 * merge it into the parent's index, taking into 863 * account the relative path. 864 */ 865 JarIndex newIndex = newLoader.getIndex(); 866 if(newIndex != null) { 867 int pos = jarName.lastIndexOf('/'); 868 newIndex.merge(this.index, (pos == -1 ? 869 null : jarName.substring(0, pos + 1))); 870 } 871 872 /* put it in the global hashtable */ 873 lmap.put(urlNoFragString, newLoader); 874 } 875 } catch (java.security.PrivilegedActionException pae) { 876 continue; 877 } catch (MalformedURLException e) { 878 continue; 879 } 880 881 882 /* Note that the addition of the url to the list of visited 883 * jars incorporates a check for presence in the hashmap 884 */ 885 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); 886 if (!visitedURL) { 887 try { 888 newLoader.ensureOpen(); 889 } catch (IOException e) { 890 throw new InternalError(e); 891 } 892 final JarEntry entry = newLoader.jar.getJarEntry(name); 893 if (entry != null) { 894 return newLoader.checkResource(name, check, entry); 895 } 896 897 /* Verify that at least one other resource with the 898 * same package name as the lookedup resource is 899 * present in the new jar 900 */ 901 if (!newLoader.validIndex(name)) { 902 /* the mapping is wrong */ 903 throw new InvalidJarIndexException("Invalid index"); 904 } 905 } 906 907 /* If newLoader is the current loader or if it is a 908 * loader that has already been searched or if the new 909 * loader does not have an index then skip it 910 * and move on to the next loader. 911 */ 912 if (visitedURL || newLoader == this || 913 newLoader.getIndex() == null) { 914 continue; 915 } 916 917 /* Process the index of the new loader 918 */ 919 if((res = newLoader.getResource(name, check, visited)) 920 != null) { 921 return res; 922 } 923 } 924 // Get the list of jar files again as the list could have grown 925 // due to merging of index files. 926 jarFilesList = index.get(name); 927 928 // If the count is unchanged, we are done. 929 } while(count < jarFilesList.size()); 930 return null; 931 } 932 933 934 /* 935 * Returns the JAR file local class path, or null if none. 936 */ 937 URL[] getClassPath() throws IOException { 938 if (index != null) { 939 return null; 940 } 941 942 ensureOpen(); 943 944 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary 945 Manifest man = jar.getManifest(); 946 if (man != null) { 947 Attributes attr = man.getMainAttributes(); 948 if (attr != null) { 949 String value = attr.getValue(Name.CLASS_PATH); 950 if (value != null) { 951 return parseClassPath(csu, value); 952 } 953 } 954 } 955 } 956 return null; 957 } 958 959 /* 960 * Parses value of the Class-Path manifest attribute and returns 961 * an array of URLs relative to the specified base URL. 962 */ 963 private URL[] parseClassPath(URL base, String value) 964 throws MalformedURLException 965 { 966 StringTokenizer st = new StringTokenizer(value); 967 URL[] urls = new URL[st.countTokens()]; 968 int i = 0; 969 while (st.hasMoreTokens()) { 970 String path = st.nextToken(); 971 urls[i] = new URL(base, path); 972 i++; 973 } 974 return urls; 975 } 976 } 977 978 /* 979 * Inner class used to represent a loader of classes and resources 980 * from a file URL that refers to a directory. 981 */ 982 private static class FileLoader extends Loader { 983 /* Canonicalized File */ 984 private File dir; 985 986 FileLoader(URL url) throws IOException { 987 super(url); 988 if (!"file".equals(url.getProtocol())) { 989 throw new IllegalArgumentException("url"); 990 } 991 String path = url.getFile().replace('/', File.separatorChar); 992 path = ParseUtil.decode(path); 993 dir = (new File(path)).getCanonicalFile(); 994 } 995 996 /* 997 * Returns the URL for a resource with the specified name 998 */ 999 URL findResource(final String name, boolean check) { 1000 Resource rsc = getResource(name, check); 1001 if (rsc != null) { 1002 return rsc.getURL(); 1003 } 1004 return null; 1005 } 1006 1007 Resource getResource(final String name, boolean check) { 1008 final URL url; 1009 try { 1010 URL normalizedBase = new URL(getBaseURL(), "."); 1011 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 1012 1013 if (url.getFile().startsWith(normalizedBase.getFile()) == false) { 1014 // requested resource had ../..'s in path 1015 return null; 1016 } 1017 1018 if (check) 1019 URLClassPath.check(url); 1020 1021 final File file; 1022 if (name.indexOf("..") != -1) { 1023 file = (new File(dir, name.replace('/', File.separatorChar))) 1024 .getCanonicalFile(); 1025 if ( !((file.getPath()).startsWith(dir.getPath())) ) { 1026 /* outside of base dir */ 1027 return null; 1028 } 1029 } else { 1030 file = new File(dir, name.replace('/', File.separatorChar)); 1031 } 1032 1033 if (file.exists()) { 1034 return new Resource() { 1035 public String getName() { return name; }; 1036 public URL getURL() { return url; }; 1037 public URL getCodeSourceURL() { return getBaseURL(); }; 1038 public InputStream getInputStream() throws IOException 1039 { return new FileInputStream(file); }; 1040 public int getContentLength() throws IOException 1041 { return (int)file.length(); }; 1042 }; 1043 } 1044 } catch (Exception e) { 1045 return null; 1046 } 1047 return null; 1048 } 1049 } 1050 }