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