diff --git a/README.md b/README.md index 380579b..e982f98 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,6 @@ ordinary archives and scales to very large ones. zget https://example.com/archive.zip README.txt zget -o report.pdf https://example.com/documents.zip path/to/report.pdf zget -l https://example.com/archive.zip -zget -l https://example.com/documents.zip path/to/report.pdf zget -1 https://example.com/archive.zip zget https://example.com/data.zip config.json | jq . ``` @@ -42,8 +41,8 @@ sudo install zget-X.Y.Z-linux-x86_64/zget /usr/local/bin/zget ```text zget [-o FILE] URL MEMBER -zget -l URL [MEMBER] -zget -1 URL [MEMBER] +zget -l URL +zget -1 URL ``` ## Familiar by design @@ -58,7 +57,7 @@ and emits only names. Extraction requires both a URL and an exact member name. A URL without a member is a usage error rather than an implicit request to list the archive; use `-l` -explicitly to stream all entries, or add `MEMBER` to list one exact match. +or `-1` explicitly to stream the complete Central Directory listing. Listings use the familiar `unzip -l` columns: uncompressed length, modification date and time in UTC, and member name. Timestamps are resolved from NTFS, @@ -67,8 +66,7 @@ Central Directory once and retain only the current entry. Control characters and backslashes are escaped so every member stays on one safe output line. For a compact listing, `-1` writes only member names, one per line, with no -header or totals. It accepts the same optional exact `MEMBER` as `-l` and uses -the same safe escaping. +header or totals, using the same safe escaping as `-l`. ## Why zget? diff --git a/cli/zget.c b/cli/zget.c index b607d85..20ca65d 100644 --- a/cli/zget.c +++ b/cli/zget.c @@ -14,12 +14,10 @@ struct file_output { struct list_output { struct file_output stream; - const char *member; uint64_t entries; uint64_t total_size; int total_overflow; int names_only; - int found; int header_written; }; @@ -138,12 +136,6 @@ static int list_member(void *opaque, const zget_member_info *member) char prefix[64]; int length; - if (out->member != NULL && - (out->found || strcmp(out->member, member->name) != 0)) - return 0; - if (out->member != NULL) - out->found = 1; - /* * The short form deliberately shares the normal name encoder. Besides * making one-name-per-line output pleasant for scripts, this prevents a @@ -181,15 +173,15 @@ static int list_member(void *opaque, const zget_member_info *member) static void usage(FILE *file) { fprintf(file, "usage: zget [-o FILE] URL MEMBER\n" - " zget -l URL [MEMBER]\n" - " zget -1 URL [MEMBER]\n"); + " zget -l URL\n" + " zget -1 URL\n"); } int main(int argc, char **argv) { const char *output_path = NULL, *url, *member = NULL; struct file_output output = {stdout, 0}; - struct list_output listing = {{stdout, 0}, NULL, 0, 0, 0, 0, 0, 0}; + struct list_output listing = {{stdout, 0}, 0, 0, 0, 0, 0}; int arg = 1, close_output = 0, list_mode = 0, rc, exit_status = 1; if (argc == 2 && !strcmp(argv[1], "--version")) { @@ -212,13 +204,13 @@ int main(int argc, char **argv) } output_path = argv[arg++]; } - if ((list_mode && (argc - arg < 1 || argc - arg > 2)) || + if ((list_mode && argc - arg != 1) || (!list_mode && argc - arg != 2)) { usage(stderr); return 2; } url = argv[arg]; - if (!list_mode || argc - arg == 2) + if (!list_mode) member = argv[arg + 1]; if (member != NULL && member[0] == '\0') { fprintf(stderr, "zget: member path must not be empty\n"); @@ -243,17 +235,12 @@ int main(int argc, char **argv) char summary[96]; int length; - listing.member = member; rc = zget_list(url, list_member, &listing); if (rc != ZGET_OK) { if (listing.stream.broken_pipe) goto done; goto zget_failure; } - if (member != NULL && !listing.found) { - rc = ZGET_ENOTFOUND; - goto zget_failure; - } if (!listing.names_only) { if (write_list_header(&listing) || write_stream(&listing.stream, footer, sizeof(footer) - 1)) diff --git a/docs/zget.1.in b/docs/zget.1.in index 2c204dd..4025dd0 100644 --- a/docs/zget.1.in +++ b/docs/zget.1.in @@ -10,12 +10,10 @@ zget \- fetch one member from a remote ZIP archive .B zget .B \-l .I URL -.RI [ MEMBER ] .br .B zget .B \-1 .I URL -.RI [ MEMBER ] .br .B zget .RB [ \-h | \-\-help ] @@ -50,9 +48,9 @@ fails after writing begins, partial output remains. The special file name selects standard output. .PP .B \-l -streams a listing directly from the Central Directory. Each output row contains -the uncompressed length, UTC modification date and time, and member name -in a layout inspired by +streams the complete archive listing directly from the Central Directory. Each +output row contains the uncompressed length, UTC modification date and time, and +member name in a layout inspired by .BR unzip (1). Timestamps are resolved from NTFS, Extended Timestamp, or packed DOS metadata in that order. Control characters and backslashes are escaped so an @@ -60,7 +58,7 @@ archive name cannot create additional terminal lines. Listing memory use is independent of the entry count. .PP .B \-1 -uses the same streaming lookup but writes only member names, one per line, +uses the same streaming listing but writes only member names, one per line, without a header or totals. Names receive the same safe escaping as the table listing. This form follows the convention established by .BR zipinfo (1). @@ -77,15 +75,11 @@ of selects standard output. .TP .B \-l -List archive members to standard output. With -.IR MEMBER , -emit only the first exact match; otherwise emit every member. A URL without -this explicit option does not imply listing. +List all archive members to standard output. A URL without this explicit option +does not imply listing. .TP .B \-1 -List only archive member names, one per line. With -.IR MEMBER , -emit only the first exact match; otherwise emit every member. +List all archive member names, one per line. .TP .BR \-h , " " \-\-help Show command usage and exit. @@ -144,12 +138,6 @@ Stream the archive listing: zget -l https://example.com/archive.zip .fi .PP -List one exact member without extracting it: -.PP -.nf -zget -l https://example.com/documents.zip path/to/report.pdf -.fi -.PP List only member names: .PP .nf diff --git a/tests/integration.py b/tests/integration.py index 2decbea..900105c 100644 --- a/tests/integration.py +++ b/tests/integration.py @@ -205,6 +205,7 @@ def main(binary): # Syntax failures must remain local and use the conventional usage status. for arguments in ([], ["only-a-url"], ["-o"], ["-o", ""], ["-l"], ["-1"], + ["-l", "url", "member"], ["-1", "url", "member"], ["-l", "url", "member", "extra"], ["-1", "url", "member", "extra"]): result = subprocess.run([binary, *arguments], stdout=subprocess.PIPE, @@ -286,39 +287,13 @@ def suffix_fallback(base): # One rejected suffix, one size probe, one exact tail, and one CD request. assert run_server(data, "suffix-unsupported", suffix_fallback) == 4 - def specific_listing(base): - """An exact listing emits one row and stops at the first name match.""" - url = base + "/archive.zip" - result = subprocess.run([binary, "-l", url, "stored.txt"], - check=True, stdout=subprocess.PIPE) - lines = result.stdout.splitlines() - assert lines[2].endswith(b" stored.txt") - assert lines[2].startswith(f"{14:9d} ".encode()) - assert lines[-1] == f"{14:9d} 1 file".encode() - assert len(lines) == 5 - short = subprocess.run([binary, "-1", url, "stored.txt"], - check=True, stdout=subprocess.PIPE) - assert short.stdout == b"stored.txt\n" - missing = subprocess.run([binary, "-l", url, "missing"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - assert missing.returncode != 0 - assert b"member not found" in missing.stderr - short_missing = subprocess.run([binary, "-1", url, "missing"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - assert short_missing.returncode != 0 - assert short_missing.stdout == b"" - assert b"member not found" in short_missing.stderr - assert run_server(data, "normal", specific_listing) == 8 - stored_cd = central_entry(data, "stored.txt") invalid_date = mutate(data, stored_cd + 14, b"\x00\x00") def invalid_timestamp_listing(base): """Reject an entry when no valid modification-time source exists.""" - result = subprocess.run( - [binary, "-l", base + "/archive.zip", "stored.txt"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + result = subprocess.run([binary, "-l", base + "/archive.zip"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) assert result.returncode != 0 run_server(invalid_date, "normal", invalid_timestamp_listing) @@ -550,10 +525,6 @@ def duplicate(base): result = subprocess.run([binary, base + "/archive.zip", "same"], check=True, stdout=subprocess.PIPE) assert result.stdout == b"first" - result = subprocess.run([binary, "-l", base + "/archive.zip", "same"], - check=True, stdout=subprocess.PIPE) - assert f"{5:9d} ".encode() in result.stdout - assert result.stdout.endswith(f"{5:9d} 1 file\n".encode()) run_server(duplicates.getvalue(), "normal", duplicate) unicode_cd = central_entry(data, "unicod\u00e9.txt")