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
This commit is contained in:
Elliott Hughes 2019-10-28 21:35:52 -07:00
parent 51c41e94d9
commit d50952587d
3 changed files with 72 additions and 24 deletions

View file

@ -40,8 +40,8 @@ enum {
* 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 ZipEntry {
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 @@ struct ZipEntry {
// 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;
};

View file

@ -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 int CompressionRatio(int64_t uncompressed, int64_t compressed) {
}
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 MaybeShowHeader(ZipArchiveHandle zah) {
}
static void MaybeShowFooter() {
if (is_unzip) {
if (role == kUnzip) {
if (flag_v) {
printf(
"-------- ------- --- -------\n"
@ -185,8 +193,7 @@ static void ExtractToPipe(ZipArchiveHandle zah, ZipEntry& entry, const std::stri
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 @@ static void ExtractOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string&
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 @@ static void ExtractOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string&
}
// 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 @@ static void InfoOne(const ZipEntry& entry, const std::string& name) {
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 @@ static void InfoOne(const ZipEntry& entry, const std::string& name) {
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 @@ static void InfoOne(const ZipEntry& entry, const std::string& name) {
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 ProcessAll(ZipArchiveHandle zah) {
}
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 @@ static void HandleCommonOption(int opt) {
}
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) {

View file

@ -622,12 +622,16 @@ static int32_t FindEntry(const ZipArchive* archive, const int32_t ent, ZipEntry*
// 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);