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