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