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