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                     /* JarURLConnection.getInputStream() returns a separate
 536                      * instance on each call. So we have to close this here.
 537                      * The jar file cache will keep the file open.
 538                      * Also, need to remember the jar file so it can be closed
 539                      * in a hurry.
 540                      */
 541                     JarURLConnection juc = (JarURLConnection)uc;
 542                     jarfile = juc.getJarFile();
 543                     in.close();
 544                 }
 545             } catch (Exception e) {
 546                 return null;
 547             }
 548             return new Resource() {
 549                 public String getName() { return name; }
 550                 public URL getURL() { return url; }
 551                 public URL getCodeSourceURL() { return base; }
 552                 public InputStream getInputStream() throws IOException {
 553                     return uc.getInputStream();
 554                 }
 555                 public int getContentLength() throws IOException {
 556                     return uc.getContentLength();
 557                 }
 558             };
 559         }
 560 
 561         /*
 562          * Returns the Resource for the specified name, or null if not
 563          * found or the caller does not have the permission to get the
 564          * resource.
 565          */
 566         Resource getResource(final String name) {
 567             return getResource(name, true);
 568         }
 569 
 570         /*
 571          * close this loader and release all resources
 572          * method overridden in sub-classes
 573          */
 574         public void close () throws IOException {
 575             if (jarfile != null) {
 576                 jarfile.close();
 577             }
 578         }
 579 
 580         /*
 581          * Returns the local class path for this loader, or null if none.
 582          */
 583         URL[] getClassPath() throws IOException {
 584             return null;
 585         }
 586     }
 587 
 588     /*
 589      * Inner class used to represent a Loader of resources from a JAR URL.
 590      */
 591     static class JarLoader extends Loader {
 592         private JarFile jar;
 593         private URL csu;
 594         private JarIndex index;
 595         private MetaIndex metaIndex;
 596         private URLStreamHandler handler;
 597         private HashMap<String, Loader> lmap;
 598         private boolean closed = false;
 599 
 600         /*
 601          * Creates a new JarLoader for the specified URL referring to
 602          * a JAR file.
 603          */
 604         JarLoader(URL url, URLStreamHandler jarHandler,
 605                   HashMap<String, Loader> loaderMap)
 606             throws IOException
 607         {
 608             super(new URL("jar", "", -1, url + "!/", jarHandler));
 609             csu = url;
 610             handler = jarHandler;
 611             lmap = loaderMap;
 612 
 613             if (!isOptimizable(url)) {
 614                 ensureOpen();
 615             } else {
 616                  String fileName = url.getFile();
 617                 if (fileName != null) {
 618                     fileName = ParseUtil.decode(fileName);
 619                     File f = new File(fileName);
 620                     metaIndex = MetaIndex.forJar(f);
 621                     // If the meta index is found but the file is not
 622                     // installed, set metaIndex to null. A typical
 623                     // senario is charsets.jar which won't be installed
 624                     // when the user is running in certain locale environment.
 625                     // The side effect of null metaIndex will cause
 626                     // ensureOpen get called so that IOException is thrown.
 627                     if (metaIndex != null && !f.exists()) {
 628                         metaIndex = null;
 629                     }
 630                 }
 631 
 632                 // metaIndex is null when either there is no such jar file
 633                 // entry recorded in meta-index file or such jar file is
 634                 // missing in JRE. See bug 6340399.
 635                 if (metaIndex == null) {
 636                     ensureOpen();
 637                 }
 638             }
 639         }
 640 
 641         @Override
 642         public void close () throws IOException {
 643             // closing is synchronized at higher level
 644             if (!closed) {
 645                 closed = true;
 646                 // in case not already open.
 647                 ensureOpen();
 648                 jar.close();
 649             }
 650         }
 651 
 652         JarFile getJarFile () {
 653             return jar;
 654         }
 655 
 656         private boolean isOptimizable(URL url) {
 657             return "file".equals(url.getProtocol());
 658         }
 659 
 660         private void ensureOpen() throws IOException {
 661             if (jar == null) {
 662                 try {
 663                     java.security.AccessController.doPrivileged(
 664                         new java.security.PrivilegedExceptionAction<Void>() {
 665                             public Void run() throws IOException {
 666                                 if (DEBUG) {
 667                                     System.err.println("Opening " + csu);
 668                                     Thread.dumpStack();
 669                                 }
 670 
 671                                 jar = getJarFile(csu);
 672                                 index = JarIndex.getJarIndex(jar, metaIndex);
 673                                 if (index != null) {
 674                                     String[] jarfiles = index.getJarFiles();
 675                                 // Add all the dependent URLs to the lmap so that loaders
 676                                 // will not be created for them by URLClassPath.getLoader(int)
 677                                 // if the same URL occurs later on the main class path.  We set
 678                                 // Loader to null here to avoid creating a Loader for each
 679                                 // URL until we actually need to try to load something from them.
 680                                     for(int i = 0; i < jarfiles.length; i++) {
 681                                         try {
 682                                             URL jarURL = new URL(csu, jarfiles[i]);
 683                                             // If a non-null loader already exists, leave it alone.
 684                                             String urlNoFragString = URLUtil.urlNoFragString(jarURL);
 685                                             if (!lmap.containsKey(urlNoFragString)) {
 686                                                 lmap.put(urlNoFragString, null);
 687                                             }
 688                                         } catch (MalformedURLException e) {
 689                                             continue;
 690                                         }
 691                                     }
 692                                 }
 693                                 return null;
 694                             }
 695                         }
 696                     );
 697                 } catch (java.security.PrivilegedActionException pae) {
 698                     throw (IOException)pae.getException();
 699                 }
 700             }
 701         }
 702 
 703         private JarFile getJarFile(URL url) throws IOException {
 704             // Optimize case where url refers to a local jar file
 705             if (isOptimizable(url)) {
 706                 FileURLMapper p = new FileURLMapper (url);
 707                 if (!p.exists()) {
 708                     throw new FileNotFoundException(p.getPath());
 709                 }
 710                 return new JarFile (p.getPath());
 711             }
 712             URLConnection uc = getBaseURL().openConnection();
 713             uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION);
 714             return ((JarURLConnection)uc).getJarFile();
 715         }
 716 
 717         /*
 718          * Returns the index of this JarLoader if it exists.
 719          */
 720         JarIndex getIndex() {
 721             try {
 722                 ensureOpen();
 723             } catch (IOException e) {
 724                 throw (InternalError) new InternalError().initCause(e);
 725             }
 726             return index;
 727         }
 728 
 729         /*
 730          * Creates the resource and if the check flag is set to true, checks if
 731          * is its okay to return the resource.
 732          */
 733         Resource checkResource(final String name, boolean check,
 734             final JarEntry entry) {
 735 
 736             final URL url;
 737             try {
 738                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
 739                 if (check) {
 740                     URLClassPath.check(url);
 741                 }
 742             } catch (MalformedURLException e) {
 743                 return null;
 744                 // throw new IllegalArgumentException("name");
 745             } catch (IOException e) {
 746                 return null;
 747             } catch (AccessControlException e) {
 748                 return null;
 749             }
 750 
 751             return new Resource() {
 752                 public String getName() { return name; }
 753                 public URL getURL() { return url; }
 754                 public URL getCodeSourceURL() { return csu; }
 755                 public InputStream getInputStream() throws IOException
 756                     { return jar.getInputStream(entry); }
 757                 public int getContentLength()
 758                     { return (int)entry.getSize(); }
 759                 public Manifest getManifest() throws IOException
 760                     { return jar.getManifest(); };
 761                 public Certificate[] getCertificates()
 762                     { return entry.getCertificates(); };
 763                 public CodeSigner[] getCodeSigners()
 764                     { return entry.getCodeSigners(); };
 765             };
 766         }
 767 
 768 
 769         /*
 770          * Returns true iff atleast one resource in the jar file has the same
 771          * package name as that of the specified resource name.
 772          */
 773         boolean validIndex(final String name) {
 774             String packageName = name;
 775             int pos;
 776             if((pos = name.lastIndexOf("/")) != -1) {
 777                 packageName = name.substring(0, pos);
 778             }
 779 
 780             String entryName;
 781             ZipEntry entry;
 782             Enumeration<JarEntry> enum_ = jar.entries();
 783             while (enum_.hasMoreElements()) {
 784                 entry = enum_.nextElement();
 785                 entryName = entry.getName();
 786                 if((pos = entryName.lastIndexOf("/")) != -1)
 787                     entryName = entryName.substring(0, pos);
 788                 if (entryName.equals(packageName)) {
 789                     return true;
 790                 }
 791             }
 792             return false;
 793         }
 794 
 795         /*
 796          * Returns the URL for a resource with the specified name
 797          */
 798         URL findResource(final String name, boolean check) {
 799             Resource rsc = getResource(name, check);
 800             if (rsc != null) {
 801                 return rsc.getURL();
 802             }
 803             return null;
 804         }
 805 
 806         /*
 807          * Returns the JAR Resource for the specified name.
 808          */
 809         Resource getResource(final String name, boolean check) {
 810             if (metaIndex != null) {
 811                 if (!metaIndex.mayContain(name)) {
 812                     return null;
 813                 }
 814             }
 815 
 816             try {
 817                 ensureOpen();
 818             } catch (IOException e) {
 819                 throw (InternalError) new InternalError().initCause(e);
 820             }
 821             final JarEntry entry = jar.getJarEntry(name);
 822             if (entry != null)
 823                 return checkResource(name, check, entry);
 824 
 825             if (index == null)
 826                 return null;
 827 
 828             HashSet<String> visited = new HashSet<String>();
 829             return getResource(name, check, visited);
 830         }
 831 
 832         /*
 833          * Version of getResource() that tracks the jar files that have been
 834          * visited by linking through the index files. This helper method uses
 835          * a HashSet to store the URLs of jar files that have been searched and
 836          * uses it to avoid going into an infinite loop, looking for a
 837          * non-existent resource
 838          */
 839         Resource getResource(final String name, boolean check,
 840                              Set<String> visited) {
 841 
 842             Resource res;
 843             Object[] jarFiles;
 844             boolean done = false;
 845             int count = 0;
 846             LinkedList jarFilesList = null;
 847 
 848             /* If there no jar files in the index that can potential contain
 849              * this resource then return immediately.
 850              */
 851             if((jarFilesList = index.get(name)) == null)
 852                 return null;
 853 
 854             do {
 855                 jarFiles = jarFilesList.toArray();
 856                 int size = jarFilesList.size();
 857                 /* loop through the mapped jar file list */
 858                 while(count < size) {
 859                     String jarName = (String)jarFiles[count++];
 860                     JarLoader newLoader;
 861                     final URL url;
 862 
 863                     try{
 864                         url = new URL(csu, jarName);
 865                         String urlNoFragString = URLUtil.urlNoFragString(url);
 866                         if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) {
 867                             /* no loader has been set up for this jar file
 868                              * before
 869                              */
 870                             newLoader = AccessController.doPrivileged(
 871                                 new PrivilegedExceptionAction<JarLoader>() {
 872                                     public JarLoader run() throws IOException {
 873                                         return new JarLoader(url, handler,
 874                                             lmap);
 875                                     }
 876                                 });
 877 
 878                             /* this newly opened jar file has its own index,
 879                              * merge it into the parent's index, taking into
 880                              * account the relative path.
 881                              */
 882                             JarIndex newIndex = newLoader.getIndex();
 883                             if(newIndex != null) {
 884                                 int pos = jarName.lastIndexOf("/");
 885                                 newIndex.merge(this.index, (pos == -1 ?
 886                                     null : jarName.substring(0, pos + 1)));
 887                             }
 888 
 889                             /* put it in the global hashtable */
 890                             lmap.put(urlNoFragString, newLoader);
 891                         }
 892                     } catch (java.security.PrivilegedActionException pae) {
 893                         continue;
 894                     } catch (MalformedURLException e) {
 895                         continue;
 896                     }
 897 
 898 
 899                     /* Note that the addition of the url to the list of visited
 900                      * jars incorporates a check for presence in the hashmap
 901                      */
 902                     boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url));
 903                     if (!visitedURL) {
 904                         try {
 905                             newLoader.ensureOpen();
 906                         } catch (IOException e) {
 907                             throw (InternalError) new InternalError().initCause(e);
 908                         }
 909                         final JarEntry entry = newLoader.jar.getJarEntry(name);
 910                         if (entry != null) {
 911                             return newLoader.checkResource(name, check, entry);
 912                         }
 913 
 914                         /* Verify that at least one other resource with the
 915                          * same package name as the lookedup resource is
 916                          * present in the new jar
 917                          */
 918                         if (!newLoader.validIndex(name)) {
 919                             /* the mapping is wrong */
 920                             throw new InvalidJarIndexException("Invalid index");
 921                         }
 922                     }
 923 
 924                     /* If newLoader is the current loader or if it is a
 925                      * loader that has already been searched or if the new
 926                      * loader does not have an index then skip it
 927                      * and move on to the next loader.
 928                      */
 929                     if (visitedURL || newLoader == this ||
 930                             newLoader.getIndex() == null) {
 931                         continue;
 932                     }
 933 
 934                     /* Process the index of the new loader
 935                      */
 936                     if((res = newLoader.getResource(name, check, visited))
 937                             != null) {
 938                         return res;
 939                     }
 940                 }
 941                 // Get the list of jar files again as the list could have grown
 942                 // due to merging of index files.
 943                 jarFilesList = index.get(name);
 944 
 945             // If the count is unchanged, we are done.
 946             } while(count < jarFilesList.size());
 947             return null;
 948         }
 949 
 950 
 951         /*
 952          * Returns the JAR file local class path, or null if none.
 953          */
 954         URL[] getClassPath() throws IOException {
 955             if (index != null) {
 956                 return null;
 957             }
 958 
 959             if (metaIndex != null) {
 960                 return null;
 961             }
 962 
 963             ensureOpen();
 964             parseExtensionsDependencies();
 965             if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary
 966                 Manifest man = jar.getManifest();
 967                 if (man != null) {
 968                     Attributes attr = man.getMainAttributes();
 969                     if (attr != null) {
 970                         String value = attr.getValue(Name.CLASS_PATH);
 971                         if (value != null) {
 972                             return parseClassPath(csu, value);
 973                         }
 974                     }
 975                 }
 976             }
 977             return null;
 978         }
 979 
 980         /*
 981          * parse the standard extension dependencies
 982          */
 983         private void  parseExtensionsDependencies() throws IOException {
 984             ExtensionDependency.checkExtensionsDependencies(jar);
 985         }
 986 
 987         /*
 988          * Parses value of the Class-Path manifest attribute and returns
 989          * an array of URLs relative to the specified base URL.
 990          */
 991         private URL[] parseClassPath(URL base, String value)
 992             throws MalformedURLException
 993         {
 994             StringTokenizer st = new StringTokenizer(value);
 995             URL[] urls = new URL[st.countTokens()];
 996             int i = 0;
 997             while (st.hasMoreTokens()) {
 998                 String path = st.nextToken();
 999                 urls[i] = new URL(base, path);
1000                 i++;
1001             }
1002             return urls;
1003         }
1004     }
1005 
1006     /*
1007      * Inner class used to represent a loader of classes and resources
1008      * from a file URL that refers to a directory.
1009      */
1010     private static class FileLoader extends Loader {
1011         /* Canonicalized File */
1012         private File dir;
1013 
1014         FileLoader(URL url) throws IOException {
1015             super(url);
1016             if (!"file".equals(url.getProtocol())) {
1017                 throw new IllegalArgumentException("url");
1018             }
1019             String path = url.getFile().replace('/', File.separatorChar);
1020             path = ParseUtil.decode(path);
1021             dir = (new File(path)).getCanonicalFile();
1022         }
1023 
1024         /*
1025          * Returns the URL for a resource with the specified name
1026          */
1027         URL findResource(final String name, boolean check) {
1028             Resource rsc = getResource(name, check);
1029             if (rsc != null) {
1030                 return rsc.getURL();
1031             }
1032             return null;
1033         }
1034 
1035         Resource getResource(final String name, boolean check) {
1036             final URL url;
1037             try {
1038                 URL normalizedBase = new URL(getBaseURL(), ".");
1039                 url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
1040 
1041                 if (url.getFile().startsWith(normalizedBase.getFile()) == false) {
1042                     // requested resource had ../..'s in path
1043                     return null;
1044                 }
1045 
1046                 if (check)
1047                     URLClassPath.check(url);
1048 
1049                 final File file;
1050                 if (name.indexOf("..") != -1) {
1051                     file = (new File(dir, name.replace('/', File.separatorChar)))
1052                           .getCanonicalFile();
1053                     if ( !((file.getPath()).startsWith(dir.getPath())) ) {
1054                         /* outside of base dir */
1055                         return null;
1056                     }
1057                 } else {
1058                     file = new File(dir, name.replace('/', File.separatorChar));
1059                 }
1060 
1061                 if (file.exists()) {
1062                     return new Resource() {
1063                         public String getName() { return name; };
1064                         public URL getURL() { return url; };
1065                         public URL getCodeSourceURL() { return getBaseURL(); };
1066                         public InputStream getInputStream() throws IOException
1067                             { return new FileInputStream(file); };
1068                         public int getContentLength() throws IOException
1069                             { return (int)file.length(); };
1070                     };
1071                 }
1072             } catch (Exception e) {
1073                 return null;
1074             }
1075             return null;
1076         }
1077     }
1078 }