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