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