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 /* Need to remember the jar file so it can be closed 536 * in a hurry. 537 */ 538 JarURLConnection juc = (JarURLConnection)uc; 539 jarfile = juc.getJarFile(); 540 } 541 } catch (Exception e) { 542 return null; 543 } 544 return new Resource() { 545 public String getName() { return name; } 546 public URL getURL() { return url; } 547 public URL getCodeSourceURL() { return base; } 548 public InputStream getInputStream() throws IOException { 549 return uc.getInputStream(); 550 } 551 public int getContentLength() throws IOException { 552 return uc.getContentLength(); 553 } 554 }; 555 } 556 557 /* 558 * Returns the Resource for the specified name, or null if not 559 * found or the caller does not have the permission to get the 560 * resource. 561 */ 562 Resource getResource(final String name) { 563 return getResource(name, true); 564 } 565 566 /* 567 * close this loader and release all resources 568 * method overridden in sub-classes 569 */ 570 public void close () throws IOException { 571 if (jarfile != null) { 572 jarfile.close(); 573 } 574 } 575 576 /* 577 * Returns the local class path for this loader, or null if none. 578 */ 579 URL[] getClassPath() throws IOException { 580 return null; 581 } 582 } 583 584 /* 585 * Inner class used to represent a Loader of resources from a JAR URL. 586 */ 587 static class JarLoader extends Loader { 588 private JarFile jar; 589 private URL csu; 590 private JarIndex index; 591 private MetaIndex metaIndex; 592 private URLStreamHandler handler; 593 private HashMap<String, Loader> lmap; 594 private boolean closed = false; 595 596 /* 597 * Creates a new JarLoader for the specified URL referring to 598 * a JAR file. 599 */ 600 JarLoader(URL url, URLStreamHandler jarHandler, 601 HashMap<String, Loader> loaderMap) 602 throws IOException 603 { 604 super(new URL("jar", "", -1, url + "!/", jarHandler)); 605 csu = url; 606 handler = jarHandler; 607 lmap = loaderMap; 608 609 if (!isOptimizable(url)) { 610 ensureOpen(); 611 } else { 612 String fileName = url.getFile(); 613 if (fileName != null) { 614 fileName = ParseUtil.decode(fileName); 615 File f = new File(fileName); 616 metaIndex = MetaIndex.forJar(f); 617 // If the meta index is found but the file is not 618 // installed, set metaIndex to null. A typical 619 // senario is charsets.jar which won't be installed 620 // when the user is running in certain locale environment. 621 // The side effect of null metaIndex will cause 622 // ensureOpen get called so that IOException is thrown. 623 if (metaIndex != null && !f.exists()) { 624 metaIndex = null; 625 } 626 } 627 628 // metaIndex is null when either there is no such jar file 629 // entry recorded in meta-index file or such jar file is 630 // missing in JRE. See bug 6340399. 631 if (metaIndex == null) { 632 ensureOpen(); 633 } 634 } 635 } 636 637 @Override 638 public void close () throws IOException { 639 // closing is synchronized at higher level 640 if (!closed) { 641 closed = true; 642 // in case not already open. 643 ensureOpen(); 644 jar.close(); 645 } 646 } 647 648 JarFile getJarFile () { 649 return jar; 650 } 651 652 private boolean isOptimizable(URL url) { 653 return "file".equals(url.getProtocol()); 654 } 655 656 private void ensureOpen() throws IOException { 657 if (jar == null) { 658 try { 659 java.security.AccessController.doPrivileged( 660 new java.security.PrivilegedExceptionAction<Void>() { 661 public Void run() throws IOException { 662 if (DEBUG) { 663 System.err.println("Opening " + csu); 664 Thread.dumpStack(); 665 } 666 667 jar = getJarFile(csu); 668 index = JarIndex.getJarIndex(jar, metaIndex); 669 if (index != null) { 670 String[] jarfiles = index.getJarFiles(); 671 // Add all the dependent URLs to the lmap so that loaders 672 // will not be created for them by URLClassPath.getLoader(int) 673 // if the same URL occurs later on the main class path. We set 674 // Loader to null here to avoid creating a Loader for each 675 // URL until we actually need to try to load something from them. 676 for(int i = 0; i < jarfiles.length; i++) { 677 try { 678 URL jarURL = new URL(csu, jarfiles[i]); 679 // If a non-null loader already exists, leave it alone. 680 String urlNoFragString = URLUtil.urlNoFragString(jarURL); 681 if (!lmap.containsKey(urlNoFragString)) { 682 lmap.put(urlNoFragString, null); 683 } 684 } catch (MalformedURLException e) { 685 continue; 686 } 687 } 688 } 689 return null; 690 } 691 } 692 ); 693 } catch (java.security.PrivilegedActionException pae) { 694 throw (IOException)pae.getException(); 695 } 696 } 697 } 698 699 private JarFile getJarFile(URL url) throws IOException { 700 // Optimize case where url refers to a local jar file 701 if (isOptimizable(url)) { 702 FileURLMapper p = new FileURLMapper (url); 703 if (!p.exists()) { 704 throw new FileNotFoundException(p.getPath()); 705 } 706 return new JarFile (p.getPath()); 707 } 708 URLConnection uc = getBaseURL().openConnection(); 709 uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION); 710 return ((JarURLConnection)uc).getJarFile(); 711 } 712 713 /* 714 * Returns the index of this JarLoader if it exists. 715 */ 716 JarIndex getIndex() { 717 try { 718 ensureOpen(); 719 } catch (IOException e) { 720 throw new InternalError(e); 721 } 722 return index; 723 } 724 725 /* 726 * Creates the resource and if the check flag is set to true, checks if 727 * is its okay to return the resource. 728 */ 729 Resource checkResource(final String name, boolean check, 730 final JarEntry entry) { 731 732 final URL url; 733 try { 734 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 735 if (check) { 736 URLClassPath.check(url); 737 } 738 } catch (MalformedURLException e) { 739 return null; 740 // throw new IllegalArgumentException("name"); 741 } catch (IOException e) { 742 return null; 743 } catch (AccessControlException e) { 744 return null; 745 } 746 747 return new Resource() { 748 public String getName() { return name; } 749 public URL getURL() { return url; } 750 public URL getCodeSourceURL() { return csu; } 751 public InputStream getInputStream() throws IOException 752 { return jar.getInputStream(entry); } 753 public int getContentLength() 754 { return (int)entry.getSize(); } 755 public Manifest getManifest() throws IOException 756 { return jar.getManifest(); }; 757 public Certificate[] getCertificates() 758 { return entry.getCertificates(); }; 759 public CodeSigner[] getCodeSigners() 760 { return entry.getCodeSigners(); }; 761 }; 762 } 763 764 765 /* 766 * Returns true iff atleast one resource in the jar file has the same 767 * package name as that of the specified resource name. 768 */ 769 boolean validIndex(final String name) { 770 String packageName = name; 771 int pos; 772 if((pos = name.lastIndexOf("/")) != -1) { 773 packageName = name.substring(0, pos); 774 } 775 776 String entryName; 777 ZipEntry entry; 778 Enumeration<JarEntry> enum_ = jar.entries(); 779 while (enum_.hasMoreElements()) { 780 entry = enum_.nextElement(); 781 entryName = entry.getName(); 782 if((pos = entryName.lastIndexOf("/")) != -1) 783 entryName = entryName.substring(0, pos); 784 if (entryName.equals(packageName)) { 785 return true; 786 } 787 } 788 return false; 789 } 790 791 /* 792 * Returns the URL for a resource with the specified name 793 */ 794 URL findResource(final String name, boolean check) { 795 Resource rsc = getResource(name, check); 796 if (rsc != null) { 797 return rsc.getURL(); 798 } 799 return null; 800 } 801 802 /* 803 * Returns the JAR Resource for the specified name. 804 */ 805 Resource getResource(final String name, boolean check) { 806 if (metaIndex != null) { 807 if (!metaIndex.mayContain(name)) { 808 return null; 809 } 810 } 811 812 try { 813 ensureOpen(); 814 } catch (IOException e) { 815 throw new InternalError(e); 816 } 817 final JarEntry entry = jar.getJarEntry(name); 818 if (entry != null) 819 return checkResource(name, check, entry); 820 821 if (index == null) 822 return null; 823 824 HashSet<String> visited = new HashSet<String>(); 825 return getResource(name, check, visited); 826 } 827 828 /* 829 * Version of getResource() that tracks the jar files that have been 830 * visited by linking through the index files. This helper method uses 831 * a HashSet to store the URLs of jar files that have been searched and 832 * uses it to avoid going into an infinite loop, looking for a 833 * non-existent resource 834 */ 835 Resource getResource(final String name, boolean check, 836 Set<String> visited) { 837 838 Resource res; 839 String[] jarFiles; 840 int count = 0; 841 LinkedList<String> jarFilesList = null; 842 843 /* If there no jar files in the index that can potential contain 844 * this resource then return immediately. 845 */ 846 if((jarFilesList = index.get(name)) == null) 847 return null; 848 849 do { 850 int size = jarFilesList.size(); 851 jarFiles = jarFilesList.toArray(new String[size]); 852 /* loop through the mapped jar file list */ 853 while(count < size) { 854 String jarName = jarFiles[count++]; 855 JarLoader newLoader; 856 final URL url; 857 858 try{ 859 url = new URL(csu, jarName); 860 String urlNoFragString = URLUtil.urlNoFragString(url); 861 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { 862 /* no loader has been set up for this jar file 863 * before 864 */ 865 newLoader = AccessController.doPrivileged( 866 new PrivilegedExceptionAction<JarLoader>() { 867 public JarLoader run() throws IOException { 868 return new JarLoader(url, handler, 869 lmap); 870 } 871 }); 872 873 /* this newly opened jar file has its own index, 874 * merge it into the parent's index, taking into 875 * account the relative path. 876 */ 877 JarIndex newIndex = newLoader.getIndex(); 878 if(newIndex != null) { 879 int pos = jarName.lastIndexOf("/"); 880 newIndex.merge(this.index, (pos == -1 ? 881 null : jarName.substring(0, pos + 1))); 882 } 883 884 /* put it in the global hashtable */ 885 lmap.put(urlNoFragString, newLoader); 886 } 887 } catch (java.security.PrivilegedActionException pae) { 888 continue; 889 } catch (MalformedURLException e) { 890 continue; 891 } 892 893 894 /* Note that the addition of the url to the list of visited 895 * jars incorporates a check for presence in the hashmap 896 */ 897 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); 898 if (!visitedURL) { 899 try { 900 newLoader.ensureOpen(); 901 } catch (IOException e) { 902 throw new InternalError(e); 903 } 904 final JarEntry entry = newLoader.jar.getJarEntry(name); 905 if (entry != null) { 906 return newLoader.checkResource(name, check, entry); 907 } 908 909 /* Verify that at least one other resource with the 910 * same package name as the lookedup resource is 911 * present in the new jar 912 */ 913 if (!newLoader.validIndex(name)) { 914 /* the mapping is wrong */ 915 throw new InvalidJarIndexException("Invalid index"); 916 } 917 } 918 919 /* If newLoader is the current loader or if it is a 920 * loader that has already been searched or if the new 921 * loader does not have an index then skip it 922 * and move on to the next loader. 923 */ 924 if (visitedURL || newLoader == this || 925 newLoader.getIndex() == null) { 926 continue; 927 } 928 929 /* Process the index of the new loader 930 */ 931 if((res = newLoader.getResource(name, check, visited)) 932 != null) { 933 return res; 934 } 935 } 936 // Get the list of jar files again as the list could have grown 937 // due to merging of index files. 938 jarFilesList = index.get(name); 939 940 // If the count is unchanged, we are done. 941 } while(count < jarFilesList.size()); 942 return null; 943 } 944 945 946 /* 947 * Returns the JAR file local class path, or null if none. 948 */ 949 URL[] getClassPath() throws IOException { 950 if (index != null) { 951 return null; 952 } 953 954 if (metaIndex != null) { 955 return null; 956 } 957 958 ensureOpen(); 959 parseExtensionsDependencies(); 960 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary 961 Manifest man = jar.getManifest(); 962 if (man != null) { 963 Attributes attr = man.getMainAttributes(); 964 if (attr != null) { 965 String value = attr.getValue(Name.CLASS_PATH); 966 if (value != null) { 967 return parseClassPath(csu, value); 968 } 969 } 970 } 971 } 972 return null; 973 } 974 975 /* 976 * parse the standard extension dependencies 977 */ 978 private void parseExtensionsDependencies() throws IOException { 979 ExtensionDependency.checkExtensionsDependencies(jar); 980 } 981 982 /* 983 * Parses value of the Class-Path manifest attribute and returns 984 * an array of URLs relative to the specified base URL. 985 */ 986 private URL[] parseClassPath(URL base, String value) 987 throws MalformedURLException 988 { 989 StringTokenizer st = new StringTokenizer(value); 990 URL[] urls = new URL[st.countTokens()]; 991 int i = 0; 992 while (st.hasMoreTokens()) { 993 String path = st.nextToken(); 994 urls[i] = new URL(base, path); 995 i++; 996 } 997 return urls; 998 } 999 } 1000 1001 /* 1002 * Inner class used to represent a loader of classes and resources 1003 * from a file URL that refers to a directory. 1004 */ 1005 private static class FileLoader extends Loader { 1006 /* Canonicalized File */ 1007 private File dir; 1008 1009 FileLoader(URL url) throws IOException { 1010 super(url); 1011 if (!"file".equals(url.getProtocol())) { 1012 throw new IllegalArgumentException("url"); 1013 } 1014 String path = url.getFile().replace('/', File.separatorChar); 1015 path = ParseUtil.decode(path); 1016 dir = (new File(path)).getCanonicalFile(); 1017 } 1018 1019 /* 1020 * Returns the URL for a resource with the specified name 1021 */ 1022 URL findResource(final String name, boolean check) { 1023 Resource rsc = getResource(name, check); 1024 if (rsc != null) { 1025 return rsc.getURL(); 1026 } 1027 return null; 1028 } 1029 1030 Resource getResource(final String name, boolean check) { 1031 final URL url; 1032 try { 1033 URL normalizedBase = new URL(getBaseURL(), "."); 1034 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 1035 1036 if (url.getFile().startsWith(normalizedBase.getFile()) == false) { 1037 // requested resource had ../..'s in path 1038 return null; 1039 } 1040 1041 if (check) 1042 URLClassPath.check(url); 1043 1044 final File file; 1045 if (name.indexOf("..") != -1) { 1046 file = (new File(dir, name.replace('/', File.separatorChar))) 1047 .getCanonicalFile(); 1048 if ( !((file.getPath()).startsWith(dir.getPath())) ) { 1049 /* outside of base dir */ 1050 return null; 1051 } 1052 } else { 1053 file = new File(dir, name.replace('/', File.separatorChar)); 1054 } 1055 1056 if (file.exists()) { 1057 return new Resource() { 1058 public String getName() { return name; }; 1059 public URL getURL() { return url; }; 1060 public URL getCodeSourceURL() { return getBaseURL(); }; 1061 public InputStream getInputStream() throws IOException 1062 { return new FileInputStream(file); }; 1063 public int getContentLength() throws IOException 1064 { return (int)file.length(); }; 1065 }; 1066 } 1067 } catch (Exception e) { 1068 return null; 1069 } 1070 return null; 1071 } 1072 } 1073 }