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