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