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