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