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