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