|
14 | 14 | import io.netty.util.internal.PlatformDependent; |
15 | 15 | import io.vertx.core.VertxException; |
16 | 16 | import io.vertx.core.file.FileSystemOptions; |
| 17 | +import io.vertx.core.impl.Utils; |
17 | 18 | import io.vertx.core.spi.file.FileResolver; |
18 | 19 |
|
19 | | -import java.io.Closeable; |
20 | 20 | import java.io.File; |
21 | 21 | import java.io.IOException; |
22 | 22 | import java.io.InputStream; |
| 23 | +import java.net.JarURLConnection; |
23 | 24 | import java.net.URL; |
| 25 | +import java.nio.file.FileVisitResult; |
| 26 | +import java.nio.file.Files; |
| 27 | +import java.nio.file.Path; |
| 28 | +import java.nio.file.SimpleFileVisitor; |
| 29 | +import java.nio.file.attribute.BasicFileAttributes; |
| 30 | +import java.util.ArrayList; |
24 | 31 | import java.util.Enumeration; |
| 32 | +import java.util.List; |
25 | 33 | import java.util.zip.ZipEntry; |
26 | 34 | import java.util.zip.ZipFile; |
27 | 35 |
|
@@ -275,76 +283,107 @@ private File unpackFromFileURL(URL url, String fileName, ClassLoader cl) { |
275 | 283 | return cacheFile; |
276 | 284 | } |
277 | 285 |
|
278 | | - private File unpackFromJarURL(URL url, String fileName, ClassLoader cl) { |
279 | | - ZipFile zip = null; |
280 | | - try { |
281 | | - String path = url.getPath(); |
282 | | - int idx1 = -1, idx2 = -1; |
283 | | - for (int i = path.length() - 1; i > 4; ) { |
284 | | - if (path.charAt(i) == '!' && (path.startsWith(".jar", i - 4) || path.startsWith(".zip", i - 4) || path.startsWith(".war", i - 4))) { |
285 | | - if (idx1 == -1) { |
286 | | - idx1 = i; |
287 | | - i -= 4; |
288 | | - continue; |
289 | | - } else { |
290 | | - idx2 = i; |
291 | | - break; |
292 | | - } |
293 | | - } |
294 | | - i--; |
295 | | - } |
296 | | - if (idx2 == -1) { |
297 | | - File file = new File(decodeURIComponent(path.substring(5, idx1), false)); |
298 | | - zip = new ZipFile(file); |
| 286 | + /** |
| 287 | + * Parse the list of entries of a URL assuming the URL is a jar URL. |
| 288 | + * |
| 289 | + * <ul> |
| 290 | + * <li>when the URL is a nested file within the archive, the list is the jar entry</li> |
| 291 | + * <li>when the URL is a nested file within a nested file within the archive, the list is jar entry followed by the jar entry of the nested archive</li> |
| 292 | + * <li>and so on.</li> |
| 293 | + * </ul> |
| 294 | + * |
| 295 | + * @param url the URL |
| 296 | + * @return the list of entries |
| 297 | + */ |
| 298 | + private List<String> listOfEntries(URL url) { |
| 299 | + String path = url.getPath(); |
| 300 | + List<String> list = new ArrayList<>(); |
| 301 | + int last = path.length(); |
| 302 | + for (int i = path.length() - 2; i >= 0;) { |
| 303 | + if (path.charAt(i) == '!' && path.charAt(i + 1) == '/') { |
| 304 | + list.add(path.substring(2 + i, last)); |
| 305 | + last = i; |
| 306 | + i -= 2; |
299 | 307 | } else { |
300 | | - String s = path.substring(idx2 + 2, idx1); |
301 | | - File file = resolveFile(s); |
302 | | - zip = new ZipFile(file); |
| 308 | + i--; |
303 | 309 | } |
| 310 | + } |
| 311 | + return list; |
| 312 | + } |
304 | 313 |
|
305 | | - String inJarPath = path.substring(idx1 + 2); |
306 | | - StringBuilder prefixBuilder = new StringBuilder(); |
307 | | - int first = 0; |
308 | | - int second; |
309 | | - int len = JAR_URL_SEP.length(); |
310 | | - while ((second = inJarPath.indexOf(JAR_URL_SEP, first)) >= 0) { |
311 | | - prefixBuilder.append(inJarPath, first, second).append("/"); |
312 | | - first = second + len; |
313 | | - } |
314 | | - String prefix = prefixBuilder.toString(); |
315 | | - Enumeration<? extends ZipEntry> entries = zip.entries(); |
316 | | - String prefixCheck = prefix.isEmpty() ? fileName : prefix + fileName; |
317 | | - while (entries.hasMoreElements()) { |
318 | | - ZipEntry entry = entries.nextElement(); |
319 | | - String name = entry.getName(); |
320 | | - if (name.startsWith(prefixCheck)) { |
321 | | - String p = prefix.isEmpty() ? name : name.substring(prefix.length()); |
322 | | - if (name.endsWith("/")) { |
323 | | - // Directory |
324 | | - cache.cacheDir(p); |
325 | | - } else { |
326 | | - try (InputStream is = zip.getInputStream(entry)) { |
327 | | - cache.cacheFile(p, is, !enableCaching); |
| 314 | + private File unpackFromJarURL(URL url, String fileName, ClassLoader cl) { |
| 315 | + try { |
| 316 | + List<String> listOfEntries = listOfEntries(url); |
| 317 | + switch (listOfEntries.size()) { |
| 318 | + case 1: |
| 319 | + JarURLConnection conn = (JarURLConnection) url.openConnection(); |
| 320 | + try (ZipFile zip = conn.getJarFile()) { |
| 321 | + extractFilesFromJarFile(zip, fileName); |
| 322 | + } |
| 323 | + break; |
| 324 | + case 2: |
| 325 | + URL nestedURL = cl.getResource(listOfEntries.get(1)); |
| 326 | + if (nestedURL != null && nestedURL.getProtocol().equals("jar")) { |
| 327 | + File root = unpackFromJarURL(nestedURL, listOfEntries.get(1), cl); |
| 328 | + if (root.isDirectory()) { |
| 329 | + // jar:file:/path/to/nesting.jar!/xxx-inf/classes |
| 330 | + // we need to unpack xxx-inf/classes and then copy the content as is in the cache |
| 331 | + Path path = root.toPath(); |
| 332 | + Files.walkFileTree(path, new SimpleFileVisitor<Path>() { |
| 333 | + @Override |
| 334 | + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { |
| 335 | + Path relative = path.relativize(dir); |
| 336 | + cache.cacheDir(relative.toString()); |
| 337 | + return FileVisitResult.CONTINUE; |
| 338 | + } |
| 339 | + @Override |
| 340 | + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { |
| 341 | + Path relative = path.relativize(file); |
| 342 | + cache.cacheFile(relative.toString(), file.toFile(), false); |
| 343 | + return FileVisitResult.CONTINUE; |
| 344 | + } |
| 345 | + }); |
| 346 | + } else { |
| 347 | + // jar:file:/path/to/nesting.jar!/path/to/nested.jar |
| 348 | + try (ZipFile zip = new ZipFile(root)) { |
| 349 | + extractFilesFromJarFile(zip, fileName); |
| 350 | + } |
328 | 351 | } |
| 352 | + } else { |
| 353 | + throw new VertxException("Unexpected nested url : " + nestedURL); |
329 | 354 | } |
330 | | - |
331 | | - } |
| 355 | + break; |
| 356 | + default: |
| 357 | + throw new VertxException("Nesting more than two levels is not supported"); |
332 | 358 | } |
333 | 359 | } catch (IOException e) { |
334 | 360 | throw new VertxException(FileSystemImpl.getFileAccessErrorMessage("unpack", url.toString()), e); |
335 | | - } finally { |
336 | | - closeQuietly(zip); |
337 | 361 | } |
338 | | - |
339 | 362 | return cache.getFile(fileName); |
340 | 363 | } |
341 | 364 |
|
342 | | - private void closeQuietly(Closeable zip) { |
343 | | - if (zip != null) { |
344 | | - try { |
345 | | - zip.close(); |
346 | | - } catch (IOException e) { |
347 | | - // Ignored. |
| 365 | + /** |
| 366 | + * Extract a subset of the entries to the cache. |
| 367 | + */ |
| 368 | + private void extractFilesFromJarFile(ZipFile zip, String entryFilter) throws IOException { |
| 369 | + Enumeration<? extends ZipEntry> entries = zip.entries(); |
| 370 | + while (entries.hasMoreElements()) { |
| 371 | + ZipEntry entry = entries.nextElement(); |
| 372 | + String name = entry.getName(); |
| 373 | + int len = name.length(); |
| 374 | + if (len == 0) { |
| 375 | + return; |
| 376 | + } |
| 377 | + if (name.charAt(len - 1) != ' ' || !Utils.isWindows()) { |
| 378 | + if (name.startsWith(entryFilter)) { |
| 379 | + if (name.charAt(len - 1) == '/') { |
| 380 | + cache.cacheDir(name); |
| 381 | + } else { |
| 382 | + try (InputStream is = zip.getInputStream(entry)) { |
| 383 | + cache.cacheFile(name, is, !enableCaching); |
| 384 | + } |
| 385 | + } |
| 386 | + } |
348 | 387 | } |
349 | 388 | } |
350 | 389 | } |
|
0 commit comments