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 /* The context to be used when loading classes and resources. If non-null 122 * this is the context that was captured during the creation of the 123 * URLClassLoader. null implies no additional security restrictions. */ 124 private final AccessControlContext acc; 125 126 /** 127 * Creates a new URLClassPath for the given URLs. The URLs will be 128 * searched in the order specified for classes and resources. A URL 129 * ending with a '/' is assumed to refer to a directory. Otherwise, 130 * the URL is assumed to refer to a JAR file. 131 * 132 * @param urls the directory and JAR file URLs to search for classes 133 * and resources 134 * @param factory the URLStreamHandlerFactory to use when creating new URLs 135 * @param acc the context to be used when loading classes and resources, may 136 * be null 137 */ 138 public URLClassPath(URL[] urls, 139 URLStreamHandlerFactory factory, 140 AccessControlContext acc) { 141 ArrayList<URL> path = new ArrayList<>(urls.length); 142 ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(urls.length); 143 for (URL url : urls) { 144 path.add(url); 145 unopenedUrls.add(url); 146 } 147 this.path = path; 148 this.unopenedUrls = unopenedUrls; 149 150 if (factory != null) { 151 jarHandler = factory.createURLStreamHandler("jar"); 152 } else { 153 jarHandler = null; 154 } 155 if (DISABLE_ACC_CHECKING) 156 this.acc = null; 157 else 158 this.acc = acc; 159 } 160 161 public URLClassPath(URL[] urls, AccessControlContext acc) { 162 this(urls, null, acc); 163 } 164 165 /** 166 * Constructs a URLClassPath from a class path string. 167 * 168 * @param cp the class path string 169 * @param skipEmptyElements indicates if empty elements are ignored or 170 * treated as the current working directory 171 * 172 * @apiNote Used to create the application class path. 173 */ 174 URLClassPath(String cp, boolean skipEmptyElements) { 175 ArrayList<URL> path = new ArrayList<>(); 176 if (cp != null) { 177 // map each element of class path to a file URL 178 int off = 0; 179 int next; 180 while ((next = cp.indexOf(File.pathSeparator, off)) != -1) { 181 String element = cp.substring(off, next); 182 if (element.length() > 0 || !skipEmptyElements) { 183 URL url = toFileURL(element); 184 if (url != null) path.add(url); 185 } 186 off = next + 1; 187 } 188 189 // remaining element 190 String element = cp.substring(off); 191 if (element.length() > 0 || !skipEmptyElements) { 192 URL url = toFileURL(element); 193 if (url != null) path.add(url); 194 } 195 } 196 197 // can't use ArrayDeque#addAll or new ArrayDeque(Collection); 198 // it's too early in the bootstrap to trigger use of lambdas 199 int size = path.size(); 200 ArrayDeque<URL> unopenedUrls = new ArrayDeque<>(size); 201 for (int i = 0; i < size; i++) 202 unopenedUrls.add(path.get(i)); 203 204 this.unopenedUrls = unopenedUrls; 205 this.path = path; 206 this.jarHandler = null; 207 this.acc = null; 208 } 209 210 public synchronized List<IOException> closeLoaders() { 211 if (closed) { 212 return Collections.emptyList(); 213 } 214 List<IOException> result = new LinkedList<>(); 215 for (Loader loader : loaders) { 216 try { 217 loader.close(); 218 } catch (IOException e) { 219 result.add(e); 220 } 221 } 222 closed = true; 223 return result; 224 } 225 226 /** 227 * Appends the specified URL to the search path of directory and JAR 228 * file URLs from which to load classes and resources. 229 * <p> 230 * If the URL specified is null or is already in the list of 231 * URLs, then invoking this method has no effect. 232 */ 233 public synchronized void addURL(URL url) { 234 if (closed || url == null) 235 return; 236 synchronized (unopenedUrls) { 237 if (! path.contains(url)) { 238 unopenedUrls.addLast(url); 239 path.add(url); 240 } 241 } 242 } 243 244 /** 245 * Appends the specified file path as a file URL to the search path. 246 */ 247 public void addFile(String s) { 248 URL url = toFileURL(s); 249 if (url != null) { 250 addURL(url); 251 } 252 } 253 254 /** 255 * Returns a file URL for the given file path. 256 */ 257 private static URL toFileURL(String s) { 258 try { 259 File f = new File(s).getCanonicalFile(); 260 return ParseUtil.fileToEncodedURL(f); 261 } catch (IOException e) { 262 return null; 263 } 264 } 265 266 /** 267 * Returns the original search path of URLs. 268 */ 269 public URL[] getURLs() { 270 synchronized (unopenedUrls) { 271 return path.toArray(new URL[0]); 272 } 273 } 274 275 /** 276 * Finds the resource with the specified name on the URL search path 277 * or null if not found or security check fails. 278 * 279 * @param name the name of the resource 280 * @param check whether to perform a security check 281 * @return a {@code URL} for the resource, or {@code null} 282 * if the resource could not be found. 283 */ 284 public URL findResource(String name, boolean check) { 285 Loader loader; 286 for (int i = 0; (loader = getLoader(i)) != null; i++) { 287 URL url = loader.findResource(name, check); 288 if (url != null) { 289 return url; 290 } 291 } 292 return null; 293 } 294 295 /** 296 * Finds the first Resource on the URL search path which has the specified 297 * name. Returns null if no Resource could be found. 298 * 299 * @param name the name of the Resource 300 * @param check whether to perform a security check 301 * @return the Resource, or null if not found 302 */ 303 public Resource getResource(String name, boolean check) { 304 if (DEBUG) { 305 System.err.println("URLClassPath.getResource(\"" + name + "\")"); 306 } 307 308 Loader loader; 309 for (int i = 0; (loader = getLoader(i)) != null; i++) { 310 Resource res = loader.getResource(name, check); 311 if (res != null) { 312 return res; 313 } 314 } 315 return null; 316 } 317 318 /** 319 * Finds all resources on the URL search path with the given name. 320 * Returns an enumeration of the URL objects. 321 * 322 * @param name the resource name 323 * @return an Enumeration of all the urls having the specified name 324 */ 325 public Enumeration<URL> findResources(final String name, 326 final boolean check) { 327 return new Enumeration<>() { 328 private int index = 0; 329 private URL url = null; 330 331 private boolean next() { 332 if (url != null) { 333 return true; 334 } else { 335 Loader loader; 336 while ((loader = getLoader(index++)) != null) { 337 url = loader.findResource(name, check); 338 if (url != null) { 339 return true; 340 } 341 } 342 return false; 343 } 344 } 345 346 public boolean hasMoreElements() { 347 return next(); 348 } 349 350 public URL nextElement() { 351 if (!next()) { 352 throw new NoSuchElementException(); 353 } 354 URL u = url; 355 url = null; 356 return u; 357 } 358 }; 359 } 360 361 public Resource getResource(String name) { 362 return getResource(name, true); 363 } 364 365 /** 366 * Finds all resources on the URL search path with the given name. 367 * Returns an enumeration of the Resource objects. 368 * 369 * @param name the resource name 370 * @return an Enumeration of all the resources having the specified name 371 */ 372 public Enumeration<Resource> getResources(final String name, 373 final boolean check) { 374 return new Enumeration<>() { 375 private int index = 0; 376 private Resource res = null; 377 378 private boolean next() { 379 if (res != null) { 380 return true; 381 } else { 382 Loader loader; 383 while ((loader = getLoader(index++)) != null) { 384 res = loader.getResource(name, check); 385 if (res != null) { 386 return true; 387 } 388 } 389 return false; 390 } 391 } 392 393 public boolean hasMoreElements() { 394 return next(); 395 } 396 397 public Resource nextElement() { 398 if (!next()) { 399 throw new NoSuchElementException(); 400 } 401 Resource r = res; 402 res = null; 403 return r; 404 } 405 }; 406 } 407 408 public Enumeration<Resource> getResources(final String name) { 409 return getResources(name, true); 410 } 411 412 /* 413 * Returns the Loader at the specified position in the URL search 414 * path. The URLs are opened and expanded as needed. Returns null 415 * if the specified index is out of range. 416 */ 417 private synchronized Loader getLoader(int index) { 418 if (closed) { 419 return null; 420 } 421 // Expand URL search path until the request can be satisfied 422 // or unopenedUrls is exhausted. 423 while (loaders.size() < index + 1) { 424 final URL url; 425 synchronized (unopenedUrls) { 426 url = unopenedUrls.pollFirst(); 427 if (url == null) 428 return null; 429 } 430 // Skip this URL if it already has a Loader. (Loader 431 // may be null in the case where URL has not been opened 432 // but is referenced by a JAR index.) 433 String urlNoFragString = URLUtil.urlNoFragString(url); 434 if (lmap.containsKey(urlNoFragString)) { 435 continue; 436 } 437 // Otherwise, create a new Loader for the URL. 438 Loader loader; 439 try { 440 loader = getLoader(url); 441 // If the loader defines a local class path then add the 442 // URLs as the next URLs to be opened. 443 URL[] urls = loader.getClassPath(); 444 if (urls != null) { 445 push(urls); 446 } 447 } catch (IOException e) { 448 // Silently ignore for now... 449 continue; 450 } catch (SecurityException se) { 451 // Always silently ignore. The context, if there is one, that 452 // this URLClassPath was given during construction will never 453 // have permission to access the URL. 454 if (DEBUG) { 455 System.err.println("Failed to access " + url + ", " + se ); 456 } 457 continue; 458 } 459 // Finally, add the Loader to the search path. 460 loaders.add(loader); 461 lmap.put(urlNoFragString, loader); 462 } 463 return loaders.get(index); 464 } 465 466 /* 467 * Returns the Loader for the specified base URL. 468 */ 469 private Loader getLoader(final URL url) throws IOException { 470 try { 471 return AccessController.doPrivileged( 472 new PrivilegedExceptionAction<>() { 473 public Loader run() throws IOException { 474 String protocol = url.getProtocol(); // lower cased in URL 475 String file = url.getFile(); 476 if (file != null && file.endsWith("/")) { 477 if ("file".equals(protocol)) { 478 return new FileLoader(url); 479 } else if ("jar".equals(protocol) && 480 isDefaultJarHandler(url) && 481 file.endsWith("!/")) { 482 // extract the nested URL 483 URL nestedUrl = new URL(file.substring(0, file.length() - 2)); 484 return new JarLoader(nestedUrl, jarHandler, lmap, acc); 485 } else { 486 return new Loader(url); 487 } 488 } else { 489 return new JarLoader(url, jarHandler, lmap, acc); 490 } 491 } 492 }, acc); 493 } catch (PrivilegedActionException pae) { 494 throw (IOException)pae.getException(); 495 } 496 } 497 498 private static final JavaNetURLAccess JNUA 499 = SharedSecrets.getJavaNetURLAccess(); 500 501 private static boolean isDefaultJarHandler(URL u) { 502 URLStreamHandler h = JNUA.getHandler(u); 503 return h instanceof sun.net.www.protocol.jar.Handler; 504 } 505 506 /* 507 * Pushes the specified URLs onto the head of unopened URLs. 508 */ 509 private void push(URL[] urls) { 510 synchronized (unopenedUrls) { 511 for (int i = urls.length - 1; i >= 0; --i) { 512 unopenedUrls.addFirst(urls[i]); 513 } 514 } 515 } 516 517 /* 518 * Checks whether the resource URL should be returned. 519 * Returns null on security check failure. 520 * Called by java.net.URLClassLoader. 521 */ 522 public static URL checkURL(URL url) { 523 if (url != null) { 524 try { 525 check(url); 526 } catch (Exception e) { 527 return null; 528 } 529 } 530 return url; 531 } 532 533 /* 534 * Checks whether the resource URL should be returned. 535 * Throws exception on failure. 536 * Called internally within this file. 537 */ 538 public static void check(URL url) throws IOException { 539 SecurityManager security = System.getSecurityManager(); 540 if (security != null) { 541 URLConnection urlConnection = url.openConnection(); 542 Permission perm = urlConnection.getPermission(); 543 if (perm != null) { 544 try { 545 security.checkPermission(perm); 546 } catch (SecurityException se) { 547 // fallback to checkRead/checkConnect for pre 1.2 548 // security managers 549 if ((perm instanceof java.io.FilePermission) && 550 perm.getActions().indexOf("read") != -1) { 551 security.checkRead(perm.getName()); 552 } else if ((perm instanceof 553 java.net.SocketPermission) && 554 perm.getActions().indexOf("connect") != -1) { 555 URL locUrl = url; 556 if (urlConnection instanceof JarURLConnection) { 557 locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); 558 } 559 security.checkConnect(locUrl.getHost(), 560 locUrl.getPort()); 561 } else { 562 throw se; 563 } 564 } 565 } 566 } 567 } 568 569 /** 570 * Nested class used to represent a loader of resources and classes 571 * 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(final 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 * Nested class used to represent 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 is 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 its 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 { return jar.getManifest(); }; 867 public Certificate[] getCertificates() 868 { return entry.getCertificates(); }; 869 public CodeSigner[] getCodeSigners() 870 { return entry.getCodeSigners(); }; 871 }; 872 } 873 874 875 /* 876 * Returns true iff at least one resource in the jar file has the same 877 * package name as that of the specified resource name. 878 */ 879 boolean validIndex(final String name) { 880 String packageName = name; 881 int pos; 882 if ((pos = name.lastIndexOf('/')) != -1) { 883 packageName = name.substring(0, pos); 884 } 885 886 String entryName; 887 ZipEntry entry; 888 Enumeration<JarEntry> enum_ = jar.entries(); 889 while (enum_.hasMoreElements()) { 890 entry = enum_.nextElement(); 891 entryName = entry.getName(); 892 if ((pos = entryName.lastIndexOf('/')) != -1) 893 entryName = entryName.substring(0, pos); 894 if (entryName.equals(packageName)) { 895 return true; 896 } 897 } 898 return false; 899 } 900 901 /* 902 * Returns the URL for a resource with the specified name 903 */ 904 @Override 905 URL findResource(final String name, boolean check) { 906 Resource rsc = getResource(name, check); 907 if (rsc != null) { 908 return rsc.getURL(); 909 } 910 return null; 911 } 912 913 /* 914 * Returns the JAR Resource for the specified name. 915 */ 916 @Override 917 Resource getResource(final String name, boolean check) { 918 try { 919 ensureOpen(); 920 } catch (IOException e) { 921 throw new InternalError(e); 922 } 923 final JarEntry entry = jar.getJarEntry(name); 924 if (entry != null) 925 return checkResource(name, check, entry); 926 927 if (index == null) 928 return null; 929 930 HashSet<String> visited = new HashSet<>(); 931 return getResource(name, check, visited); 932 } 933 934 /* 935 * Version of getResource() that tracks the jar files that have been 936 * visited by linking through the index files. This helper method uses 937 * a HashSet to store the URLs of jar files that have been searched and 938 * uses it to avoid going into an infinite loop, looking for a 939 * non-existent resource. 940 */ 941 Resource getResource(final String name, boolean check, 942 Set<String> visited) { 943 Resource res; 944 String[] jarFiles; 945 int count = 0; 946 LinkedList<String> jarFilesList = null; 947 948 /* If there no jar files in the index that can potential contain 949 * this resource then return immediately. 950 */ 951 if ((jarFilesList = index.get(name)) == null) 952 return null; 953 954 do { 955 int size = jarFilesList.size(); 956 jarFiles = jarFilesList.toArray(new String[size]); 957 /* loop through the mapped jar file list */ 958 while (count < size) { 959 String jarName = jarFiles[count++]; 960 JarLoader newLoader; 961 final URL url; 962 963 try{ 964 url = new URL(csu, jarName); 965 String urlNoFragString = URLUtil.urlNoFragString(url); 966 if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { 967 /* no loader has been set up for this jar file 968 * before 969 */ 970 newLoader = AccessController.doPrivileged( 971 new PrivilegedExceptionAction<>() { 972 public JarLoader run() throws IOException { 973 return new JarLoader(url, handler, 974 lmap, acc); 975 } 976 }, acc); 977 978 /* this newly opened jar file has its own index, 979 * merge it into the parent's index, taking into 980 * account the relative path. 981 */ 982 JarIndex newIndex = newLoader.getIndex(); 983 if (newIndex != null) { 984 int pos = jarName.lastIndexOf('/'); 985 newIndex.merge(this.index, (pos == -1 ? 986 null : jarName.substring(0, pos + 1))); 987 } 988 989 /* put it in the global hashtable */ 990 lmap.put(urlNoFragString, newLoader); 991 } 992 } catch (PrivilegedActionException pae) { 993 continue; 994 } catch (MalformedURLException e) { 995 continue; 996 } 997 998 /* Note that the addition of the url to the list of visited 999 * jars incorporates a check for presence in the hashmap 1000 */ 1001 boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); 1002 if (!visitedURL) { 1003 try { 1004 newLoader.ensureOpen(); 1005 } catch (IOException e) { 1006 throw new InternalError(e); 1007 } 1008 final JarEntry entry = newLoader.jar.getJarEntry(name); 1009 if (entry != null) { 1010 return newLoader.checkResource(name, check, entry); 1011 } 1012 1013 /* Verify that at least one other resource with the 1014 * same package name as the lookedup resource is 1015 * present in the new jar 1016 */ 1017 if (!newLoader.validIndex(name)) { 1018 /* the mapping is wrong */ 1019 throw new InvalidJarIndexError("Invalid index"); 1020 } 1021 } 1022 1023 /* If newLoader is the current loader or if it is a 1024 * loader that has already been searched or if the new 1025 * loader does not have an index then skip it 1026 * and move on to the next loader. 1027 */ 1028 if (visitedURL || newLoader == this || 1029 newLoader.getIndex() == null) { 1030 continue; 1031 } 1032 1033 /* Process the index of the new loader 1034 */ 1035 if ((res = newLoader.getResource(name, check, visited)) 1036 != null) { 1037 return res; 1038 } 1039 } 1040 // Get the list of jar files again as the list could have grown 1041 // due to merging of index files. 1042 jarFilesList = index.get(name); 1043 1044 // If the count is unchanged, we are done. 1045 } while (count < jarFilesList.size()); 1046 return null; 1047 } 1048 1049 1050 /* 1051 * Returns the JAR file local class path, or null if none. 1052 */ 1053 @Override 1054 URL[] getClassPath() throws IOException { 1055 if (index != null) { 1056 return null; 1057 } 1058 1059 ensureOpen(); 1060 1061 // Only get manifest when necessary 1062 if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { 1063 Manifest man = jar.getManifest(); 1064 if (man != null) { 1065 Attributes attr = man.getMainAttributes(); 1066 if (attr != null) { 1067 String value = attr.getValue(Name.CLASS_PATH); 1068 if (value != null) { 1069 return parseClassPath(csu, value); 1070 } 1071 } 1072 } 1073 } 1074 return null; 1075 } 1076 1077 /* 1078 * Parses value of the Class-Path manifest attribute and returns 1079 * an array of URLs relative to the specified base URL. 1080 */ 1081 private static URL[] parseClassPath(URL base, String value) 1082 throws MalformedURLException 1083 { 1084 StringTokenizer st = new StringTokenizer(value); 1085 URL[] urls = new URL[st.countTokens()]; 1086 int i = 0; 1087 while (st.hasMoreTokens()) { 1088 String path = st.nextToken(); 1089 urls[i] = new URL(base, path); 1090 i++; 1091 } 1092 return urls; 1093 } 1094 } 1095 1096 /* 1097 * Nested class used to represent a loader of classes and resources 1098 * from a file URL that refers to a directory. 1099 */ 1100 private static class FileLoader extends Loader { 1101 /* Canonicalized File */ 1102 private File dir; 1103 1104 FileLoader(URL url) throws IOException { 1105 super(url); 1106 if (!"file".equals(url.getProtocol())) { 1107 throw new IllegalArgumentException("url"); 1108 } 1109 String path = url.getFile().replace('/', File.separatorChar); 1110 path = ParseUtil.decode(path); 1111 dir = (new File(path)).getCanonicalFile(); 1112 } 1113 1114 /* 1115 * Returns the URL for a resource with the specified name 1116 */ 1117 @Override 1118 URL findResource(final String name, boolean check) { 1119 Resource rsc = getResource(name, check); 1120 if (rsc != null) { 1121 return rsc.getURL(); 1122 } 1123 return null; 1124 } 1125 1126 @Override 1127 Resource getResource(final String name, boolean check) { 1128 final URL url; 1129 try { 1130 URL normalizedBase = new URL(getBaseURL(), "."); 1131 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); 1132 1133 if (url.getFile().startsWith(normalizedBase.getFile()) == false) { 1134 // requested resource had ../..'s in path 1135 return null; 1136 } 1137 1138 if (check) 1139 URLClassPath.check(url); 1140 1141 final File file; 1142 if (name.indexOf("..") != -1) { 1143 file = (new File(dir, name.replace('/', File.separatorChar))) 1144 .getCanonicalFile(); 1145 if ( !((file.getPath()).startsWith(dir.getPath())) ) { 1146 /* outside of base dir */ 1147 return null; 1148 } 1149 } else { 1150 file = new File(dir, name.replace('/', File.separatorChar)); 1151 } 1152 1153 if (file.exists()) { 1154 return new Resource() { 1155 public String getName() { return name; }; 1156 public URL getURL() { return url; }; 1157 public URL getCodeSourceURL() { return getBaseURL(); }; 1158 public InputStream getInputStream() throws IOException 1159 { return new FileInputStream(file); }; 1160 public int getContentLength() throws IOException 1161 { return (int)file.length(); }; 1162 }; 1163 } 1164 } catch (Exception e) { 1165 return null; 1166 } 1167 return null; 1168 } 1169 } 1170 }