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