Showing the changes to java/pkg/sun/misc/URLClassPath.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,1074 @@ | |||
| 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 (InternalError) new InternalError().initCause(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 (InternalError) new InternalError().initCause(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 | Object[] jarFiles; | ||
| 840 | boolean done = false; | ||
| 841 | int count = 0; | ||
| 842 | LinkedList jarFilesList = null; | ||
| 843 | |||
| 844 | /* If there no jar files in the index that can potential contain | ||
| 845 | * this resource then return immediately. | ||
| 846 | */ | ||
| 847 | if((jarFilesList = index.get(name)) == null) | ||
| 848 | return null; | ||
| 849 | |||
| 850 | do { | ||
| 851 | jarFiles = jarFilesList.toArray(); | ||
| 852 | int size = jarFilesList.size(); | ||
| 853 | /* loop through the mapped jar file list */ | ||
| 854 | while(count < size) { | ||
| 855 | String jarName = (String)jarFiles[count++]; | ||
| 856 | JarLoader newLoader; | ||
| 857 | final URL url; | ||
| 858 | |||
| 859 | try{ | ||
| 860 | url = new URL(csu, jarName); | ||
| 861 | String urlNoFragString = URLUtil.urlNoFragString(url); | ||
| 862 | if ((newLoader = (JarLoader)lmap.get(urlNoFragString)) == null) { | ||
| 863 | /* no loader has been set up for this jar file | ||
| 864 | * before | ||
| 865 | */ | ||
| 866 | newLoader = AccessController.doPrivileged( | ||
| 867 | new PrivilegedExceptionAction<JarLoader>() { | ||
| 868 | public JarLoader run() throws IOException { | ||
| 869 | return new JarLoader(url, handler, | ||
| 870 | lmap); | ||
| 871 | } | ||
| 872 | }); | ||
| 873 | |||
| 874 | /* this newly opened jar file has its own index, | ||
| 875 | * merge it into the parent's index, taking into | ||
| 876 | * account the relative path. | ||
| 877 | */ | ||
| 878 | JarIndex newIndex = newLoader.getIndex(); | ||
| 879 | if(newIndex != null) { | ||
| 880 | int pos = jarName.lastIndexOf("/"); | ||
| 881 | newIndex.merge(this.index, (pos == -1 ? | ||
| 882 | null : jarName.substring(0, pos + 1))); | ||
| 883 | } | ||
| 884 | |||
| 885 | /* put it in the global hashtable */ | ||
| 886 | lmap.put(urlNoFragString, newLoader); | ||
| 887 | } | ||
| 888 | } catch (java.security.PrivilegedActionException pae) { | ||
| 889 | continue; | ||
| 890 | } catch (MalformedURLException e) { | ||
| 891 | continue; | ||
| 892 | } | ||
| 893 | |||
| 894 | |||
| 895 | /* Note that the addition of the url to the list of visited | ||
| 896 | * jars incorporates a check for presence in the hashmap | ||
| 897 | */ | ||
| 898 | boolean visitedURL = !visited.add(URLUtil.urlNoFragString(url)); | ||
| 899 | if (!visitedURL) { | ||
| 900 | try { | ||
| 901 | newLoader.ensureOpen(); | ||
| 902 | } catch (IOException e) { | ||
| 903 | throw (InternalError) new InternalError().initCause(e); | ||
| 904 | } | ||
| 905 | final JarEntry entry = newLoader.jar.getJarEntry(name); | ||
| 906 | if (entry != null) { | ||
| 907 | return newLoader.checkResource(name, check, entry); | ||
| 908 | } | ||
| 909 | |||
| 910 | /* Verify that at least one other resource with the | ||
| 911 | * same package name as the lookedup resource is | ||
| 912 | * present in the new jar | ||
| 913 | */ | ||
| 914 | if (!newLoader.validIndex(name)) { | ||
| 915 | /* the mapping is wrong */ | ||
| 916 | throw new InvalidJarIndexException("Invalid index"); | ||
| 917 | } | ||
| 918 | } | ||
| 919 | |||
| 920 | /* If newLoader is the current loader or if it is a | ||
| 921 | * loader that has already been searched or if the new | ||
| 922 | * loader does not have an index then skip it | ||
| 923 | * and move on to the next loader. | ||
| 924 | */ | ||
| 925 | if (visitedURL || newLoader == this || | ||
| 926 | newLoader.getIndex() == null) { | ||
| 927 | continue; | ||
| 928 | } | ||
| 929 | |||
| 930 | /* Process the index of the new loader | ||
| 931 | */ | ||
| 932 | if((res = newLoader.getResource(name, check, visited)) | ||
| 933 | != null) { | ||
| 934 | return res; | ||
| 935 | } | ||
| 936 | } | ||
| 937 | // Get the list of jar files again as the list could have grown | ||
| 938 | // due to merging of index files. | ||
| 939 | jarFilesList = index.get(name); | ||
| 940 | |||
| 941 | // If the count is unchanged, we are done. | ||
| 942 | } while(count < jarFilesList.size()); | ||
| 943 | return null; | ||
| 944 | } | ||
| 945 | |||
| 946 | |||
| 947 | /* | ||
| 948 | * Returns the JAR file local class path, or null if none. | ||
| 949 | */ | ||
| 950 | URL[] getClassPath() throws IOException { | ||
| 951 | if (index != null) { | ||
| 952 | return null; | ||
| 953 | } | ||
| 954 | |||
| 955 | if (metaIndex != null) { | ||
| 956 | return null; | ||
| 957 | } | ||
| 958 | |||
| 959 | ensureOpen(); | ||
| 960 | parseExtensionsDependencies(); | ||
| 961 | if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary | ||
| 962 | Manifest man = jar.getManifest(); | ||
| 963 | if (man != null) { | ||
| 964 | Attributes attr = man.getMainAttributes(); | ||
| 965 | if (attr != null) { | ||
| 966 | String value = attr.getValue(Name.CLASS_PATH); | ||
| 967 | if (value != null) { | ||
| 968 | return parseClassPath(csu, value); | ||
| 969 | } | ||
| 970 | } | ||
| 971 | } | ||
| 972 | } | ||
| 973 | return null; | ||
| 974 | } | ||
| 975 | |||
| 976 | /* | ||
| 977 | * parse the standard extension dependencies | ||
| 978 | */ | ||
| 979 | private void parseExtensionsDependencies() throws IOException { | ||
| 980 | ExtensionDependency.checkExtensionsDependencies(jar); | ||
| 981 | } | ||
| 982 | |||
| 983 | /* | ||
| 984 | * Parses value of the Class-Path manifest attribute and returns | ||
| 985 | * an array of URLs relative to the specified base URL. | ||
| 986 | */ | ||
| 987 | private URL[] parseClassPath(URL base, String value) | ||
| 988 | throws MalformedURLException | ||
| 989 | { | ||
| 990 | StringTokenizer st = new StringTokenizer(value); | ||
| 991 | URL[] urls = new URL[st.countTokens()]; | ||
| 992 | int i = 0; | ||
| 993 | while (st.hasMoreTokens()) { | ||
| 994 | String path = st.nextToken(); | ||
| 995 | urls[i] = new URL(base, path); | ||
| 996 | i++; | ||
| 997 | } | ||
| 998 | return urls; | ||
| 999 | } | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | /* | ||
| 1003 | * Inner class used to represent a loader of classes and resources | ||
| 1004 | * from a file URL that refers to a directory. | ||
| 1005 | */ | ||
| 1006 | private static class FileLoader extends Loader { | ||
| 1007 | /* Canonicalized File */ | ||
| 1008 | private File dir; | ||
| 1009 | |||
| 1010 | FileLoader(URL url) throws IOException { | ||
| 1011 | super(url); | ||
| 1012 | if (!"file".equals(url.getProtocol())) { | ||
| 1013 | throw new IllegalArgumentException("url"); | ||
| 1014 | } | ||
| 1015 | String path = url.getFile().replace('/', File.separatorChar); | ||
| 1016 | path = ParseUtil.decode(path); | ||
| 1017 | dir = (new File(path)).getCanonicalFile(); | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | /* | ||
| 1021 | * Returns the URL for a resource with the specified name | ||
| 1022 | */ | ||
| 1023 | URL findResource(final String name, boolean check) { | ||
| 1024 | Resource rsc = getResource(name, check); | ||
| 1025 | if (rsc != null) { | ||
| 1026 | return rsc.getURL(); | ||
| 1027 | } | ||
| 1028 | return null; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | Resource getResource(final String name, boolean check) { | ||
| 1032 | final URL url; | ||
| 1033 | try { | ||
| 1034 | URL normalizedBase = new URL(getBaseURL(), "."); | ||
| 1035 | url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); | ||
| 1036 | |||
| 1037 | if (url.getFile().startsWith(normalizedBase.getFile()) == false) { | ||
| 1038 | // requested resource had ../..'s in path | ||
| 1039 | return null; | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | if (check) | ||
| 1043 | URLClassPath.check(url); | ||
| 1044 | |||
| 1045 | final File file; | ||
| 1046 | if (name.indexOf("..") != -1) { | ||
| 1047 | file = (new File(dir, name.replace('/', File.separatorChar))) | ||
| 1048 | .getCanonicalFile(); | ||
| 1049 | if ( !((file.getPath()).startsWith(dir.getPath())) ) { | ||
| 1050 | /* outside of base dir */ | ||
| 1051 | return null; | ||
| 1052 | } | ||
| 1053 | } else { | ||
| 1054 | file = new File(dir, name.replace('/', File.separatorChar)); | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | if (file.exists()) { | ||
| 1058 | return new Resource() { | ||
| 1059 | public String getName() { return name; }; | ||
| 1060 | public URL getURL() { return url; }; | ||
| 1061 | public URL getCodeSourceURL() { return getBaseURL(); }; | ||
| 1062 | public InputStream getInputStream() throws IOException | ||
| 1063 | { return new FileInputStream(file); }; | ||
| 1064 | public int getContentLength() throws IOException | ||
| 1065 | { return (int)file.length(); }; | ||
| 1066 | }; | ||
| 1067 | } | ||
| 1068 | } catch (Exception e) { | ||
| 1069 | return null; | ||
| 1070 | } | ||
| 1071 | return null; | ||
| 1072 | } | ||
| 1073 | } | ||
| 1074 | } | ||