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