< prev index next >
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
Print this page
rev 54189 : 8213031: (zipfs) Add support for POSIX file permissions
*** 41,50 ****
--- 41,54 ----
import java.nio.channels.SeekableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.*;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
+ import java.nio.file.attribute.GroupPrincipal;
+ import java.nio.file.attribute.PosixFilePermission;
+ import java.nio.file.attribute.PosixFilePermissions;
+ import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
*** 76,115 ****
* A FileSystem built on a zip file
*
* @author Xueming Shen
*/
class ZipFileSystem extends FileSystem {
private final ZipFileSystemProvider provider;
private final Path zfpath;
final ZipCoder zc;
private final ZipPath rootdir;
private boolean readOnly = false; // readonly file system
// configurable by env map
private final boolean noExtt; // see readExtra()
private final boolean useTempFile; // use a temp file for newOS, default
// is to use BAOS for better performance
! private static final boolean isWindows = AccessController.doPrivileged(
! (PrivilegedAction<Boolean>)() -> System.getProperty("os.name")
! .startsWith("Windows"));
private final boolean forceEnd64;
private final int defaultMethod; // METHOD_STORED if "noCompression=true"
// METHOD_DEFLATED otherwise
ZipFileSystem(ZipFileSystemProvider provider,
Path zfpath,
Map<String, ?> env) throws IOException
{
// default encoding for name/comment
String nameEncoding = env.containsKey("encoding") ?
! (String)env.get("encoding") : "UTF-8";
this.noExtt = "false".equals(env.get("zipinfo-time"));
this.useTempFile = isTrue(env, "useTempFile");
this.forceEnd64 = isTrue(env, "forceZIP64End");
! this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED;
if (Files.notExists(zfpath)) {
! // create a new zip if not exists
if (isTrue(env, "create")) {
try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
new END().write(os, 0, forceEnd64);
}
} else {
--- 80,144 ----
* A FileSystem built on a zip file
*
* @author Xueming Shen
*/
class ZipFileSystem extends FileSystem {
+ // statics
+ private static final boolean isWindows = AccessController.doPrivileged(
+ (PrivilegedAction<Boolean>)()->System.getProperty("os.name")
+ .startsWith("Windows"));
+ private static final String OPT_POSIX = "enablePosixFileAttributes";
+ private static final String OPT_DEFAULT_OWNER = "defaultOwner";
+ private static final String OPT_DEFAULT_GROUP = "defaultGroup";
+ private static final String OPT_DEFAULT_PERMISSIONS = "defaultPermissions";
+
+ private static final String DEFAULT_PRINCIPAL_NAME = "<zipfs_default>";
+ private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS =
+ PosixFilePermissions.fromString("rwxrwxrwx");
+
private final ZipFileSystemProvider provider;
private final Path zfpath;
final ZipCoder zc;
private final ZipPath rootdir;
private boolean readOnly = false; // readonly file system
// configurable by env map
private final boolean noExtt; // see readExtra()
private final boolean useTempFile; // use a temp file for newOS, default
// is to use BAOS for better performance
!
private final boolean forceEnd64;
private final int defaultMethod; // METHOD_STORED if "noCompression=true"
// METHOD_DEFLATED otherwise
+ // POSIX support
+ final boolean supportPosix;
+ private final UserPrincipal defaultOwner;
+ private final GroupPrincipal defaultGroup;
+ private final Set<PosixFilePermission> defaultPermissions;
+
+ private final Set<String> supportedFileAttributeViews;
+
ZipFileSystem(ZipFileSystemProvider provider,
Path zfpath,
Map<String, ?> env) throws IOException
{
// default encoding for name/comment
String nameEncoding = env.containsKey("encoding") ?
! (String)env.get("encoding") : "UTF-8";
this.noExtt = "false".equals(env.get("zipinfo-time"));
this.useTempFile = isTrue(env, "useTempFile");
this.forceEnd64 = isTrue(env, "forceZIP64End");
! this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED;
! this.supportPosix = isTrue(env, OPT_POSIX);
! this.defaultOwner = initOwner(zfpath, env);
! this.defaultGroup = initGroup(env);
! this.defaultPermissions = initPermissions(env);
! this.supportedFileAttributeViews = supportPosix ?
! Set.of("basic", "posix", "zip") : Set.of("basic", "zip");
if (Files.notExists(zfpath)) {
! // create a new zip if it doesn't exist
if (isTrue(env, "create")) {
try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
new END().write(os, 0, forceEnd64);
}
} else {
*** 117,127 ****
}
}
// sm and existence check
zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
boolean writeable = AccessController.doPrivileged(
! (PrivilegedAction<Boolean>) () -> Files.isWritable(zfpath));
this.readOnly = !writeable;
this.zc = ZipCoder.get(nameEncoding);
this.rootdir = new ZipPath(this, new byte[]{'/'});
this.ch = Files.newByteChannel(zfpath, READ);
try {
--- 146,156 ----
}
}
// sm and existence check
zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
boolean writeable = AccessController.doPrivileged(
! (PrivilegedAction<Boolean>)()->Files.isWritable(zfpath));
this.readOnly = !writeable;
this.zc = ZipCoder.get(nameEncoding);
this.rootdir = new ZipPath(this, new byte[]{'/'});
this.ch = Files.newByteChannel(zfpath, READ);
try {
*** 141,150 ****
--- 170,263 ----
// returns true if there is a name=true/"true" setting in env
private static boolean isTrue(Map<String, ?> env, String name) {
return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
}
+ // Initialize the default owner for files inside the zip archive.
+ // If not specified in env, it is the owner of the archive. If no owner can
+ // be determined, we try to go with system property "user.name". If that's not
+ // accessible, we return "<zipfs_default>".
+ private UserPrincipal initOwner(Path zfpath, Map<String, ?> env) {
+ Object o = env.get(OPT_DEFAULT_OWNER);
+ if (o == null) {
+ try {
+ return Files.getOwner(zfpath);
+ } catch (Exception e) {
+ try {
+ String userName = AccessController.doPrivileged(
+ (PrivilegedAction<String>)()->System.getProperty("user.name"));
+ return ()->userName;
+ } catch (Exception e2) {
+ return ()->DEFAULT_PRINCIPAL_NAME;
+ }
+ }
+ }
+ if (o instanceof String) {
+ if (((String)o).isEmpty()) {
+ throw new IllegalArgumentException("Value for property " +
+ OPT_DEFAULT_OWNER + " must not be empty.");
+ }
+ return ()->(String)o;
+ }
+ if (o instanceof UserPrincipal) {
+ return (UserPrincipal)o;
+ }
+ throw new IllegalArgumentException("Value for property " +
+ OPT_DEFAULT_OWNER + " must be of type " + String.class +
+ " or " + UserPrincipal.class);
+ }
+
+ // Initialize the default group for files inside the zip archive.
+ // If not specified in env, it will return a group principal going
+ // by the same name as the default owner.
+ private GroupPrincipal initGroup(Map<String, ?> env) {
+ Object o = env.get(OPT_DEFAULT_GROUP);
+ if (o == null) {
+ return ()->defaultOwner.getName();
+ }
+ if (o instanceof String) {
+ if (((String)o).isEmpty()) {
+ throw new IllegalArgumentException("Value for property " +
+ OPT_DEFAULT_GROUP + " must not be empty.");
+ }
+ return ()->(String)o;
+ }
+ if (o instanceof GroupPrincipal) {
+ return (GroupPrincipal)o;
+ }
+ throw new IllegalArgumentException("Value for property " +
+ OPT_DEFAULT_GROUP + " must be of type " + String.class +
+ " or " + GroupPrincipal.class);
+ }
+
+ // Initialize the default permissions for files inside the zip archive.
+ // If not specified in env, it will return 777.
+ private Set<PosixFilePermission> initPermissions(Map<String, ?> env) {
+ Object o = env.get(OPT_DEFAULT_PERMISSIONS);
+ if (o == null) {
+ return DEFAULT_PERMISSIONS;
+ }
+ if (o instanceof String) {
+ return PosixFilePermissions.fromString((String)o);
+ }
+ if (!(o instanceof Set)) {
+ throw new IllegalArgumentException("Value for property " +
+ OPT_DEFAULT_PERMISSIONS + " must be of type " + String.class +
+ " or " + Set.class);
+ }
+ Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
+ for (Object o2 : (Set<?>)o) {
+ if (o2 instanceof PosixFilePermission) {
+ perms.add((PosixFilePermission)o2);
+ } else {
+ throw new IllegalArgumentException(OPT_DEFAULT_PERMISSIONS +
+ " must only contain objects of type " + PosixFilePermission.class);
+ }
+ }
+ return perms;
+ }
+
@Override
public FileSystemProvider provider() {
return provider;
}
*** 216,228 ****
@Override
public Iterable<FileStore> getFileStores() {
return List.of(new ZipFileStore(rootdir));
}
- private static final Set<String> supportedFileAttributeViews =
- Set.of("basic", "zip");
-
@Override
public Set<String> supportedFileAttributeViews() {
return supportedFileAttributeViews;
}
--- 329,338 ----
*** 369,379 ****
ensureOpen();
Entry e = getEntry(path); // ensureOpen checked
if (e == null)
throw new NoSuchFileException(getString(path));
if (e.type == Entry.CEN)
! e.type = Entry.COPY; // copy e
if (mtime != null)
e.mtime = mtime.toMillis();
if (atime != null)
e.atime = atime.toMillis();
if (ctime != null)
--- 479,489 ----
ensureOpen();
Entry e = getEntry(path); // ensureOpen checked
if (e == null)
throw new NoSuchFileException(getString(path));
if (e.type == Entry.CEN)
! e.type = Entry.COPY; // copy e
if (mtime != null)
e.mtime = mtime.toMillis();
if (atime != null)
e.atime = atime.toMillis();
if (ctime != null)
*** 382,391 ****
--- 492,558 ----
} finally {
endWrite();
}
}
+ void setOwner(byte[] path, UserPrincipal owner) throws IOException {
+ checkWritable();
+ beginWrite();
+ try {
+ ensureOpen();
+ Entry e = getEntry(path); // ensureOpen checked
+ if (e == null) {
+ throw new NoSuchFileException(getString(path));
+ }
+ // as the owner information is not persistent, we don't need to
+ // change e.type to Entry.COPY
+ e.owner = owner;
+ update(e);
+ } finally {
+ endWrite();
+ }
+ }
+
+ void setPermissions(byte[] path, Set<PosixFilePermission> perms)
+ throws IOException
+ {
+ checkWritable();
+ beginWrite();
+ try {
+ ensureOpen();
+ Entry e = getEntry(path); // ensureOpen checked
+ if (e == null) {
+ throw new NoSuchFileException(getString(path));
+ }
+ if (e.type == Entry.CEN) {
+ e.type = Entry.COPY; // copy e
+ }
+ e.posixPerms = perms == null ? -1 : ZipUtils.permsToFlags(perms);
+ update(e);
+ } finally {
+ endWrite();
+ }
+ }
+
+ void setGroup(byte[] path, GroupPrincipal group) throws IOException {
+ checkWritable();
+ beginWrite();
+ try {
+ ensureOpen();
+ Entry e = getEntry(path); // ensureOpen checked
+ if (e == null) {
+ throw new NoSuchFileException(getString(path));
+ }
+ // as the group information is not persistent, we don't need to
+ // change e.type to Entry.COPY
+ e.group = group;
+ update(e);
+ } finally {
+ endWrite();
+ }
+ }
+
boolean exists(byte[] path)
throws IOException
{
beginRead();
try {
*** 451,461 ****
try {
ensureOpen();
if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
throw new FileAlreadyExistsException(getString(dir));
checkParents(dir);
! Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
update(e);
} finally {
endWrite();
}
}
--- 618,628 ----
try {
ensureOpen();
if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
throw new FileAlreadyExistsException(getString(dir));
checkParents(dir);
! Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
update(e);
} finally {
endWrite();
}
}
*** 632,645 ****
private int getCompressMethod(FileAttribute<?>... attrs) {
return defaultMethod;
}
! // Returns a Writable/ReadByteChannel for now. Might consdier to use
// newFileChannel() instead, which dump the entry data into a regular
! // file on the default file system and create a FileChannel on top of
! // it.
SeekableByteChannel newByteChannel(byte[] path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException
{
--- 799,811 ----
private int getCompressMethod(FileAttribute<?>... attrs) {
return defaultMethod;
}
! // Returns a Writable/ReadByteChannel for now. Might consider to use
// newFileChannel() instead, which dump the entry data into a regular
! // file on the default file system and create a FileChannel on top of it.
SeekableByteChannel newByteChannel(byte[] path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException
{
*** 673,683 ****
}
if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
throw new NoSuchFileException(getString(path));
checkParents(path);
return new EntryOutputChannel(
! new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));
} finally {
endRead();
}
} else {
--- 839,849 ----
}
if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
throw new NoSuchFileException(getString(path));
checkParents(path);
return new EntryOutputChannel(
! new Entry(path, Entry.NEW, false, getCompressMethod(attrs), attrs));
} finally {
endRead();
}
} else {
*** 738,748 ****
final boolean isFCH = (e != null && e.type == Entry.FILECH);
final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
final FileChannel fch = tmpfile.getFileSystem()
.provider()
.newFileChannel(tmpfile, options, attrs);
! final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
if (forWrite) {
u.flag = FLAG_DATADESCR;
u.method = getCompressMethod(attrs);
}
// is there a better way to hook into the FileChannel's close method?
--- 904,914 ----
final boolean isFCH = (e != null && e.type == Entry.FILECH);
final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
final FileChannel fch = tmpfile.getFileSystem()
.provider()
.newFileChannel(tmpfile, options, attrs);
! final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH, attrs);
if (forWrite) {
u.flag = FLAG_DATADESCR;
u.method = getCompressMethod(attrs);
}
// is there a better way to hook into the FileChannel's close method?
*** 1267,1280 ****
} else { // unchanged inode
if (inode.pos == -1) {
continue; // pseudo directory node
}
if (inode.name.length == 1 && inode.name[0] == '/') {
! continue; // no root '/' directory even it
! // exits in original zip/jar file.
}
! e = Entry.readCEN(this, inode);
try {
written += copyLOCEntry(e, false, os, written, buf);
elist.add(e);
} catch (IOException x) {
x.printStackTrace(); // skip any wrong entry
--- 1433,1446 ----
} else { // unchanged inode
if (inode.pos == -1) {
continue; // pseudo directory node
}
if (inode.name.length == 1 && inode.name[0] == '/') {
! continue; // no root '/' directory even if it
! // exists in original zip/jar file.
}
! e = new Entry(inode);
try {
written += copyLOCEntry(e, false, os, written, buf);
elist.add(e);
} catch (IOException x) {
x.printStackTrace(); // skip any wrong entry
*** 1308,1318 ****
IndexNode inode = getInode(path);
if (inode instanceof Entry)
return (Entry)inode;
if (inode == null || inode.pos == -1)
return null;
! return Entry.readCEN(this, inode);
}
public void deleteFile(byte[] path, boolean failIfNotExists)
throws IOException
{
--- 1474,1484 ----
IndexNode inode = getInode(path);
if (inode instanceof Entry)
return (Entry)inode;
if (inode == null || inode.pos == -1)
return null;
! return new Entry(inode);
}
public void deleteFile(byte[] path, boolean failIfNotExists)
throws IOException
{
*** 1776,1787 ****
// The node itself can be used as a "key" to lookup itself in
// the HashMap inodes.
static class IndexNode {
byte[] name;
int hashcode; // node is hashable/hashed by its name
! int pos = -1; // position in cen table, -1 menas the
! // entry does not exists in zip file
boolean isdir;
IndexNode(byte[] name, boolean isdir) {
name(name);
this.isdir = isdir;
--- 1942,1953 ----
// The node itself can be used as a "key" to lookup itself in
// the HashMap inodes.
static class IndexNode {
byte[] name;
int hashcode; // node is hashable/hashed by its name
! int pos = -1; // position in cen table, -1 means the
! // entry does not exist in zip file
boolean isdir;
IndexNode(byte[] name, boolean isdir) {
name(name);
this.isdir = isdir;
*** 1853,1863 ****
IndexNode() {}
IndexNode sibling;
IndexNode child; // 1st child
}
! static class Entry extends IndexNode implements ZipFileAttributes {
static final int CEN = 1; // entry read from cen
static final int NEW = 2; // updated contents in bytes or file
static final int FILECH = 3; // fch update in "file"
static final int COPY = 4; // copy of a CEN entry
--- 2019,2029 ----
IndexNode() {}
IndexNode sibling;
IndexNode child; // 1st child
}
! class Entry extends IndexNode implements ZipFileAttributes {
static final int CEN = 1; // entry read from cen
static final int NEW = 2; // updated contents in bytes or file
static final int FILECH = 3; // fch update in "file"
static final int COPY = 4; // copy of a CEN entry
*** 1867,1876 ****
--- 2033,2043 ----
int type = CEN; // default is the entry read from cen
// entry attributes
int version;
int flag;
+ int posixPerms = -1; // posix permissions
int method = -1; // compression method
long mtime = -1; // last modification time (in DOS time)
long atime = -1; // last access time
long ctime = -1; // create time
long crc = -1; // crc-32 of entry data
*** 1886,1895 ****
--- 2053,2066 ----
// int attrs;
// long attrsEx;
long locoff;
byte[] comment;
+ // posix support
+ private UserPrincipal owner = defaultOwner;
+ private GroupPrincipal group = defaultGroup;
+
Entry() {}
Entry(byte[] name, boolean isdir, int method) {
name(name);
this.isdir = isdir;
*** 1898,1913 ****
this.size = 0;
this.csize = 0;
this.method = method;
}
! Entry(byte[] name, int type, boolean isdir, int method) {
this(name, isdir, method);
this.type = type;
}
! Entry (Entry e, int type) {
name(e.name);
this.isdir = e.isdir;
this.version = e.version;
this.ctime = e.ctime;
this.atime = e.atime;
--- 2069,2091 ----
this.size = 0;
this.csize = 0;
this.method = method;
}
! @SuppressWarnings("unchecked")
! Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
this(name, isdir, method);
this.type = type;
+ for (FileAttribute<?> attr : attrs) {
+ String attrName = attr.name();
+ if (attrName.equals("posix:permissions")) {
+ posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
+ }
+ }
}
! Entry(Entry e, int type) {
name(e.name);
this.isdir = e.isdir;
this.version = e.version;
this.ctime = e.ctime;
this.atime = e.atime;
*** 1923,1959 ****
this.attrs = e.attrs;
this.attrsEx = e.attrsEx;
*/
this.locoff = e.locoff;
this.comment = e.comment;
this.type = type;
}
! Entry (byte[] name, Path file, int type) {
this(name, type, false, METHOD_STORED);
this.file = file;
}
! int version() throws ZipException {
! if (method == METHOD_DEFLATED)
! return 20;
! else if (method == METHOD_STORED)
! return 10;
! throw new ZipException("unsupported compression method");
! }
!
! ///////////////////// CEN //////////////////////
! static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
! throws IOException
! {
! return new Entry().cen(zipfs, inode);
! }
!
! private Entry cen(ZipFileSystem zipfs, IndexNode inode)
! throws IOException
! {
! byte[] cen = zipfs.cen;
int pos = inode.pos;
if (!cenSigAt(cen, pos))
zerror("invalid CEN header (bad signature)");
version = CENVER(cen, pos);
flag = CENFLG(cen, pos);
--- 2101,2130 ----
this.attrs = e.attrs;
this.attrsEx = e.attrsEx;
*/
this.locoff = e.locoff;
this.comment = e.comment;
+ this.posixPerms = e.posixPerms;
+ this.owner = e.owner;
+ this.group = e.group;
this.type = type;
}
! @SuppressWarnings("unchecked")
! Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
this(name, type, false, METHOD_STORED);
this.file = file;
+ for (FileAttribute<?> attr : attrs) {
+ String attrName = attr.name();
+ if (attrName.equals("posix:permissions")) {
+ posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
+ }
+ }
}
! // reads the full entry from an IndexNode
! Entry(IndexNode inode) throws IOException {
int pos = inode.pos;
if (!cenSigAt(cen, pos))
zerror("invalid CEN header (bad signature)");
version = CENVER(cen, pos);
flag = CENFLG(cen, pos);
*** 1969,1998 ****
versionMade = CENVEM(cen, pos);
disk = CENDSK(cen, pos);
attrs = CENATT(cen, pos);
attrsEx = CENATX(cen, pos);
*/
locoff = CENOFF(cen, pos);
pos += CENHDR;
this.name = inode.name;
this.isdir = inode.isdir;
this.hashcode = inode.hashcode;
pos += nlen;
if (elen > 0) {
extra = Arrays.copyOfRange(cen, pos, pos + elen);
pos += elen;
! readExtra(zipfs);
}
if (clen > 0) {
comment = Arrays.copyOfRange(cen, pos, pos + clen);
}
- return this;
}
int writeCEN(OutputStream os) throws IOException {
- int version0 = version();
long csize0 = csize;
long size0 = size;
long locoff0 = locoff;
int elen64 = 0; // extra for ZIP64
int elenNTFS = 0; // extra for NTFS (a/c/mtime)
--- 2140,2191 ----
versionMade = CENVEM(cen, pos);
disk = CENDSK(cen, pos);
attrs = CENATT(cen, pos);
attrsEx = CENATX(cen, pos);
*/
+ if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
+ posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
+ }
locoff = CENOFF(cen, pos);
pos += CENHDR;
this.name = inode.name;
this.isdir = inode.isdir;
this.hashcode = inode.hashcode;
pos += nlen;
if (elen > 0) {
extra = Arrays.copyOfRange(cen, pos, pos + elen);
pos += elen;
! readExtra(ZipFileSystem.this);
}
if (clen > 0) {
comment = Arrays.copyOfRange(cen, pos, pos + clen);
}
}
+ int version(boolean zip64) throws ZipException {
+ if (zip64) {
+ return 45;
+ }
+ if (method == METHOD_DEFLATED)
+ return 20;
+ else if (method == METHOD_STORED)
+ return 10;
+ throw new ZipException("unsupported compression method");
+ }
+
+ /**
+ * Adds information about compatibility of file attribute information
+ * to a version value.
+ */
+ int versionMadeBy(int version) {
+ return (posixPerms < 0) ? version :
+ VERSION_BASE_UNIX | (version & 0xff);
+ }
+
+ ///////////////////// CEN //////////////////////
int writeCEN(OutputStream os) throws IOException {
long csize0 = csize;
long size0 = size;
long locoff0 = locoff;
int elen64 = 0; // extra for ZIP64
int elenNTFS = 0; // extra for NTFS (a/c/mtime)
*** 2019,2028 ****
--- 2212,2223 ----
elen64 += 8; // offset(8)
}
if (elen64 != 0) {
elen64 += 4; // header and data sz 4 bytes
}
+ boolean zip64 = (elen64 != 0);
+ int version0 = version(zip64);
while (eoff + 4 < elen) {
int tag = SH(extra, eoff);
int sz = SH(extra, eoff + 2);
if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
foundExtraTime = true;
*** 2035,2051 ****
} else { // Extended Timestamp otherwise
elenEXTT = 9; // only mtime in cen
}
}
writeInt(os, CENSIG); // CEN header signature
! if (elen64 != 0) {
! writeShort(os, 45); // ver 4.5 for zip64
! writeShort(os, 45);
! } else {
! writeShort(os, version0); // version made by
! writeShort(os, version0); // version needed to extract
! }
writeShort(os, flag); // general purpose bit flag
writeShort(os, method); // compression method
// last modification time
writeInt(os, (int)javaToDosTime(mtime));
writeInt(os, crc); // crc-32
--- 2230,2241 ----
} else { // Extended Timestamp otherwise
elenEXTT = 9; // only mtime in cen
}
}
writeInt(os, CENSIG); // CEN header signature
! writeShort(os, versionMadeBy(version0)); // version made by
! writeShort(os, version0); // version needed to extract
writeShort(os, flag); // general purpose bit flag
writeShort(os, method); // compression method
// last modification time
writeInt(os, (int)javaToDosTime(mtime));
writeInt(os, crc); // crc-32
*** 2059,2072 ****
} else {
writeShort(os, 0);
}
writeShort(os, 0); // starting disk number
writeShort(os, 0); // internal file attributes (unused)
! writeInt(os, 0); // external file attributes (unused)
writeInt(os, locoff0); // relative offset of local header
writeBytes(os, zname, 1, nlen);
! if (elen64 != 0) {
writeShort(os, EXTID_ZIP64);// Zip64 extra
writeShort(os, elen64 - 4); // size of "this" extra block
if (size0 == ZIP64_MINVAL)
writeLong(os, size);
if (csize0 == ZIP64_MINVAL)
--- 2249,2264 ----
} else {
writeShort(os, 0);
}
writeShort(os, 0); // starting disk number
writeShort(os, 0); // internal file attributes (unused)
! writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
! // attributes, used for storing posix
! // permissions
writeInt(os, locoff0); // relative offset of local header
writeBytes(os, zname, 1, nlen);
! if (zip64) {
writeShort(os, EXTID_ZIP64);// Zip64 extra
writeShort(os, elen64 - 4); // size of "this" extra block
if (size0 == ZIP64_MINVAL)
writeLong(os, size);
if (csize0 == ZIP64_MINVAL)
*** 2101,2122 ****
}
///////////////////// LOC //////////////////////
int writeLOC(OutputStream os) throws IOException {
- int version0 = version();
byte[] zname = isdir ? toDirectoryPath(name) : name;
int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
int elen = (extra != null) ? extra.length : 0;
boolean foundExtraTime = false; // if extra timestamp present
int eoff = 0;
int elen64 = 0;
int elenEXTT = 0;
int elenNTFS = 0;
writeInt(os, LOCSIG); // LOC header signature
if ((flag & FLAG_DATADESCR) != 0) {
! writeShort(os, version0); // version needed to extract
writeShort(os, flag); // general purpose bit flag
writeShort(os, method); // compression method
// last modification time
writeInt(os, (int)javaToDosTime(mtime));
// store size, uncompressed size, and crc-32 in data descriptor
--- 2293,2314 ----
}
///////////////////// LOC //////////////////////
int writeLOC(OutputStream os) throws IOException {
byte[] zname = isdir ? toDirectoryPath(name) : name;
int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
int elen = (extra != null) ? extra.length : 0;
boolean foundExtraTime = false; // if extra timestamp present
int eoff = 0;
int elen64 = 0;
+ boolean zip64 = false;
int elenEXTT = 0;
int elenNTFS = 0;
writeInt(os, LOCSIG); // LOC header signature
if ((flag & FLAG_DATADESCR) != 0) {
! writeShort(os, version(zip64)); // version needed to extract
writeShort(os, flag); // general purpose bit flag
writeShort(os, method); // compression method
// last modification time
writeInt(os, (int)javaToDosTime(mtime));
// store size, uncompressed size, and crc-32 in data descriptor
*** 2125,2144 ****
writeInt(os, 0);
writeInt(os, 0);
} else {
if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
! writeShort(os, 45); // ver 4.5 for zip64
! } else {
! writeShort(os, version0); // version needed to extract
}
writeShort(os, flag); // general purpose bit flag
writeShort(os, method); // compression method
// last modification time
writeInt(os, (int)javaToDosTime(mtime));
writeInt(os, crc); // crc-32
! if (elen64 != 0) {
writeInt(os, ZIP64_MINVAL);
writeInt(os, ZIP64_MINVAL);
} else {
writeInt(os, csize); // compressed size
writeInt(os, size); // uncompressed size
--- 2317,2335 ----
writeInt(os, 0);
writeInt(os, 0);
} else {
if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
elen64 = 20; //headid(2) + size(2) + size(8) + csize(8)
! zip64 = true;
}
+ writeShort(os, version(zip64)); // version needed to extract
writeShort(os, flag); // general purpose bit flag
writeShort(os, method); // compression method
// last modification time
writeInt(os, (int)javaToDosTime(mtime));
writeInt(os, crc); // crc-32
! if (zip64) {
writeInt(os, ZIP64_MINVAL);
writeInt(os, ZIP64_MINVAL);
} else {
writeInt(os, csize); // compressed size
writeInt(os, size); // uncompressed size
*** 2164,2174 ****
}
}
writeShort(os, nlen);
writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
writeBytes(os, zname, 1, nlen);
! if (elen64 != 0) {
writeShort(os, EXTID_ZIP64);
writeShort(os, 16);
writeLong(os, size);
writeLong(os, csize);
}
--- 2355,2365 ----
}
}
writeShort(os, nlen);
writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
writeBytes(os, zname, 1, nlen);
! if (zip64) {
writeShort(os, EXTID_ZIP64);
writeShort(os, 16);
writeLong(os, size);
writeLong(os, csize);
}
*** 2201,2211 ****
writeBytes(os, extra);
}
return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT;
}
! // Data Descriptior
int writeEXT(OutputStream os) throws IOException {
writeInt(os, EXTSIG); // EXT header signature
writeInt(os, crc); // crc-32
if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
writeLong(os, csize);
--- 2392,2402 ----
writeBytes(os, extra);
}
return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT;
}
! // Data Descriptor
int writeEXT(OutputStream os) throws IOException {
writeInt(os, EXTSIG); // EXT header signature
writeInt(os, crc); // crc-32
if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
writeLong(os, csize);
*** 2278,2299 ****
if (sz == 5)
mtime = unixToJavaTime(LG(extra, pos + 1));
break;
}
byte[] buf = new byte[LOCHDR];
! if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
!= buf.length)
throw new ZipException("loc: reading failed");
if (!locSigAt(buf, 0))
throw new ZipException("loc: wrong sig ->"
+ Long.toString(getSig(buf, 0), 16));
int locElen = LOCEXT(buf);
if (locElen < 9) // EXTT is at lease 9 bytes
break;
int locNlen = LOCNAM(buf);
buf = new byte[locElen];
! if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
!= buf.length)
throw new ZipException("loc extra: reading failed");
int locPos = 0;
while (locPos + 4 < buf.length) {
int locTag = SH(buf, locPos);
--- 2469,2490 ----
if (sz == 5)
mtime = unixToJavaTime(LG(extra, pos + 1));
break;
}
byte[] buf = new byte[LOCHDR];
! if (zipfs.readFullyAt(buf, 0, buf.length, locoff)
!= buf.length)
throw new ZipException("loc: reading failed");
if (!locSigAt(buf, 0))
throw new ZipException("loc: wrong sig ->"
+ Long.toString(getSig(buf, 0), 16));
int locElen = LOCEXT(buf);
if (locElen < 9) // EXTT is at lease 9 bytes
break;
int locNlen = LOCNAM(buf);
buf = new byte[locElen];
! if (zipfs.readFullyAt(buf, 0, buf.length, locoff + LOCHDR + locNlen)
!= buf.length)
throw new ZipException("loc extra: reading failed");
int locPos = 0;
while (locPos + 4 < buf.length) {
int locTag = SH(buf, locPos);
*** 2330,2339 ****
--- 2521,2554 ----
extra = Arrays.copyOf(extra, newOff);
else
extra = null;
}
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(1024);
+ Formatter fm = new Formatter(sb);
+ fm.format(" name : %s%n", new String(name));
+ fm.format(" creationTime : %tc%n", creationTime().toMillis());
+ fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
+ fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
+ fm.format(" isRegularFile : %b%n", isRegularFile());
+ fm.format(" isDirectory : %b%n", isDirectory());
+ fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
+ fm.format(" isOther : %b%n", isOther());
+ fm.format(" fileKey : %s%n", fileKey());
+ fm.format(" size : %d%n", size());
+ fm.format(" compressedSize : %d%n", compressedSize());
+ fm.format(" crc : %x%n", crc());
+ fm.format(" method : %d%n", method());
+ if (posixPerms != -1) {
+ fm.format(" permissions : %s%n", permissions());
+ }
+ fm.close();
+ return sb.toString();
+ }
+
///////// basic file attributes ///////////
@Override
public FileTime creationTime() {
return FileTime.fromMillis(ctime == -1 ? mtime : ctime);
}
*** 2376,2428 ****
@Override
public Object fileKey() {
return null;
}
! ///////// zip entry attributes ///////////
public long compressedSize() {
return csize;
}
public long crc() {
return crc;
}
public int method() {
return method;
}
public byte[] extra() {
if (extra != null)
return Arrays.copyOf(extra, extra.length);
return null;
}
public byte[] comment() {
if (comment != null)
return Arrays.copyOf(comment, comment.length);
return null;
}
! public String toString() {
! StringBuilder sb = new StringBuilder(1024);
! Formatter fm = new Formatter(sb);
! fm.format(" name : %s%n", new String(name));
! fm.format(" creationTime : %tc%n", creationTime().toMillis());
! fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
! fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
! fm.format(" isRegularFile : %b%n", isRegularFile());
! fm.format(" isDirectory : %b%n", isDirectory());
! fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
! fm.format(" isOther : %b%n", isOther());
! fm.format(" fileKey : %s%n", fileKey());
! fm.format(" size : %d%n", size());
! fm.format(" compressedSize : %d%n", compressedSize());
! fm.format(" crc : %x%n", crc());
! fm.format(" method : %d%n", method());
! fm.close();
! return sb.toString();
}
}
// ZIP directory has two issues:
// (1) ZIP spec does not require the ZIP file to include
--- 2591,2660 ----
@Override
public Object fileKey() {
return null;
}
! ///////// posix file attributes ///////////
!
! @Override
! public UserPrincipal owner() {
! return owner;
! }
!
! @Override
! public GroupPrincipal group() {
! return group;
! }
!
! @Override
! public Set<PosixFilePermission> permissions() {
! return storedPermissions().orElse(Set.copyOf(defaultPermissions));
! }
!
! ///////// zip file attributes ///////////
!
! @Override
public long compressedSize() {
return csize;
}
+ @Override
public long crc() {
return crc;
}
+ @Override
public int method() {
return method;
}
+ @Override
public byte[] extra() {
if (extra != null)
return Arrays.copyOf(extra, extra.length);
return null;
}
+ @Override
public byte[] comment() {
if (comment != null)
return Arrays.copyOf(comment, comment.length);
return null;
}
! @Override
! public Optional<Set<PosixFilePermission>> storedPermissions() {
! Set<PosixFilePermission> perms = null;
! if (posixPerms != -1) {
! perms = new HashSet<>(PosixFilePermission.values().length);
! for (PosixFilePermission perm : PosixFilePermission.values()) {
! if ((posixPerms & ZipUtils.permToFlag(perm)) != 0) {
! perms.add(perm);
! }
! }
! }
! return Optional.ofNullable(perms);
}
}
// ZIP directory has two issues:
// (1) ZIP spec does not require the ZIP file to include
< prev index next >