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