48 import java.security.PrivilegedActionException; |
49 import java.security.PrivilegedActionException; |
49 import java.security.PrivilegedExceptionAction; |
50 import java.security.PrivilegedExceptionAction; |
50 import java.util.*; |
51 import java.util.*; |
51 import java.util.concurrent.locks.ReadWriteLock; |
52 import java.util.concurrent.locks.ReadWriteLock; |
52 import java.util.concurrent.locks.ReentrantReadWriteLock; |
53 import java.util.concurrent.locks.ReentrantReadWriteLock; |
|
54 import java.util.function.Consumer; |
|
55 import java.util.function.Function; |
|
56 import java.util.jar.Attributes; |
|
57 import java.util.jar.Manifest; |
53 import java.util.regex.Pattern; |
58 import java.util.regex.Pattern; |
54 import java.util.zip.CRC32; |
59 import java.util.zip.CRC32; |
55 import java.util.zip.Deflater; |
60 import java.util.zip.Deflater; |
56 import java.util.zip.DeflaterOutputStream; |
61 import java.util.zip.DeflaterOutputStream; |
57 import java.util.zip.Inflater; |
62 import java.util.zip.Inflater; |
83 private static final byte[] ROOTPATH = new byte[] { '/' }; |
88 private static final byte[] ROOTPATH = new byte[] { '/' }; |
84 private static final String PROPERTY_POSIX = "enablePosixFileAttributes"; |
89 private static final String PROPERTY_POSIX = "enablePosixFileAttributes"; |
85 private static final String PROPERTY_DEFAULT_OWNER = "defaultOwner"; |
90 private static final String PROPERTY_DEFAULT_OWNER = "defaultOwner"; |
86 private static final String PROPERTY_DEFAULT_GROUP = "defaultGroup"; |
91 private static final String PROPERTY_DEFAULT_GROUP = "defaultGroup"; |
87 private static final String PROPERTY_DEFAULT_PERMISSIONS = "defaultPermissions"; |
92 private static final String PROPERTY_DEFAULT_PERMISSIONS = "defaultPermissions"; |
|
93 // Property used to specify the entry version to use for a multi-release JAR |
|
94 private static final String PROPERTY_RELEASE_VERSION = "releaseVersion"; |
|
95 // Original property used to specify the entry version to use for a |
|
96 // multi-release JAR which is kept for backwards compatibility. |
|
97 private static final String PROPERTY_MULTI_RELEASE = "multi-release"; |
88 |
98 |
89 private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS = |
99 private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS = |
90 PosixFilePermissions.fromString("rwxrwxrwx"); |
100 PosixFilePermissions.fromString("rwxrwxrwx"); |
91 // Property used to specify the compression mode to use |
101 // Property used to specify the compression mode to use |
92 private static final String PROPERTY_COMPRESSION_METHOD = "compressionMethod"; |
102 private static final String PROPERTY_COMPRESSION_METHOD = "compressionMethod"; |
1347 } |
1362 } |
1348 } |
1363 } |
1349 } |
1364 } |
1350 } |
1365 } |
1351 |
1366 |
|
1367 /** |
|
1368 * If a version property has been specified and the file represents a multi-release JAR, |
|
1369 * determine the requested runtime version and initialize the ZipFileSystem instance accordingly. |
|
1370 * |
|
1371 * Checks if the Zip File System property "releaseVersion" has been specified. If it has, |
|
1372 * use its value to determine the requested version. If not use the value of the "multi-release" property. |
|
1373 */ |
|
1374 private void initializeReleaseVersion(Map<String, ?> env) throws IOException { |
|
1375 Object o = env.containsKey(PROPERTY_RELEASE_VERSION) ? |
|
1376 env.get(PROPERTY_RELEASE_VERSION) : |
|
1377 env.get(PROPERTY_MULTI_RELEASE); |
|
1378 |
|
1379 if (o != null && isMultiReleaseJar()) { |
|
1380 int version; |
|
1381 if (o instanceof String) { |
|
1382 String s = (String)o; |
|
1383 if (s.equals("runtime")) { |
|
1384 version = Runtime.version().feature(); |
|
1385 } else if (s.matches("^[1-9][0-9]*$")) { |
|
1386 version = Version.parse(s).feature(); |
|
1387 } else { |
|
1388 throw new IllegalArgumentException("Invalid runtime version"); |
|
1389 } |
|
1390 } else if (o instanceof Integer) { |
|
1391 version = Version.parse(((Integer)o).toString()).feature(); |
|
1392 } else if (o instanceof Version) { |
|
1393 version = ((Version)o).feature(); |
|
1394 } else { |
|
1395 throw new IllegalArgumentException("env parameter must be String, " + |
|
1396 "Integer, or Version"); |
|
1397 } |
|
1398 createVersionedLinks(version < 0 ? 0 : version); |
|
1399 setReadOnly(); |
|
1400 } |
|
1401 } |
|
1402 |
|
1403 /** |
|
1404 * Returns true if the Manifest main attribute "Multi-Release" is set to true; false otherwise. |
|
1405 */ |
|
1406 private boolean isMultiReleaseJar() throws IOException { |
|
1407 try (InputStream is = newInputStream(getBytes("/META-INF/MANIFEST.MF"))) { |
|
1408 String multiRelease = new Manifest(is).getMainAttributes() |
|
1409 .getValue(Attributes.Name.MULTI_RELEASE); |
|
1410 return "true".equalsIgnoreCase(multiRelease); |
|
1411 } catch (NoSuchFileException x) { |
|
1412 return false; |
|
1413 } |
|
1414 } |
|
1415 |
|
1416 /** |
|
1417 * Create a map of aliases for versioned entries, for example: |
|
1418 * version/PackagePrivate.class -> META-INF/versions/9/version/PackagePrivate.class |
|
1419 * version/PackagePrivate.java -> META-INF/versions/9/version/PackagePrivate.java |
|
1420 * version/Version.class -> META-INF/versions/10/version/Version.class |
|
1421 * version/Version.java -> META-INF/versions/10/version/Version.java |
|
1422 * |
|
1423 * Then wrap the map in a function that getEntry can use to override root |
|
1424 * entry lookup for entries that have corresponding versioned entries. |
|
1425 */ |
|
1426 private void createVersionedLinks(int version) { |
|
1427 IndexNode verdir = getInode(getBytes("/META-INF/versions")); |
|
1428 // nothing to do, if no /META-INF/versions |
|
1429 if (verdir == null) { |
|
1430 return; |
|
1431 } |
|
1432 // otherwise, create a map and for each META-INF/versions/{n} directory |
|
1433 // put all the leaf inodes, i.e. entries, into the alias map |
|
1434 // possibly shadowing lower versioned entries |
|
1435 HashMap<IndexNode, byte[]> aliasMap = new HashMap<>(); |
|
1436 getVersionMap(version, verdir).values().forEach(versionNode -> |
|
1437 walk(versionNode.child, entryNode -> |
|
1438 aliasMap.put( |
|
1439 getOrCreateInode(getRootName(entryNode, versionNode), entryNode.isdir), |
|
1440 entryNode.name)) |
|
1441 ); |
|
1442 entryLookup = path -> { |
|
1443 byte[] entry = aliasMap.get(IndexNode.keyOf(path)); |
|
1444 return entry == null ? path : entry; |
|
1445 }; |
|
1446 } |
|
1447 |
|
1448 /** |
|
1449 * Create a sorted version map of version -> inode, for inodes <= max version. |
|
1450 * 9 -> META-INF/versions/9 |
|
1451 * 10 -> META-INF/versions/10 |
|
1452 */ |
|
1453 private TreeMap<Integer, IndexNode> getVersionMap(int version, IndexNode metaInfVersions) { |
|
1454 TreeMap<Integer,IndexNode> map = new TreeMap<>(); |
|
1455 IndexNode child = metaInfVersions.child; |
|
1456 while (child != null) { |
|
1457 Integer key = getVersion(child, metaInfVersions); |
|
1458 if (key != null && key <= version) { |
|
1459 map.put(key, child); |
|
1460 } |
|
1461 child = child.sibling; |
|
1462 } |
|
1463 return map; |
|
1464 } |
|
1465 |
|
1466 /** |
|
1467 * Extract the integer version number -- META-INF/versions/9 returns 9. |
|
1468 */ |
|
1469 private Integer getVersion(IndexNode inode, IndexNode metaInfVersions) { |
|
1470 try { |
|
1471 byte[] fullName = inode.name; |
|
1472 return Integer.parseInt(getString(Arrays |
|
1473 .copyOfRange(fullName, metaInfVersions.name.length + 1, fullName.length))); |
|
1474 } catch (NumberFormatException x) { |
|
1475 // ignore this even though it might indicate issues with the JAR structure |
|
1476 return null; |
|
1477 } |
|
1478 } |
|
1479 |
|
1480 /** |
|
1481 * Walk the IndexNode tree processing all leaf nodes. |
|
1482 */ |
|
1483 private void walk(IndexNode inode, Consumer<IndexNode> consumer) { |
|
1484 if (inode == null) return; |
|
1485 if (inode.isDir()) { |
|
1486 walk(inode.child, consumer); |
|
1487 } else { |
|
1488 consumer.accept(inode); |
|
1489 } |
|
1490 walk(inode.sibling, consumer); |
|
1491 } |
|
1492 |
|
1493 /** |
|
1494 * Extract the root name from a versioned entry name. |
|
1495 * E.g. given inode 'META-INF/versions/9/foo/bar.class' |
|
1496 * and prefix 'META-INF/versions/9/' returns 'foo/bar.class'. |
|
1497 */ |
|
1498 private byte[] getRootName(IndexNode inode, IndexNode prefix) { |
|
1499 byte[] fullName = inode.name; |
|
1500 return Arrays.copyOfRange(fullName, prefix.name.length, fullName.length); |
|
1501 } |
|
1502 |
1352 // Reads zip file central directory. Returns the file position of first |
1503 // Reads zip file central directory. Returns the file position of first |
1353 // CEN header, otherwise returns -1 if an error occurred. If zip->msg != NULL |
1504 // CEN header, otherwise returns -1 if an error occurred. If zip->msg != NULL |
1354 // then the error was a zip format error and zip->msg has the error text. |
1505 // then the error was a zip format error and zip->msg has the error text. |
1355 // Always pass in -1 for knownTotal; it's used for a recursive call. |
1506 // Always pass in -1 for knownTotal; it's used for a recursive call. |
1356 private byte[] initCEN() throws IOException { |
1507 private byte[] initCEN() throws IOException { |
1642 |
1793 |
1643 Files.move(tmpFile, zfpath, REPLACE_EXISTING); |
1794 Files.move(tmpFile, zfpath, REPLACE_EXISTING); |
1644 hasUpdate = false; // clear |
1795 hasUpdate = false; // clear |
1645 } |
1796 } |
1646 |
1797 |
1647 IndexNode getInode(byte[] path) { |
1798 private IndexNode getInode(byte[] path) { |
1648 return inodes.get(IndexNode.keyOf(Objects.requireNonNull(path, "path"))); |
1799 return inodes.get(IndexNode.keyOf(Objects.requireNonNull(entryLookup.apply(path), "path"))); |
1649 } |
1800 } |
1650 |
1801 |
1651 /** |
1802 /** |
1652 * Return the IndexNode from the root tree. If it doesn't exist, |
1803 * Return the IndexNode from the root tree. If it doesn't exist, |
1653 * it gets created along with all parent directory IndexNodes. |
1804 * it gets created along with all parent directory IndexNodes. |
1654 */ |
1805 */ |
1655 IndexNode getOrCreateInode(byte[] path, boolean isdir) { |
1806 private IndexNode getOrCreateInode(byte[] path, boolean isdir) { |
1656 IndexNode node = getInode(path); |
1807 IndexNode node = getInode(path); |
1657 // if node exists, return it |
1808 // if node exists, return it |
1658 if (node != null) { |
1809 if (node != null) { |
1659 return node; |
1810 return node; |
1660 } |
1811 } |