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