1 /* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.loader; 27 28 import java.io.Closeable; 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.io.FileNotFoundException; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.net.HttpURLConnection; 35 import java.net.JarURLConnection; 36 import java.net.MalformedURLException; 37 import java.net.URL; 38 import java.net.URLConnection; 39 import java.net.URLStreamHandler; 40 import java.net.URLStreamHandlerFactory; 41 import java.security.AccessControlContext; 42 import java.security.AccessControlException; 43 import java.security.AccessController; 44 import java.security.CodeSigner; 45 import java.security.Permission; 46 import java.security.PrivilegedActionException; 47 import java.security.PrivilegedExceptionAction; 48 import java.security.cert.Certificate; 49 import java.util.ArrayDeque; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Collections; 53 import java.util.Enumeration; 54 import java.util.HashMap; 55 import java.util.HashSet; 56 import java.util.LinkedList; 57 import java.util.List; 58 import java.util.NoSuchElementException; 59 import java.util.Properties; 60 import java.util.Set; 61 import java.util.StringTokenizer; 62 import java.util.jar.JarFile; 63 import java.util.zip.ZipEntry; 64 import java.util.jar.JarEntry; 65 import java.util.jar.Manifest; 66 import java.util.jar.Attributes; 67 import java.util.jar.Attributes.Name; 68 import java.util.zip.ZipFile; 69 70 import jdk.internal.misc.JavaNetURLAccess; 71 import jdk.internal.misc.JavaUtilZipFileAccess; 72 import jdk.internal.misc.SharedSecrets; 73 import jdk.internal.util.jar.InvalidJarIndexError; 74 import jdk.internal.util.jar.JarIndex; 75 import sun.net.util.URLUtil; 76 import sun.net.www.ParseUtil; 77 import sun.security.action.GetPropertyAction; 78 79 /** 80 * This class is used to maintain a search path of URLs for loading classes 81 * and resources from both JAR files and directories. 82 * 83 * @author David Connelly 84 */ 85 public class URLClassPath { 86 private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version"; 87 private static final String JAVA_VERSION; 88 private static final boolean DEBUG; 89 private static final boolean DISABLE_JAR_CHECKING; 90 private static final boolean DISABLE_ACC_CHECKING; 91 92 static { 93 Properties props = GetPropertyAction.privilegedGetProperties(); 94 JAVA_VERSION = props.getProperty("java.version"); 95 DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null); 96 String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking"); 97 DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false; 98 99 p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions"); 100 DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.equals("") : false; 101 } 102 103 /** The original search path of URLs. */ 104 private final ArrayList<URL> path; 105 106 /** The deque of unopened URLs. */ 107 private final ArrayDeque<URL> unopenedUrls; 108 109 /** The resulting search path of Loaders. */ 110 private final ArrayList<Loader> loaders = new ArrayList<>(); 111 112 /** Map of each URL opened to its corresponding Loader. */ 113 private final HashMap<String, Loader> lmap = new HashMap<>(); 114 115 /** The jar protocol handler to use when creating new URLs. */ 116 private final URLStreamHandler jarHandler; 117 118 /** Whether this URLClassLoader has been closed yet. */ 119 private boolean closed = false; 120 121 /** 122 * The context to be used when loading classes and resources. If 123 * non-null this is the context that was captured during the creation 124 * of the URLClassLoader. null implies no additional security 125 * restrictions. 126 */ 127 private final AccessControlContext acc; 128 129 /** 130 * Creates a new URLClassPath for the given URLs. The URLs will be 131 * searched in the order specified for classes and resources. A URL 132 * ending with a '/' is assumed to refer to a directory. Otherwise, 133 * the URL is assumed to refer to a JAR file. 134 * 135 * @param urls the directory and JAR file URLs to search for classes 136 * and resources 137 * @param factory the URLStreamHandlerFactory to use when creating new URLs 138 * @param acc the context to be used when loading classes and resources, may 139 * be null 140 */ 141 public URLClassPath(URL[] urls, 142 URLStreamHandlerFactory factory, 143 AccessControlContext acc) { 144 ArrayList<URL> path = new ArrayList<>(urls.length); 145 for (URL url : urls) { 146 path.add(url); 147 } 148 this.path = path; 149 this.unopenedUrls = copyToArrayDeque(path); 150 151 if (factory != null) { 152 jarHandler = factory.createURLStreamHandler("jar"); 153 } else { 154 jarHandler = null; 155 } 156 if (DISABLE_ACC_CHECKING) 157 this.acc = null; 158 else 159 this.acc = acc; 160 } 161 162 public URLClassPath(URL[] urls, AccessControlContext acc) { 163 this(urls, null, acc); 164 } 165 166 /** 167 * Constructs a URLClassPath from a class path string. 168 * 169 * @param cp the class path string 170 * @param skipEmptyElements indicates if empty elements are ignored or 171 * treated as the current working directory 172 * 173 * @apiNote Used to create the application class path. 174 */ 175 URLClassPath(String cp, boolean skipEmptyElements) { 176 ArrayList<URL> path = new ArrayList<>(); 177 if (cp != null) { 178 // map each element of class path to a file URL 179 int off = 0; 180 int next; 181 while ((next = cp.indexOf(File.pathSeparator, off)) != -1) { 182 String element = cp.substring(off, next); 183 if (element.length() > 0 || !skipEmptyElements) { 184 URL url = toFileURL(element); 185 if (url != null) path.add(url); 186 } 187 off = next + 1; 188 } 189 190 // remaining element 191 String element = cp.substring(off); 192 if (element.length() > 0 || !skipEmptyElements) { 193 URL url = toFileURL(element); 194 if (url != null) path.add(url); 195 } 196 } 197 198 this.unopenedUrls = copyToArrayDeque(path); 199 this.path = path; 200 this.jarHandler = null; 201 this.acc = null; 202 } 203 204 private <T> ArrayDeque<T> copyToArrayDeque(ArrayList<T> list) { 205 // can't simplify to deque.addAll(list) or new ArrayDeque(list); 206 // it's too early in the bootstrap to trigger use of lambdas 207 int size = list.size(); 208 ArrayDeque<T> deque = new ArrayDeque<>(size); 209 for (int i = 0; i < size; i++) 210 deque.add(list.get(i)); 211 return deque; 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 to the list of 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.push(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 { return jar.getManifest(); }; 871 public Certificate[] getCertificates() 872 { return entry.getCertificates(); }; 873 public CodeSigner[] getCodeSigners() 874 { return entry.getCodeSigners(); }; 875 }; 876 } 877 878 879 /** 880 * Returns true iff at least one resource in the jar file has the same 881 * package name as that of the specified resource name. 882 */ 883 boolean validIndex(final String name) { 884 String packageName = name; 885 int pos; 886 if ((pos = name.lastIndexOf('/')) != -1) { 887 packageName = name.substring(0, pos); 888 } 889 890 String entryName; 891 ZipEntry entry; 892 Enumeration<JarEntry> enum_ = jar.entries(); 893 while (enum_.hasMoreElements()) { 894 entry = enum_.nextElement(); 895 entryName = entry.getName(); 896 if ((pos = entryName.lastIndexOf('/')) != -1) 897 entryName = entryName.substring(0, pos); 898 if (entryName.equals(packageName)) { 899 return true; 900 } 901 } 902 return false; 903 } 904 905 /** 906 * Returns the URL for a resource with the specified name 907 */ 908 @Override 909 URL findResource(final String name, boolean check) { 910 Resource rsc = getResource(name, check); 911 if (rsc != null) { 912 return rsc.getURL(); 913 } 914 return null; 915 } 916 917 /** 918 * Returns the JAR Resource for the specified name. 919 */ 920 @Override 921 Resource getResource(final String name, boolean check) { 922 try { 923 ensureOpen(); 924 } catch (IOException e) { 925 throw new InternalError(e); 926 } 927 final JarEntry entry = jar.getJarEntry(name); 928 if (entry != null) 929 return checkResource(name, check, entry); 930 931 if (index == null) 932 return null; 933 934 HashSet<String> visited = new HashSet<>(); 935 return getResource(name, check, visited); 936 } 937 938 /** 939 * Version of getResource() that tracks the jar files that have been 940 * visited by linking through the index files. This helper method uses 941 * a HashSet to store the URLs of jar files that have been searched and 942 * uses it to avoid going into an infinite loop, looking for a 943 * non-existent resource. 944 */ 945 Resource getResource(final String name, boolean check, 946 Set<String> visited) { 947 Resource res; 948 String[] jarFiles; 949 int count = 0; 950 LinkedList<String> jarFilesList = null; 951 952 /* If there no jar files in the index that can potential contain 953 * this resource then return immediately. 954 */ 955 if ((jarFilesList = index.get(name)) == null) 956 return null; 957 958 do { 959 int size = jarFilesList.size(); 960 jarFiles = jarFilesList.toArray(new String[size]); 961 /* loop through the mapped jar file list */ 962 while (count < size) { 963 String jarName = jarFiles[count++]; 964 JarLoader newLoader; 965 final URL url; 966 967 try{ 968 url = new URL(csu, jarName); 969 String urlNoFragString = URLUtil.urlNoFragString(url); 970 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { 971 /* no loader has been set up for this jar file 972 * before 973 */ 974 newLoader = AccessController.doPrivileged( 975 new PrivilegedExceptionAction<>() { 976 public JarLoader run() throws IOException { 977 return new JarLoader(url, handler, 978 lmap, acc); 979 } 980 }, acc); 981 982 /* this newly opened jar file has its own index, 983 * merge it into the parent's index, taking into 984 * account the relative path. 985 */ 986 JarIndex newIndex = newLoader.getIndex(); 987 if (newIndex != null) { 988 int pos = jarName.lastIndexOf('/'); 989 newIndex.merge(this.index, (pos == -1 ? 990 null : jarName.substring(0, pos + 1))); 991 } 992 993 /* put it in the global hashtable */ 994 lmap.put(urlNoFragString, newLoader); 995 } 996 } catch (PrivilegedActionException pae) { 997 continue; 998 } catch (MalformedURLException e) { 999 continue; 1000 } 1001 1002 /* Note that the addition of the url to the list of visited 1003 * jars incorporates a check for presence in the hashmap 1004 */ 1005 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); 1006 if (!visitedURL) { 1007 try { 1008 newLoader.ensureOpen(); 1009 } catch (IOException e) { 1010 throw new InternalError(e); 1011 } 1012 final JarEntry entry = newLoader.jar.getJarEntry(name); 1013 if (entry != null) { 1014 return newLoader.checkResource(name, check, entry); 1015 } 1016 1017 /* Verify that at least one other resource with the 1018 * same package name as the lookedup resource is 1019 * present in the new jar 1020 */ 1021 if (!newLoader.validIndex(name)) { 1022 /* the mapping is wrong */ 1023 throw new InvalidJarIndexError("Invalid index"); 1024 } 1025 } 1026 1027 /* If newLoader is the current loader or if it is a 1028 * loader that has already been searched or if the new 1029 * loader does not have an index then skip it 1030 * and move on to the next loader. 1031 */ 1032 if (visitedURL || newLoader == this || 1033 newLoader.getIndex() == null) { 1034 continue; 1035 } 1036 1037 /* Process the index of the new loader 1038 */ 1039 if ((res = newLoader.getResource(name, check, visited)) 1040 != null) { 1041 return res; 1042 } 1043 } 1044 // Get the list of jar files again as the list could have grown 1045 // due to merging of index files. 1046 jarFilesList = index.get(name); 1047 1048 // If the count is unchanged, we are done. 1049 } while (count < jarFilesList.size()); 1050 return null; 1051 } 1052 1053 1054 /** 1055 * Returns the JAR file local class path, or null if none. 1056 */ 1057 @Override 1058 URL[] getClassPath() throws IOException { 1059 if (index != null) { 1060 return null; 1061 } 1062 1063 ensureOpen(); 1064 1065 // Only get manifest when necessary 1066 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { 1067 Manifest man = jar.getManifest(); 1068 if (man != null) { 1069 Attributes attr = man.getMainAttributes(); 1070 if (attr != null) { 1071 String value = attr.getValue(Name.CLASS_PATH); 1072 if (value != null) { 1073 return parseClassPath(csu, value); 1074 } 1075 } 1076 } 1077 } 1078 return null; 1079 } 1080 1081 /** 1082 * Parses value of the Class-Path manifest attribute and returns 1083 * an array of URLs relative to the specified base URL. 1084 */ 1085 private static URL[] parseClassPath(URL base, String value) 1086 throws MalformedURLException 1087 { 1088 StringTokenizer st = new StringTokenizer(value); 1089 URL[] urls = new URL[st.countTokens()]; 1090 int i = 0; 1091 while (st.hasMoreTokens()) { 1092 String path = st.nextToken(); 1093 urls[i] = new URL(base, path); 1094 i++; 1095 } 1096 return urls; 1097 } 1098 } 1099 1100 /** 1101 * Nested class used to represent a loader of classes and resources 1102 * from a file URL that refers to a directory. 1103 */ 1104 private static class FileLoader extends Loader { 1105 /** Canonicalized File */ 1106 private File dir; 1107 1108 FileLoader(URL url) throws IOException { 1109 super(url); 1110 if (!"file".equals(url.getProtocol())) { 1111 throw new IllegalArgumentException("url"); 1112 } 1113 String path = url.getFile().replace('/', File.separatorChar); 1114 path = ParseUtil.decode(path); 1115 dir = (new File(path)).getCanonicalFile(); 1116 } 1117 1118 /** 1119 * Returns the URL for a resource with the specified name 1120 */ 1121 @Override 1122 URL findResource(final String name, boolean check) { 1123 Resource rsc = getResource(name, check); 1124 if (rsc != null) { 1125 return rsc.getURL(); 1126 } 1127 return null; 1128 } 1129 1130 @Override 1131 Resource getResource(final String name, boolean check) { 1132 final URL url; 1133 try { 1134 URL normalizedBase = new URL(getBaseURL(), "."); 1135 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 1136 1137 if (url.getFile().startsWith(normalizedBase.getFile()) == false) { 1138 // requested resource had ../..'s in path 1139 return null; 1140 } 1141 1142 if (check) 1143 URLClassPath.check(url); 1144 1145 final File file; 1146 if (name.indexOf("..") != -1) { 1147 file = (new File(dir, name.replace('/', File.separatorChar))) 1148 .getCanonicalFile(); 1149 if ( !((file.getPath()).startsWith(dir.getPath())) ) { 1150 /* outside of base dir */ 1151 return null; 1152 } 1153 } else { 1154 file = new File(dir, name.replace('/', File.separatorChar)); 1155 } 1156 1157 if (file.exists()) { 1158 return new Resource() { 1159 public String getName() { return name; }; 1160 public URL getURL() { return url; }; 1161 public URL getCodeSourceURL() { return getBaseURL(); }; 1162 public InputStream getInputStream() throws IOException 1163 { return new FileInputStream(file); }; 1164 public int getContentLength() throws IOException 1165 { return (int)file.length(); }; 1166 }; 1167 } 1168 } catch (Exception e) { 1169 return null; 1170 } 1171 return null; 1172 } 1173 } 1174 }