zipinfo: support DOS attributes.
golang doesn't include Unix mode by default.
Also show all the deflate variants ("defN" versus "defX").
Cope better with being called directly rather than via symlink.
Test: manual
Change-Id: I23b441c847ce9a557ea866b3c43bdf0542b26f10
diff --git a/libziparchive/include/ziparchive/zip_archive.h b/libziparchive/include/ziparchive/zip_archive.h
index f0f5a1d..047af90 100644
--- a/libziparchive/include/ziparchive/zip_archive.h
+++ b/libziparchive/include/ziparchive/zip_archive.h
@@ -40,8 +40,8 @@
* Represents information about a zip entry in a zip file.
*/
struct ZipEntry {
- // Compression method: One of kCompressStored or
- // kCompressDeflated.
+ // Compression method. One of kCompressStored or kCompressDeflated.
+ // See also `gpbf` for deflate subtypes.
uint16_t method;
// Modification time. The zipfile format specifies
@@ -55,7 +55,7 @@
struct tm GetModificationTime() const;
// Suggested Unix mode for this entry, from the zip archive if created on
- // Unix, or a default otherwise.
+ // Unix, or a default otherwise. See also `external_file_attributes`.
mode_t unix_mode;
// 1 if this entry contains a data descriptor segment, 0
@@ -80,10 +80,16 @@
// The offset to the start of data for this ZipEntry.
off64_t offset;
- // The version of zip and the host file system this came from.
+ // The version of zip and the host file system this came from (for zipinfo).
uint16_t version_made_by;
- // Whether this entry is believed to be text or binary.
+ // The raw attributes, whose interpretation depends on the host
+ // file system in `version_made_by` (for zipinfo). See also `unix_mode`.
+ uint32_t external_file_attributes;
+
+ // Specifics about the deflation (for zipinfo).
+ uint16_t gpbf;
+ // Whether this entry is believed to be text or binary (for zipinfo).
bool is_text;
};
diff --git a/libziparchive/unzip.cpp b/libziparchive/unzip.cpp
index e936614..6c2221e 100644
--- a/libziparchive/unzip.cpp
+++ b/libziparchive/unzip.cpp
@@ -34,13 +34,21 @@
#include <android-base/strings.h>
#include <ziparchive/zip_archive.h>
+using android::base::EndsWith;
+using android::base::StartsWith;
+
enum OverwriteMode {
kAlways,
kNever,
kPrompt,
};
-static bool is_unzip;
+enum Role {
+ kUnzip,
+ kZipinfo,
+};
+
+static Role role;
static OverwriteMode overwrite_mode = kPrompt;
static bool flag_1 = false;
static const char* flag_d = nullptr;
@@ -92,7 +100,7 @@
}
static void MaybeShowHeader(ZipArchiveHandle zah) {
- if (is_unzip) {
+ if (role == kUnzip) {
// unzip has three formats.
if (!flag_q) printf("Archive: %s\n", archive_name);
if (flag_v) {
@@ -116,7 +124,7 @@
}
static void MaybeShowFooter() {
- if (is_unzip) {
+ if (role == kUnzip) {
if (flag_v) {
printf(
"-------- ------- --- -------\n"
@@ -185,8 +193,7 @@
static void ExtractOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
// Bad filename?
- if (android::base::StartsWith(name, "/") || android::base::StartsWith(name, "../") ||
- name.find("/../") != std::string::npos) {
+ if (StartsWith(name, "/") || StartsWith(name, "../") || name.find("/../") != std::string::npos) {
error(1, 0, "bad filename %s", name.c_str());
}
@@ -194,7 +201,7 @@
std::string dst;
if (flag_d) {
dst = flag_d;
- if (!android::base::EndsWith(dst, "/")) dst += '/';
+ if (!EndsWith(dst, "/")) dst += '/';
}
dst += name;
@@ -204,7 +211,7 @@
}
// An entry in a zip file can just be a directory itself.
- if (android::base::EndsWith(name, "/")) {
+ if (EndsWith(name, "/")) {
if (mkdir(name.c_str(), entry.unix_mode) == -1) {
// If the directory already exists, that's fine.
if (errno == EEXIST) {
@@ -258,9 +265,26 @@
int version = entry.version_made_by & 0xff;
int os = (entry.version_made_by >> 8) & 0xff;
- // TODO: Support suid/sgid? Non-Unix host file system attributes?
- char mode[] = "??????????";
- if (os == 3) {
+ // TODO: Support suid/sgid? Non-Unix/non-FAT host file system attributes?
+ const char* src_fs = "???";
+ char mode[] = "??? ";
+ if (os == 0) {
+ src_fs = "fat";
+ // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
+ int attrs = entry.external_file_attributes & 0xff;
+ mode[0] = (attrs & 0x10) ? 'd' : '-';
+ mode[1] = 'r';
+ mode[2] = (attrs & 0x01) ? '-' : 'w';
+ // The man page also mentions ".btm", but that seems to be obsolete?
+ mode[3] = EndsWith(name, ".exe") || EndsWith(name, ".com") || EndsWith(name, ".bat") ||
+ EndsWith(name, ".cmd")
+ ? 'x'
+ : '-';
+ mode[4] = (attrs & 0x20) ? 'a' : '-';
+ mode[5] = (attrs & 0x02) ? 'h' : '-';
+ mode[6] = (attrs & 0x04) ? 's' : '-';
+ } else if (os == 3) {
+ src_fs = "unx";
mode[0] = S_ISDIR(entry.unix_mode) ? 'd' : (S_ISREG(entry.unix_mode) ? '-' : '?');
mode[1] = entry.unix_mode & S_IRUSR ? 'r' : '-';
mode[2] = entry.unix_mode & S_IWUSR ? 'w' : '-';
@@ -273,6 +297,11 @@
mode[9] = entry.unix_mode & S_IXOTH ? 'x' : '-';
}
+ char method[5] = "stor";
+ if (entry.method == kCompressDeflated) {
+ snprintf(method, sizeof(method), "def%c", "NXFS"[(entry.gpbf >> 1) & 0x3]);
+ }
+
// TODO: zipinfo (unlike unzip) sometimes uses time zone?
// TODO: this uses 4-digit years because we're not barbarians unless interoperability forces it.
tm t = entry.GetModificationTime();
@@ -281,14 +310,13 @@
t.tm_mday, t.tm_hour, t.tm_min);
// "-rw-r--r-- 3.0 unx 577 t- defX 19-Feb-12 16:09 android-ndk-r19b/sources/android/NOTICE"
- printf("%s %2d.%d %s %8d %c%c %s %s %s\n", mode, version / 10, version % 10,
- os == 3 ? "unx" : "???", entry.uncompressed_length, entry.is_text ? 't' : 'b',
- entry.has_data_descriptor ? 'X' : 'x', entry.method == kCompressStored ? "stor" : "defX",
- time, name.c_str());
+ printf("%s %2d.%d %s %8d %c%c %s %s %s\n", mode, version / 10, version % 10, src_fs,
+ entry.uncompressed_length, entry.is_text ? 't' : 'b',
+ entry.has_data_descriptor ? 'X' : 'x', method, time, name.c_str());
}
static void ProcessOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
- if (is_unzip) {
+ if (role == kUnzip) {
if (flag_l || flag_v) {
// -l or -lv or -lq or -v.
ListOne(entry, name);
@@ -333,7 +361,7 @@
}
static void ShowHelp(bool full) {
- if (is_unzip) {
+ if (role == kUnzip) {
fprintf(full ? stdout : stderr, "usage: unzip [-d DIR] [-lnopqv] ZIP [FILE...] [-x FILE...]\n");
if (!full) exit(EXIT_FAILURE);
@@ -391,12 +419,22 @@
}
int main(int argc, char* argv[]) {
- static struct option opts[] = {
+ // Who am I, and what am I doing?
+ const char* base = basename(argv[0]);
+ if (!strcmp(base, "ziptool") && argc > 1) return main(argc - 1, argv + 1);
+ if (!strcmp(base, "unzip")) {
+ role = kUnzip;
+ } else if (!strcmp(base, "zipinfo")) {
+ role = kZipinfo;
+ } else {
+ error(1, 0, "run as ziptool with unzip or zipinfo as the first argument, or symlink");
+ }
+
+ static const struct option opts[] = {
{"help", no_argument, 0, 'h'},
};
- is_unzip = !strcmp(basename(argv[0]), "unzip");
- if (is_unzip) {
+ if (role == kUnzip) {
int opt;
while ((opt = getopt_long(argc, argv, "-d:hlnopqvx", opts, nullptr)) != -1) {
switch (opt) {
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index caf8fae..ef29188 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -622,12 +622,16 @@
// 4.4.2.1: the upper byte of `version_made_by` gives the source OS. Unix is 3.
data->version_made_by = cdr->version_made_by;
+ data->external_file_attributes = cdr->external_file_attributes;
if ((data->version_made_by >> 8) == 3) {
data->unix_mode = (cdr->external_file_attributes >> 16) & 0xffff;
} else {
data->unix_mode = 0777;
}
+ // 4.4.4: general purpose bit flags.
+ data->gpbf = lfh->gpb_flags;
+
// 4.4.14: the lowest bit of the internal file attributes field indicates text.
// Currently only needed to implement zipinfo.
data->is_text = (cdr->internal_file_attributes & 1);