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