From c157e4ce8eb170a92945cc2d292fd7106bdfcce1 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Tue, 23 May 2023 16:56:23 -0400 Subject: [PATCH] Port over FreeBSD's unzip utility This allows libarchive to act as a drop-in replacement to Info-ZIP --- .gitignore | 2 + CMakeLists.txt | 6 + Makefile.am | 118 ++- README.md | 1 + build/cmake/config.h.in | 3 + configure.ac | 49 ++ unzip/CMakeLists.txt | 37 + unzip/bsdunzip.1 | 203 +++++ unzip/bsdunzip.c | 1153 +++++++++++++++++++++++++++++ unzip/bsdunzip_platform.h | 83 +++ unzip/test/CMakeLists.txt | 70 ++ unzip/test/test.h | 40 + unzip/test/test_0.c | 58 ++ unzip/test/test_C.c | 41 + unzip/test/test_L.c | 44 ++ unzip/test/test_P_encryption.c | 41 + unzip/test/test_Z1.c | 40 + unzip/test/test_basic.c | 44 ++ unzip/test/test_basic.zip.uu | 25 + unzip/test/test_d.c | 44 ++ unzip/test/test_encrypted.zip.uu | 13 + unzip/test/test_glob.c | 44 ++ unzip/test/test_j.c | 44 ++ unzip/test/test_n.c | 48 ++ unzip/test/test_not_exist.c | 36 + unzip/test/test_o.c | 47 ++ unzip/test/test_p.c | 39 + unzip/test/test_q.c | 44 ++ unzip/test/test_singlefile.c | 41 + unzip/test/test_singlefile.zip.uu | 8 + unzip/test/test_t.c | 39 + unzip/test/test_t_bad.c | 39 + unzip/test/test_t_bad.zip.uu | 25 + unzip/test/test_x.c | 44 ++ 34 files changed, 2604 insertions(+), 9 deletions(-) create mode 100644 unzip/CMakeLists.txt create mode 100644 unzip/bsdunzip.1 create mode 100644 unzip/bsdunzip.c create mode 100644 unzip/bsdunzip_platform.h create mode 100644 unzip/test/CMakeLists.txt create mode 100644 unzip/test/test.h create mode 100644 unzip/test/test_0.c create mode 100644 unzip/test/test_C.c create mode 100644 unzip/test/test_L.c create mode 100644 unzip/test/test_P_encryption.c create mode 100644 unzip/test/test_Z1.c create mode 100644 unzip/test/test_basic.c create mode 100644 unzip/test/test_basic.zip.uu create mode 100644 unzip/test/test_d.c create mode 100644 unzip/test/test_encrypted.zip.uu create mode 100644 unzip/test/test_glob.c create mode 100644 unzip/test/test_j.c create mode 100644 unzip/test/test_n.c create mode 100644 unzip/test/test_not_exist.c create mode 100644 unzip/test/test_o.c create mode 100644 unzip/test/test_p.c create mode 100644 unzip/test/test_q.c create mode 100644 unzip/test/test_singlefile.c create mode 100644 unzip/test/test_singlefile.zip.uu create mode 100644 unzip/test/test_t.c create mode 100644 unzip/test/test_t_bad.c create mode 100644 unzip/test/test_t_bad.zip.uu create mode 100644 unzip/test/test_x.c diff --git a/.gitignore b/.gitignore index 6b4d2dc76..359b65236 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ autom4te.cache/ bsdcat bsdcpio bsdtar +bsdunzip build/autoconf/compile build/autoconf/config.guess build/autoconf/config.sub @@ -41,6 +42,7 @@ libarchive/test/list.h libtool stamp-h1 tar/test/list.h +unzip/test/list.h CMakeCache.txt CMakeFiles/ DartConfiguration.tcl diff --git a/CMakeLists.txt b/CMakeLists.txt index 5568d2404..2b9faee4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,8 @@ OPTION(ENABLE_CPIO "Enable cpio building" ON) OPTION(ENABLE_CPIO_SHARED "Enable dynamic build of cpio" FALSE) OPTION(ENABLE_CAT "Enable cat building" ON) OPTION(ENABLE_CAT_SHARED "Enable dynamic build of cat" FALSE) +OPTION(ENABLE_UNZIP "Enable unzip building" ON) +OPTION(ENABLE_UNZIP_SHARED "Enable dynamic build of unzip" FALSE) OPTION(ENABLE_XATTR "Enable extended attribute support" ON) OPTION(ENABLE_ACL "Enable ACL support" ON) OPTION(ENABLE_ICONV "Enable iconv support" ON) @@ -1472,6 +1474,9 @@ CHECK_C_SOURCE_COMPILES( "#include \n#include \nint main() {char buf[10]; return readlinkat(AT_FDCWD, \"\", buf, 0);}" HAVE_READLINKAT) +# Check for getopt uses optreset to reset +CHECK_SYMBOL_EXISTS(optreset "getopt.h" HAVE_GETOPT_OPTRESET) + # To verify major(), we need to both include the header # of interest and verify that the result can be linked. @@ -2096,3 +2101,4 @@ add_subdirectory(libarchive) add_subdirectory(cat) add_subdirectory(tar) add_subdirectory(cpio) +add_subdirectory(unzip) diff --git a/Makefile.am b/Makefile.am index 3fd2fdbf6..d3e8d3fa7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,17 +8,17 @@ ACLOCAL_AMFLAGS = -I build/autoconf # lib_LTLIBRARIES= libarchive.la noinst_LTLIBRARIES= libarchive_fe.la -bin_PROGRAMS= $(bsdtar_programs) $(bsdcpio_programs) $(bsdcat_programs) -man_MANS= $(libarchive_man_MANS) $(bsdtar_man_MANS) $(bsdcpio_man_MANS) $(bsdcat_man_MANS) -BUILT_SOURCES= libarchive/test/list.h tar/test/list.h cpio/test/list.h cat/test/list.h +bin_PROGRAMS= $(bsdtar_programs) $(bsdcpio_programs) $(bsdcat_programs) $(bsdunzip_programs) +man_MANS= $(libarchive_man_MANS) $(bsdtar_man_MANS) $(bsdcpio_man_MANS) $(bsdcat_man_MANS) $(bsdunzip_man_MANS) +BUILT_SOURCES= libarchive/test/list.h tar/test/list.h cpio/test/list.h cat/test/list.h unzip/test/list.h # # What to test: We always test libarchive, test bsdtar and bsdcpio only # if we built them. # -check_PROGRAMS= libarchive_test $(bsdtar_test_programs) $(bsdcpio_test_programs) $(bsdcat_test_programs) -TESTS= libarchive_test $(bsdtar_test_programs) $(bsdcpio_test_programs) $(bsdcat_test_programs) -TESTS_ENVIRONMENT= $(libarchive_TESTS_ENVIRONMENT) $(bsdtar_TESTS_ENVIRONMENT) $(bsdcpio_TESTS_ENVIRONMENT) $(bsdcat_TESTS_ENVIRONMENT) +check_PROGRAMS= libarchive_test $(bsdtar_test_programs) $(bsdcpio_test_programs) $(bsdcat_test_programs) $(bsdunzip_test_programs) +TESTS= libarchive_test $(bsdtar_test_programs) $(bsdcpio_test_programs) $(bsdcat_test_programs) $(bsdunzip_test_programs) +TESTS_ENVIRONMENT= $(libarchive_TESTS_ENVIRONMENT) $(bsdtar_TESTS_ENVIRONMENT) $(bsdcpio_TESTS_ENVIRONMENT) $(bsdcat_TESTS_ENVIRONMENT) $(bsdunzip_TESTS_ENVIRONMENT) # Always build and test both bsdtar and bsdcpio as part of 'distcheck' DISTCHECK_CONFIGURE_FLAGS = --enable-bsdtar --enable-bsdcpio # The next line is commented out by default in shipping libarchive releases. @@ -49,7 +49,9 @@ EXTRA_DIST= \ $(bsdcpio_EXTRA_DIST) \ $(bsdcpio_test_EXTRA_DIST) \ $(bsdcat_EXTRA_DIST) \ - $(bsdcat_test_EXTRA_DIST) + $(bsdcat_test_EXTRA_DIST) \ + $(bsdunzip_EXTRA_DIST) \ + $(bsdunzip_test_EXTRA_DIST) # a) Clean out some unneeded files and directories # b) Collect all documentation and format it for distribution. @@ -69,7 +71,8 @@ DISTCLEANFILES= \ libarchive/test/list.h \ tar/test/list.h \ cpio/test/list.h \ - cat/test/list.h + cat/test/list.h \ + unzip/test/list.h distclean-local: -rm -rf .ref @@ -82,7 +85,9 @@ distclean-local: -[ -f cpio/Makefile ] && cd cpio && make clean -[ -f cpio/test/Makefile ] && cd cpio/test && make clean -[ -f cat/Makefile ] && cd cat && make clean - -[ -f cpio/test/Makefile ] && cd cat/test && make clean + -[ -f cat/test/Makefile ] && cd cat/test && make clean + -[ -f unzip/Makefile ] && cd unzip && make clean + -[ -f unzip/test/Makefile ] && cd unzip/test && make clean # # Libarchive headers, source, etc. @@ -1428,3 +1433,98 @@ bsdcat_test_EXTRA_DIST= \ cat/test/test_expand.plain.uu \ cat/test/test_expand.xz.uu \ cat/test/CMakeLists.txt + +# +# +# bsdunzip source, docs, etc. +# +# + +bsdunzip_SOURCES= \ + unzip/bsdunzip.c \ + unzip/bsdunzip.h \ + unzip/bsdunzip_platform.h + +if INC_WINDOWS_FILES +bsdunzip_SOURCES+= +endif + +bsdunzip_DEPENDENCIES = libarchive.la libarchive_fe.la + + +if STATIC_BSDUNZIP +bsdunzip_ldstatic= -static +bsdunzip_ccstatic= -DLIBARCHIVE_STATIC +else +bsdunzip_ldstatic= +bsdunzip_ccstatic= +endif + +bsdunzip_LDADD= libarchive_fe.la libarchive.la $(LTLIBICONV) +bsdunzip_CPPFLAGS= -I$(top_srcdir)/libarchive -I$(top_srcdir)/libarchive_fe $(bsdunzip_ccstatic) $(PLATFORMCPPFLAGS) +bsdunzip_LDFLAGS= $(bsdunzip_ldstatic) $(GC_SECTIONS) + +bsdunzip_EXTRA_DIST= \ + unzip/bsdunzip.1 \ + unzip/CMakeLists.txt + + +if BUILD_BSDUNZIP +# Manpages to install +bsdunzip_man_MANS= unzip/bsdunzip.1 +bsdunzip_programs= bsdunzip +else +bsdunzip_man_MANS= +bsdunzip_programs= +endif + +# +# bsdcat_test +# + +bsdunzip_test_SOURCES= \ + $(test_utils_SOURCES) \ + unzip/test/test.h \ + unzip/test/test_0.c \ + unzip/test/test_basic.c \ + unzip/test/test_glob.c \ + unzip/test/test_not_exist.c \ + unzip/test/test_singlefile.c \ + unzip/test/test_C.c \ + unzip/test/test_p.c \ + unzip/test/test_d.c \ + unzip/test/test_j.c \ + unzip/test/test_L.c \ + unzip/test/test_n.c \ + unzip/test/test_o.c \ + unzip/test/test_q.c \ + unzip/test/test_t.c \ + unzip/test/test_t_bad.c \ + unzip/test/test_x.c \ + unzip/test/test_Z1.c \ + unzip/test/test_P_encryption.c + +bsdunzip_test_CPPFLAGS= \ + -I$(top_srcdir)/libarchive -I$(top_srcdir)/libarchive_fe \ + -I$(top_srcdir)/test_utils \ + -I$(top_srcdir)/unzip -I$(top_srcdir)/unzip/test \ + -I$(top_builddir)/unzip/test \ + $(PLATFORMCPPFLAGS) +bsdunzip_test_LDADD=libarchive_fe.la + +unzip/test/list.h: Makefile + cat $(top_srcdir)/unzip/test/test_*.c | grep '^DEFINE_TEST' > unzip/test/list.h + +if BUILD_BSDUNZIP +bsdunzip_test_programs= bsdunzip_test +bsdunzip_TESTS_ENVIRONMENT= BSDUNZIP=`cd $(top_builddir);/bin/pwd`/bsdunzip$(EXEEXT) BSDUNZIP_TEST_FILES=`cd $(top_srcdir);/bin/pwd`/unzip/test +else +bsdunzip_test_programs= +bsdunzip_TESTS_ENVIRONMENT= +endif + +bsdunzip_test_EXTRA_DIST= \ + unzip/test/list.h \ + unzip/test/test_basic.zip.uu \ + unzip/test/test_encrypted.zip.uu \ + unzip/test/CMakeLists.txt diff --git a/README.md b/README.md index e91893b19..727ed4985 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ This distribution bundle includes the following major components: * **tar**: the 'bsdtar' program is a full-featured 'tar' implementation built on libarchive * **cpio**: the 'bsdcpio' program is a different interface to essentially the same functionality * **cat**: the 'bsdcat' program is a simple replacement tool for zcat, bzcat, xzcat, and such +* **unzip**: the 'bsdunzip' program is a simple replacement tool for Info-ZIP's unzip * **examples**: Some small example programs that you may find useful. * **examples/minitar**: a compact sample demonstrating use of libarchive. * **contrib**: Various items sent to me by third parties; please contact the authors with any questions. diff --git a/build/cmake/config.h.in b/build/cmake/config.h.in index e4ff69691..eece471d2 100644 --- a/build/cmake/config.h.in +++ b/build/cmake/config.h.in @@ -618,6 +618,9 @@ typedef uint64_t uintmax_t; /* Define to 1 if you have the `getgrnam_r' function. */ #cmakedefine HAVE_GETGRNAM_R 1 +/* Define to 1 if platform uses `optreset` to reset `getopt` */ +#cmakedefine HAVE_GETOPT_OPTRESET 1 + /* Define to 1 if you have the `getpid' function. */ #cmakedefine HAVE_GETPID 1 diff --git a/configure.ac b/configure.ac index 24049852b..7d50c0295 100644 --- a/configure.ac +++ b/configure.ac @@ -267,6 +267,50 @@ case $host in ;; esac +# +# Options for building bsdunzip. +# +# Default is to build bsdunzip, but allow people to override that. +# +AC_ARG_ENABLE([bsdunzip], + [AS_HELP_STRING([--enable-bsdunzip], [enable build of bsdunzip (default)]) + AS_HELP_STRING([--enable-bsdunzip=static], [force static build of bsdunzip]) + AS_HELP_STRING([--enable-bsdunzip=shared], [force dynamic build of bsdunzip]) +AS_HELP_STRING([--disable-bsdunzip], [disable build of bsdunzip])], + [], [enable_bsdunzip=yes]) + +case "$enable_bsdunzip" in +yes) + if test "$enable_static" = "no"; then + static_bsdunzip=no + else + static_bsdunzip=yes + fi + build_bsdunzip=yes + ;; +dynamic|shared) + if test "$enable_shared" = "no"; then + AC_MSG_FAILURE([Shared linking of bsdunzip requires shared libarchive]) + fi + build_bsdunzip=yes + static_bsdunzip=no + ;; +static) + build_bsdunzip=yes + static_bsdunzip=yes + ;; +no) + build_bsdunzip=no + static_bsdunzip=no + ;; +*) + AC_MSG_FAILURE([Unsupported value for --enable-bsdunzip]) + ;; +esac + +AM_CONDITIONAL([BUILD_BSDUNZIP], [ test "$build_bsdunzip" = yes ]) +AM_CONDITIONAL([STATIC_BSDUNZIP], [ test "$static_bsdunzip" = yes ]) + # Checks for header files. AC_HEADER_DIRENT AC_HEADER_SYS_WAIT @@ -767,6 +811,11 @@ AC_CHECK_MEMBER(struct dirent.d_namlen,,, #endif ]) +AC_CHECK_DECL([optreset], + [AC_DEFINE(HAVE_GETOPT_OPTRESET, 1, [Platform uses optreset to reset getopt])], + [], + [#include ]) + # Check for Extended Attributes support AC_ARG_ENABLE([xattr], AS_HELP_STRING([--disable-xattr], diff --git a/unzip/CMakeLists.txt b/unzip/CMakeLists.txt new file mode 100644 index 000000000..13b983d89 --- /dev/null +++ b/unzip/CMakeLists.txt @@ -0,0 +1,37 @@ +############################################ +# +# How to build bsdunzip +# +############################################ +IF(ENABLE_UNZIP) + + SET(bsdunzip_SOURCES + bsdunzip.c + bsdunzip_platform.h + ../libarchive_fe/err.c + ../libarchive_fe/err.h + ../libarchive_fe/lafe_platform.h + ../libarchive_fe/passphrase.c + ../libarchive_fe/passphrase.h + ) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../libarchive_fe) + + # bsdunzip documentation + SET(bsdunzip_MANS bsdunzip.1) + + # How to build bsdunzip + ADD_EXECUTABLE(bsdunzip ${bsdunzip_SOURCES}) + IF(ENABLE_UNZIP_SHARED) + TARGET_LINK_LIBRARIES(bsdunzip archive ${ADDITIONAL_LIBS}) + ELSE(ENABLE_UNZIP_SHARED) + TARGET_LINK_LIBRARIES(bsdunzip archive_static ${ADDITIONAL_LIBS}) + SET_TARGET_PROPERTIES(bsdunzip PROPERTIES COMPILE_DEFINITIONS + LIBARCHIVE_STATIC) + ENDIF(ENABLE_UNZIP_SHARED) + + # Installation rules + INSTALL(TARGETS bsdunzip RUNTIME DESTINATION bin) + INSTALL_MAN(${bsdunzip_MANS}) +ENDIF(ENABLE_UNZIP) + +add_subdirectory(test) diff --git a/unzip/bsdunzip.1 b/unzip/bsdunzip.1 new file mode 100644 index 000000000..1bf76ead3 --- /dev/null +++ b/unzip/bsdunzip.1 @@ -0,0 +1,203 @@ +.\"- +.\" Copyright (c) 2007-2008 Dag-Erling Smørgrav +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd January 2, 2023 +.Dt BSDUNZIP 1 +.Os +.Sh NAME +.Nm bsdunzip +.Nd extract files from a ZIP archive +.Sh SYNOPSIS +.Nm +.Op Fl aCcfjLlnopqtuvy +.Op Fl d Ar dir +.Op Fl x Ar pattern +.Op Fl P Ar password +.Ar zipfile +.Op Ar member ... +.Sh DESCRIPTION +.\" ... +The following options are available: +.Bl -tag -width Fl +.It Fl a +When extracting a text file, convert DOS-style line endings to +Unix-style line endings. +.It Fl C +Match file names case-insensitively. +.It Fl c +Extract to stdout/screen. +When extracting files from the zipfile, they are written to stdout. +This is similar to +.Fl p , +but does not suppress normal output. +.It Fl d Ar dir +Extract files into the specified directory rather than the current +directory. +.It Fl f +Update existing. +Extract only files from the zipfile if a file with the same name +already exists on disk and is older than the former. +Otherwise, the file is silently skipped. +.It Fl j +Ignore directories stored in the zipfile; instead, extract all files +directly into the extraction directory. +.It Fl L +Convert the names of the extracted files and directories to lowercase. +.It Fl l +List, rather than extract, the contents of the zipfile. +.It Fl n +No overwrite. +When extracting a file from the zipfile, if a file with the same name +already exists on disk, the file is silently skipped. +.It Fl o +Overwrite. +When extracting a file from the zipfile, if a file with the same name +already exists on disk, the existing file is replaced with the file +from the zipfile. +.It Fl p +Extract to stdout. +When extracting files from the zipfile, they are written to stdout. +The normal output is suppressed as if +.Fl q +was specified. +.It Fl P Ar password +Extract encrypted files using a password. +Putting a password on the command line using this option can be +insecure. +.It Fl q +Quiet: print less information while extracting. +.It Fl t +Test: do not extract anything, but verify the checksum of every file +in the archive. +.It Fl u +Update. +When extracting a file from the zipfile, if a file with the same name +already exists on disk, the existing file is replaced with the file +from the zipfile if and only if the latter is newer than the former. +Otherwise, the file is silently skipped. +.It Fl v +List verbosely, rather than extract, the contents of the zipfile. +This differs from +.Fl l +by using the long listing. +Note that most of the data is currently fake and does not reflect the +content of the archive. +.It Fl x Ar pattern +Exclude files matching the pattern +.Ar pattern . +.It Fl y +Print four digit years in listings instead of two. +.It Fl Z Ar mode +Emulate +.Xr zipinfo 1L +mode. +Enabling +.Xr zipinfo 1L +mode changes the way in which additional arguments are parsed. +Currently only +.Xr zipinfo 1L +mode 1 is supported, which lists the file names one per line. +.It Ar [member ...] +Optional list of members to extract from the zipfile. +Can include patterns, e.g. +.Ar 'memberdir/*' +will extract all files and dirs below memberdir. +.El +.Pp +Note that only one of +.Fl n , +.Fl o , +and +.Fl u +may be specified. +If specified filename is +.Qq - , +then data is read from +.Va stdin . +.Sh ENVIRONMENT +If the +.Ev UNZIP_DEBUG +environment variable is defined, the +.Fl q +command-line option has no effect, and additional debugging +information will be printed to +.Va stderr . +.Sh COMPATIBILITY +The +.Nm +utility aims to be sufficiently compatible with other implementations +to serve as a drop-in replacement in the context of the +.Xr ports 7 +system. +No attempt has been made to replicate functionality which is not +required for that purpose. +.Pp +For compatibility reasons, command-line options will be recognized if +they are listed not only before but also after the name of the +zipfile. +.Pp +Normally, the +.Fl a +option should only affect files which are marked as text files in the +zipfile's central directory. +Since the +.Xr archive 3 +library does not provide access to that information, it is not available +to the +.Nm +utility. +Instead, the +.Nm +utility will assume that a file is a text file if no non-ASCII +characters are present within the first block of data decompressed for +that file. +If non-ASCII characters appear in subsequent blocks of data, a warning +will be issued. +.Pp +The +.Nm +utility is only able to process ZIP archives handled by +.Xr libarchive 3 . +Depending on the installed version of +.Xr libarchive 3 , +this may or may not include self-extracting or ZIPX archives. +.Sh SEE ALSO +.Xr libarchive 3 +.Sh HISTORY +The +.Nm +utility appeared in +.Fx 8.0 . +.Sh AUTHORS +The +.Nm +utility and this manual page were written by +.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org . +It uses the +.Xr archive 3 +library developed by +.An Tim Kientzle Aq Mt kientzle@FreeBSD.org . diff --git a/unzip/bsdunzip.c b/unzip/bsdunzip.c new file mode 100644 index 000000000..1d9a99b51 --- /dev/null +++ b/unzip/bsdunzip.c @@ -0,0 +1,1153 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2009, 2010 Joerg Sonnenberger + * Copyright (c) 2007-2008 Dag-Erling Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + * This file would be much shorter if we didn't care about command-line + * compatibility with Info-ZIP's UnZip, which requires us to duplicate + * parts of libarchive in order to gain more detailed control of its + * behaviour for the purpose of implementing the -n, -o, -L and -a + * options. + */ + +#include "bsdunzip_platform.h" + +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef HAVE_CTYPE_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#include +#ifdef HAVE_STDARG_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include "passphrase.h" +#include "err.h" + +/* command-line options */ +static int a_opt; /* convert EOL */ +static int C_opt; /* match case-insensitively */ +static int c_opt; /* extract to stdout */ +static const char *d_arg; /* directory */ +static int f_opt; /* update existing files only */ +static int j_opt; /* junk directories */ +static int L_opt; /* lowercase names */ +static int n_opt; /* never overwrite */ +static int o_opt; /* always overwrite */ +static int p_opt; /* extract to stdout, quiet */ +static char *P_arg; /* passphrase */ +static int q_opt; /* quiet */ +static int t_opt; /* test */ +static int u_opt; /* update */ +static int v_opt; /* verbose/list */ +static const char *y_str = ""; /* 4 digit year */ +static int Z1_opt; /* zipinfo mode list files only */ + +/* debug flag */ +static int unzip_debug; + +/* zipinfo mode */ +static int zipinfo_mode; + +/* running on tty? */ +static int tty; + +/* convenience macro */ +/* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */ +#define ac(call) \ + do { \ + int acret = (call); \ + if (acret != ARCHIVE_OK) \ + errorx("%s", archive_error_string(a)); \ + } while (0) + +/* + * Indicates that last info() did not end with EOL. This helps error() et + * al. avoid printing an error message on the same line as an incomplete + * informational message. + */ +static int noeol; + +/* for an interactive passphrase input */ +static char *passphrase_buf; + +/* fatal error message + errno */ +static void +error(const char *fmt, ...) +{ + va_list ap; + + if (noeol) + fprintf(stdout, "\n"); + fflush(stdout); + fprintf(stderr, "unzip: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errno)); + exit(EXIT_FAILURE); +} + +/* fatal error message, no errno */ +static void +errorx(const char *fmt, ...) +{ + va_list ap; + + if (noeol) + fprintf(stdout, "\n"); + fflush(stdout); + fprintf(stderr, "unzip: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(EXIT_FAILURE); +} + +/* non-fatal error message + errno */ +static void +warning(const char *fmt, ...) +{ + va_list ap; + + if (noeol) + fprintf(stdout, "\n"); + fflush(stdout); + fprintf(stderr, "unzip: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errno)); +} + +/* non-fatal error message, no errno */ +static void +warningx(const char *fmt, ...) +{ + va_list ap; + + if (noeol) + fprintf(stdout, "\n"); + fflush(stdout); + fprintf(stderr, "unzip: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +/* informational message (if not -q) */ +static void +info(const char *fmt, ...) +{ + va_list ap; + + if (q_opt && !unzip_debug) + return; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); + + if (*fmt == '\0') + noeol = 1; + else + noeol = fmt[strlen(fmt) - 1] != '\n'; +} + +/* debug message (if unzip_debug) */ +static void +debug(const char *fmt, ...) +{ + va_list ap; + + if (!unzip_debug) + return; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); + + if (*fmt == '\0') + noeol = 1; + else + noeol = fmt[strlen(fmt) - 1] != '\n'; +} + +/* duplicate a path name, possibly converting to lower case */ +static char * +pathdup(const char *path) +{ + char *str; + size_t i, len; + + if (path == NULL || path[0] == '\0') + return (NULL); + + len = strlen(path); + while (len && path[len - 1] == '/') + len--; + if ((str = malloc(len + 1)) == NULL) { + errno = ENOMEM; + error("malloc()"); + } + if (L_opt) { + for (i = 0; i < len; ++i) + str[i] = tolower((unsigned char)path[i]); + } else { + memcpy(str, path, len); + } + str[len] = '\0'; + + return (str); +} + +/* concatenate two path names */ +static char * +pathcat(const char *prefix, const char *path) +{ + char *str; + size_t prelen, len; + + prelen = prefix ? strlen(prefix) + 1 : 0; + len = strlen(path) + 1; + if ((str = malloc(prelen + len)) == NULL) { + errno = ENOMEM; + error("malloc()"); + } + if (prefix) { + memcpy(str, prefix, prelen); /* includes zero */ + str[prelen - 1] = '/'; /* splat zero */ + } + memcpy(str + prelen, path, len); /* includes zero */ + + return (str); +} + +/* + * Pattern lists for include / exclude processing + */ +struct pattern { + STAILQ_ENTRY(pattern) link; + char pattern[]; +}; + +STAILQ_HEAD(pattern_list, pattern); +static struct pattern_list include = STAILQ_HEAD_INITIALIZER(include); +static struct pattern_list exclude = STAILQ_HEAD_INITIALIZER(exclude); + +/* + * Add an entry to a pattern list + */ +static void +add_pattern(struct pattern_list *list, const char *pattern) +{ + struct pattern *entry; + size_t len; + + debug("adding pattern '%s'\n", pattern); + len = strlen(pattern); + if ((entry = malloc(sizeof *entry + len + 1)) == NULL) { + errno = ENOMEM; + error("malloc()"); + } + memcpy(entry->pattern, pattern, len + 1); + STAILQ_INSERT_TAIL(list, entry, link); +} + +/* + * Match a string against a list of patterns + */ +static int +match_pattern(struct pattern_list *list, const char *str) +{ + struct pattern *entry; + + STAILQ_FOREACH(entry, list, link) { + if (fnmatch(entry->pattern, str, C_opt ? FNM_CASEFOLD : 0) == 0) + return (1); + } + return (0); +} + +/* + * Verify that a given pathname is in the include list and not in the + * exclude list. + */ +static int +accept_pathname(const char *pathname) +{ + + if (!STAILQ_EMPTY(&include) && !match_pattern(&include, pathname)) + return (0); + if (!STAILQ_EMPTY(&exclude) && match_pattern(&exclude, pathname)) + return (0); + return (1); +} + +/* + * Create the specified directory with the specified mode, taking certain + * precautions on they way. + */ +static void +make_dir(const char *path, int mode) +{ + struct stat sb; + + if (lstat(path, &sb) == 0) { + if (S_ISDIR(sb.st_mode)) + return; + /* + * Normally, we should either ask the user about removing + * the non-directory of the same name as a directory we + * wish to create, or respect the -n or -o command-line + * options. However, this may lead to a later failure or + * even compromise (if this non-directory happens to be a + * symlink to somewhere unsafe), so we don't. + */ + + /* + * Don't check unlink() result; failure will cause mkdir() + * to fail later, which we will catch. + */ + (void)unlink(path); + } + if (mkdir(path, mode) != 0 && errno != EEXIST) + error("mkdir('%s')", path); +} + +/* + * Ensure that all directories leading up to (but not including) the + * specified path exist. + * + * XXX inefficient + modifies the file in-place + */ +static void +make_parent(char *path) +{ + struct stat sb; + char *sep; + + sep = strrchr(path, '/'); + if (sep == NULL || sep == path) + return; + *sep = '\0'; + if (lstat(path, &sb) == 0) { + if (S_ISDIR(sb.st_mode)) { + *sep = '/'; + return; + } + unlink(path); + } + make_parent(path); + mkdir(path, 0755); + *sep = '/'; + +#if 0 + for (sep = path; (sep = strchr(sep, '/')) != NULL; sep++) { + /* root in case of absolute d_arg */ + if (sep == path) + continue; + *sep = '\0'; + make_dir(path, 0755); + *sep = '/'; + } +#endif +} + +/* + * Extract a directory. + */ +static void +extract_dir(struct archive *a, struct archive_entry *e, const char *path) +{ + int mode; + + /* + * Dropbox likes to create '/' directory entries, just ignore + * such junk. + */ + if (*path == '\0') + return; + + mode = archive_entry_mode(e) & 0777; + if (mode == 0) + mode = 0755; + + /* + * Some zipfiles contain directories with weird permissions such + * as 0644 or 0444. This can cause strange issues such as being + * unable to extract files into the directory we just created, or + * the user being unable to remove the directory later without + * first manually changing its permissions. Therefore, we whack + * the permissions into shape, assuming that the user wants full + * access and that anyone who gets read access also gets execute + * access. + */ + mode |= 0700; + if (mode & 0040) + mode |= 0010; + if (mode & 0004) + mode |= 0001; + + info(" creating: %s/\n", path); + make_dir(path, mode); + ac(archive_read_data_skip(a)); +} + +static unsigned char buffer[8192]; +static char spinner[] = { '|', '/', '-', '\\' }; + +static int +handle_existing_file(char **path) +{ + size_t alen; + ssize_t len; + char buf[4]; + + for (;;) { + fprintf(stderr, + "replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", + *path); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + clearerr(stdin); + printf("NULL\n(EOF or read error, " + "treating as \"[N]one\"...)\n"); + n_opt = 1; + return -1; + } + switch (*buf) { + case 'A': + o_opt = 1; + /* FALLTHROUGH */ + case 'y': + case 'Y': + (void)unlink(*path); + return 1; + case 'N': + n_opt = 1; + /* FALLTHROUGH */ + case 'n': + return -1; + case 'r': + case 'R': + printf("New name: "); + fflush(stdout); + free(*path); + *path = NULL; + alen = 0; + len = getline(path, &alen, stdin); + if ((*path)[len - 1] == '\n') + (*path)[len - 1] = '\0'; + return 0; + default: + break; + } + } +} + +/* + * Detect binary files by a combination of character white list and + * black list. NUL bytes and other control codes without use in text files + * result directly in switching the file to binary mode. Otherwise, at least + * one white-listed byte has to be found. + * + * Black-listed: 0..6, 14..25, 28..31 + * 0xf3ffc07f = 11110011111111111100000001111111b + * White-listed: 9..10, 13, >= 32 + * 0x00002600 = 00000000000000000010011000000000b + * + * See the proginfo/txtvsbin.txt in the zip sources for a detailed discussion. + */ +#define BYTE_IS_BINARY(x) ((x) < 32 && (0xf3ffc07fU & (1U << (x)))) +#define BYTE_IS_TEXT(x) ((x) >= 32 || (0x00002600U & (1U << (x)))) + +static int +check_binary(const unsigned char *buf, size_t len) +{ + int rv; + for (rv = 1; len--; ++buf) { + if (BYTE_IS_BINARY(*buf)) + return 1; + if (BYTE_IS_TEXT(*buf)) + rv = 0; + } + + return rv; +} + +/* + * Extract to a file descriptor + */ +static int +extract2fd(struct archive *a, char *pathname, int fd) +{ + int cr, text, warn; + ssize_t len; + unsigned char *p, *q, *end; + + text = a_opt; + warn = 0; + cr = 0; + + /* loop over file contents and write to fd */ + for (int n = 0; ; n++) { + if (fd != STDOUT_FILENO) + if (tty && (n % 4) == 0) + info(" %c\b\b", spinner[(n / 4) % sizeof spinner]); + + len = archive_read_data(a, buffer, sizeof buffer); + + if (len < 0) + ac(len); + + /* left over CR from previous buffer */ + if (a_opt && cr) { + if (len == 0 || buffer[0] != '\n') + if (write(fd, "\r", 1) != 1) + error("write('%s')", pathname); + cr = 0; + } + + /* EOF */ + if (len == 0) + break; + end = buffer + len; + + /* + * Detect whether this is a text file. The correct way to + * do this is to check the least significant bit of the + * "internal file attributes" field of the corresponding + * file header in the central directory, but libarchive + * does not provide access to this field, so we have to + * guess by looking for non-ASCII characters in the + * buffer. Hopefully we won't guess wrong. If we do + * guess wrong, we print a warning message later. + */ + if (a_opt && n == 0) { + if (check_binary(buffer, len)) + text = 0; + } + + /* simple case */ + if (!a_opt || !text) { + if (write(fd, buffer, len) != len) + error("write('%s')", pathname); + continue; + } + + /* hard case: convert \r\n to \n (sigh...) */ + for (p = buffer; p < end; p = q + 1) { + for (q = p; q < end; q++) { + if (!warn && BYTE_IS_BINARY(*q)) { + warningx("%s may be corrupted due" + " to weak text file detection" + " heuristic", pathname); + warn = 1; + } + if (q[0] != '\r') + continue; + if (&q[1] == end) { + cr = 1; + break; + } + if (q[1] == '\n') + break; + } + if (write(fd, p, q - p) != q - p) + error("write('%s')", pathname); + } + } + + return text; +} + +/* + * Extract a regular file. + */ +static void +extract_file(struct archive *a, struct archive_entry *e, char **path) +{ + int mode; + struct timespec mtime; + struct stat sb; + struct timespec ts[2]; + int fd, check, text; + const char *linkname; + + mode = archive_entry_mode(e) & 0777; + if (mode == 0) + mode = 0644; + mtime.tv_sec = archive_entry_mtime(e); + mtime.tv_nsec = archive_entry_mtime_nsec(e); + + /* look for existing file of same name */ +recheck: + if (lstat(*path, &sb) == 0) { + if (u_opt || f_opt) { + /* check if up-to-date */ + if (S_ISREG(sb.st_mode) && + (sb.st_mtim.tv_sec > mtime.tv_sec || + (sb.st_mtim.tv_sec == mtime.tv_sec && + sb.st_mtim.tv_nsec >= mtime.tv_nsec))) + return; + (void)unlink(*path); + } else if (o_opt) { + /* overwrite */ + (void)unlink(*path); + } else if (n_opt) { + /* do not overwrite */ + return; + } else { + check = handle_existing_file(path); + if (check == 0) + goto recheck; + if (check == -1) + return; /* do not overwrite */ + } + } else { + if (f_opt) + return; + } + + ts[0].tv_sec = 0; + ts[0].tv_nsec = UTIME_NOW; + ts[1] = mtime; + + /* process symlinks */ + linkname = archive_entry_symlink(e); + if (linkname != NULL) { + if (symlink(linkname, *path) != 0) + error("symlink('%s')", *path); + info(" extracting: %s -> %s\n", *path, linkname); + if (lchmod(*path, mode) != 0) + warning("Cannot set mode for '%s'", *path); + /* set access and modification time */ + if (utimensat(AT_FDCWD, *path, ts, AT_SYMLINK_NOFOLLOW) != 0) + warning("utimensat('%s')", *path); + return; + } + + if ((fd = open(*path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0) + error("open('%s')", *path); + + info(" extracting: %s", *path); + + text = extract2fd(a, *path, fd); + + if (tty) + info(" \b\b"); + if (text) + info(" (text)"); + info("\n"); + + /* set access and modification time */ + if (futimens(fd, ts) != 0) + error("futimens('%s')", *path); + if (close(fd) != 0) + error("close('%s')", *path); +} + +/* + * Extract a zipfile entry: first perform some sanity checks to ensure + * that it is either a directory or a regular file and that the path is + * not absolute and does not try to break out of the current directory; + * then call either extract_dir() or extract_file() as appropriate. + * + * This is complicated a bit by the various ways in which we need to + * manipulate the path name. Case conversion (if requested by the -L + * option) happens first, but the include / exclude patterns are applied + * to the full converted path name, before the directory part of the path + * is removed in accordance with the -j option. Sanity checks are + * intentionally done earlier than they need to be, so the user will get a + * warning about insecure paths even for files or directories which + * wouldn't be extracted anyway. + */ +static void +extract(struct archive *a, struct archive_entry *e) +{ + char *pathname, *realpathname; + mode_t filetype; + char *p, *q; + + if ((pathname = pathdup(archive_entry_pathname(e))) == NULL) { + warningx("skipping empty or unreadable filename entry"); + ac(archive_read_data_skip(a)); + return; + } + filetype = archive_entry_filetype(e); + + /* sanity checks */ + if (pathname[0] == '/' || + strncmp(pathname, "../", 3) == 0 || + strstr(pathname, "/../") != NULL) { + warningx("skipping insecure entry '%s'", pathname); + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* I don't think this can happen in a zipfile.. */ + if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) { + warningx("skipping non-regular entry '%s'", pathname); + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* skip directories in -j case */ + if (S_ISDIR(filetype) && j_opt) { + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* apply include / exclude patterns */ + if (!accept_pathname(pathname)) { + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* apply -j and -d */ + if (j_opt) { + for (p = q = pathname; *p; ++p) + if (*p == '/') + q = p + 1; + realpathname = pathcat(d_arg, q); + } else { + realpathname = pathcat(d_arg, pathname); + } + + /* ensure that parent directory exists */ + make_parent(realpathname); + + if (S_ISDIR(filetype)) + extract_dir(a, e, realpathname); + else + extract_file(a, e, &realpathname); + + free(realpathname); + free(pathname); +} + +static void +extract_stdout(struct archive *a, struct archive_entry *e) +{ + char *pathname; + mode_t filetype; + + if ((pathname = pathdup(archive_entry_pathname(e))) == NULL) { + warningx("skipping empty or unreadable filename entry"); + ac(archive_read_data_skip(a)); + return; + } + filetype = archive_entry_filetype(e); + + /* I don't think this can happen in a zipfile.. */ + if (!S_ISDIR(filetype) && !S_ISREG(filetype) && !S_ISLNK(filetype)) { + warningx("skipping non-regular entry '%s'", pathname); + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* skip directories in -j case */ + if (S_ISDIR(filetype)) { + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* apply include / exclude patterns */ + if (!accept_pathname(pathname)) { + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + if (c_opt) + info("x %s\n", pathname); + + (void)extract2fd(a, pathname, STDOUT_FILENO); + + free(pathname); +} + +/* + * Print the name of an entry to stdout. + */ +static void +list(struct archive *a, struct archive_entry *e) +{ + char buf[20]; + time_t mtime; + struct tm *tm; + + mtime = archive_entry_mtime(e); + tm = localtime(&mtime); + if (*y_str) + strftime(buf, sizeof(buf), "%m-%d-%G %R", tm); + else + strftime(buf, sizeof(buf), "%m-%d-%g %R", tm); + + if (!zipinfo_mode) { + if (v_opt == 1) { + printf(" %8ju %s %s\n", + (uintmax_t)archive_entry_size(e), + buf, archive_entry_pathname(e)); + } else if (v_opt == 2) { + printf("%8ju Stored %7ju 0%% %s %08x %s\n", + (uintmax_t)archive_entry_size(e), + (uintmax_t)archive_entry_size(e), + buf, + 0U, + archive_entry_pathname(e)); + } + } else { + if (Z1_opt) + printf("%s\n",archive_entry_pathname(e)); + } + ac(archive_read_data_skip(a)); +} + +/* + * Extract to memory to check CRC + */ +static int +test(struct archive *a, struct archive_entry *e) +{ + ssize_t len; + int error_count; + + error_count = 0; + if (S_ISDIR(archive_entry_filetype(e))) + return 0; + + info(" testing: %s\t", archive_entry_pathname(e)); + while ((len = archive_read_data(a, buffer, sizeof buffer)) > 0) + /* nothing */; + if (len < 0) { + info(" %s\n", archive_error_string(a)); + ++error_count; + } else { + info(" OK\n"); + } + + /* shouldn't be necessary, but it doesn't hurt */ + ac(archive_read_data_skip(a)); + + return error_count; +} + +/* + * Callback function for reading passphrase. + * Originally from cpio.c and passphrase.c, libarchive. + */ +#define PPBUFF_SIZE 1024 +static const char * +passphrase_callback(struct archive *a, void *_client_data) +{ + char *p; + + (void)a; /* UNUSED */ + (void)_client_data; /* UNUSED */ + + if (passphrase_buf == NULL) { + passphrase_buf = malloc(PPBUFF_SIZE); + if (passphrase_buf == NULL) { + errno = ENOMEM; + error("malloc()"); + } + } + + p = lafe_readpassphrase("\nEnter password: ", passphrase_buf, + PPBUFF_SIZE); + + if (p == NULL && errno != EINTR) + error("Error reading password"); + + return p; +} + +/* + * Main loop: open the zipfile, iterate over its contents and decide what + * to do with each entry. + */ +static void +unzip(const char *fn) +{ + struct archive *a; + struct archive_entry *e; + int ret; + uintmax_t total_size, file_count, error_count; + + if ((a = archive_read_new()) == NULL) + error("archive_read_new failed"); + + ac(archive_read_support_format_zip(a)); + + if (P_arg) + archive_read_add_passphrase(a, P_arg); + else + archive_read_set_passphrase_callback(a, NULL, + &passphrase_callback); + + ac(archive_read_open_filename(a, fn, 8192)); + + if (!zipinfo_mode) { + if (!p_opt && !q_opt) + printf("Archive: %s\n", fn); + if (v_opt == 1) { + printf(" Length %sDate Time Name\n", y_str); + printf(" -------- %s---- ---- ----\n", y_str); + } else if (v_opt == 2) { + printf(" Length Method Size Ratio %sDate Time CRC-32 Name\n", y_str); + printf("-------- ------ ------- ----- %s---- ---- ------ ----\n", y_str); + } + } + + total_size = 0; + file_count = 0; + error_count = 0; + for (;;) { + ret = archive_read_next_header(a, &e); + if (ret == ARCHIVE_EOF) + break; + ac(ret); + if (!zipinfo_mode) { + if (t_opt) + error_count += test(a, e); + else if (v_opt) + list(a, e); + else if (p_opt || c_opt) + extract_stdout(a, e); + else + extract(a, e); + } else { + if (Z1_opt) + list(a, e); + } + + total_size += archive_entry_size(e); + ++file_count; + } + + if (zipinfo_mode) { + if (v_opt == 1) { + printf(" -------- %s-------\n", y_str); + printf(" %8ju %s%ju file%s\n", + total_size, y_str, file_count, file_count != 1 ? "s" : ""); + } else if (v_opt == 2) { + printf("-------- ------- --- %s-------\n", y_str); + printf("%8ju %7ju 0%% %s%ju file%s\n", + total_size, total_size, y_str, file_count, + file_count != 1 ? "s" : ""); + } + } + + ac(archive_read_free(a)); + + if (passphrase_buf != NULL) { + memset(passphrase_buf, 0, PPBUFF_SIZE); + free(passphrase_buf); + } + + if (t_opt) { + if (error_count > 0) { + errorx("%ju checksum error(s) found.", error_count); + } + else { + printf("No errors detected in compressed data of %s.\n", + fn); + } + } +} + +static void +usage(void) +{ + + fprintf(stderr, +"Usage: unzip [-aCcfjLlnopqtuvyZ1] [-d dir] [-x pattern] [-P password] zipfile\n" +" [member ...]\n"); + exit(EXIT_FAILURE); +} + +static int +getopts(int argc, char *argv[]) +{ + int opt; + + optreset = optind = 1; + while ((opt = getopt(argc, argv, "aCcd:fjLlnopP:qtuvx:yZ1")) != -1) + switch (opt) { + case '1': + Z1_opt = 1; + break; + case 'a': + a_opt = 1; + break; + case 'C': + C_opt = 1; + break; + case 'c': + c_opt = 1; + break; + case 'd': + d_arg = optarg; + break; + case 'f': + f_opt = 1; + break; + case 'j': + j_opt = 1; + break; + case 'L': + L_opt = 1; + break; + case 'l': + if (v_opt == 0) + v_opt = 1; + break; + case 'n': + n_opt = 1; + break; + case 'o': + o_opt = 1; + q_opt = 1; + break; + case 'p': + p_opt = 1; + break; + case 'P': + P_arg = optarg; + break; + case 'q': + q_opt = 1; + break; + case 't': + t_opt = 1; + break; + case 'u': + u_opt = 1; + break; + case 'v': + v_opt = 2; + break; + case 'x': + add_pattern(&exclude, optarg); + break; + case 'y': + y_str = " "; + break; + case 'Z': + zipinfo_mode = 1; + break; + default: + usage(); + } + + return (optind); +} + +int +main(int argc, char *argv[]) +{ + const char *zipfile; + int nopts; + + if (isatty(STDOUT_FILENO)) + tty = 1; + + if (getenv("UNZIP_DEBUG") != NULL) + unzip_debug = 1; + for (int i = 0; i < argc; ++i) + debug("%s%c", argv[i], (i < argc - 1) ? ' ' : '\n'); + + /* + * Info-ZIP's unzip(1) expects certain options to come before the + * zipfile name, and others to come after - though it does not + * enforce this. For simplicity, we accept *all* options both + * before and after the zipfile name. + */ + nopts = getopts(argc, argv); + + /* + * When more of the zipinfo mode options are implemented, this + * will need to change. + */ + if (zipinfo_mode && !Z1_opt) { + printf("Zipinfo mode needs additional options\n"); + exit(EXIT_FAILURE); + } + + if (argc <= nopts) + usage(); + zipfile = argv[nopts++]; + + if (strcmp(zipfile, "-") == 0) + zipfile = NULL; /* STDIN */ + + while (nopts < argc && *argv[nopts] != '-') + add_pattern(&include, argv[nopts++]); + + nopts--; /* fake argv[0] */ + nopts += getopts(argc - nopts, argv + nopts); + + if (n_opt + o_opt + u_opt > 1) + errorx("-n, -o and -u are contradictory"); + + unzip(zipfile); + + exit(EXIT_SUCCESS); +} diff --git a/unzip/bsdunzip_platform.h b/unzip/bsdunzip_platform.h new file mode 100644 index 000000000..5aff5f208 --- /dev/null +++ b/unzip/bsdunzip_platform.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD: src/usr.bin/tar/bsdtar_platform.h,v 1.26 2008/12/06 07:37:14 kientzle Exp $ + */ + +/* + * This header is the first thing included in any of the bsdtar + * source files. As far as possible, platform-specific issues should + * be dealt with here and not within individual source files. + */ + +#ifndef BSDUNZIP_PLATFORM_H_INCLUDED +#define BSDUNZIP_PLATFORM_H_INCLUDED + +#if defined(PLATFORM_CONFIG_H) +/* Use hand-built config.h in environments that need it. */ +#include PLATFORM_CONFIG_H +#else +/* Not having a config.h of some sort is a serious problem. */ +#include "config.h" +#endif + +/* Get a real definition for __FBSDID if we can */ +#if HAVE_SYS_CDEFS_H +#include +#endif + +/* If not, define it so as to avoid dangling semicolons. */ +#ifndef __FBSDID +#define __FBSDID(a) struct _undefined_hack +#endif + +#ifdef HAVE_LIBARCHIVE +/* If we're using the platform libarchive, include system headers. */ +#include +#include +#else +/* Otherwise, include user headers. */ +#include "archive.h" +#include "archive_entry.h" +#endif + +#ifndef HAVE_GETOPT_OPTRESET +/* + * If platform doesn't use optreset for resetting getopt, declare it so + * C source doesn't have to know this platform-specific difference + */ +int optreset; +#endif + +/* How to mark functions that don't return. */ +/* This facilitates use of some newer static code analysis tools. */ +#undef __LA_DEAD +#if defined(__GNUC__) && (__GNUC__ > 2 || \ + (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) +#define __LA_DEAD __attribute__((__noreturn__)) +#else +#define __LA_DEAD +#endif + +#endif /* !BSDUNZIP_PLATFORM_H_INCLUDED */ diff --git a/unzip/test/CMakeLists.txt b/unzip/test/CMakeLists.txt new file mode 100644 index 000000000..ad95f7a5c --- /dev/null +++ b/unzip/test/CMakeLists.txt @@ -0,0 +1,70 @@ +############################################ +# +# How to build bsdunzip_test +# +############################################ +IF(ENABLE_UNZIP AND ENABLE_TEST) + SET(bsdunzip_test_SOURCES + ../../libarchive_fe/err.c + ../../test_utils/test_utils.c + ../../test_utils/test_main.c + test.h + test_0.c + test_basic.c + test_glob.c + test_singlefile.c + test_C.c + test_p.c + test_d.c + test_j.c + test_L.c + test_n.c + test_o.c + test_q.c + test_t.c + test_t_bad.c + test_x.c + test_Z1.c + test_P_encryption.c + ) + + # + # Register target + # + ADD_EXECUTABLE(bsdunzip_test ${bsdunzip_test_SOURCES}) + SET_PROPERTY(TARGET bsdunzip_test PROPERTY COMPILE_DEFINITIONS LIST_H) + + # + # Generate list.h by grepping DEFINE_TEST() lines out of the C sources. + # + GENERATE_LIST_H(${CMAKE_CURRENT_BINARY_DIR}/list.h + ${CMAKE_CURRENT_LIST_FILE} ${bsdunzip_test_SOURCES}) + SET_PROPERTY(DIRECTORY APPEND PROPERTY INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_BINARY_DIR}) + + # list.h has a line DEFINE_TEST(testname) for every + # test. We can use that to define the tests for cmake by + # defining a DEFINE_TEST macro and reading list.h in. + MACRO (DEFINE_TEST _testname) + ADD_TEST( + NAME bsdunzip_${_testname} + COMMAND bsdunzip_test -vv + -p $ + -r ${CMAKE_CURRENT_SOURCE_DIR} + ${_testname}) + ENDMACRO (DEFINE_TEST _testname) + + INCLUDE(${CMAKE_CURRENT_BINARY_DIR}/list.h) + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) + INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/test_utils) + INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/unzip/test) + + # Experimental new test handling + ADD_CUSTOM_TARGET(run_bsdunzip_test + COMMAND bsdunzip_test -p $ + -r ${CMAKE_CURRENT_SOURCE_DIR} + -vv) + ADD_DEPENDENCIES(run_bsdunzip_test bsdunzip) + ADD_DEPENDENCIES(run_all_tests run_bsdunzip_test) +ENDIF(ENABLE_UNZIP AND ENABLE_TEST) + diff --git a/unzip/test/test.h b/unzip/test/test.h new file mode 100644 index 000000000..8df106e12 --- /dev/null +++ b/unzip/test/test.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2003-2017 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* Every test program should #include "test.h" as the first thing. */ + +#define KNOWNREF "test_basic.zip.uu" +#define ENVBASE "BSDUNZIP" /* Prefix for environment variables. */ +#define PROGRAM "bsdunzip" /* Name of program being tested. */ +#define PROGRAM_ALIAS "unzip" /* Generic alias for program */ +#undef LIBRARY /* Not testing a library. */ +#undef EXTRA_DUMP /* How to dump extra data */ +#undef EXTRA_ERRNO /* How to dump errno */ +/* How to generate extra version info. */ +#define EXTRA_VERSION (system(testprog) ? "" : "") + +#include "test_common.h" diff --git a/unzip/test/test_0.c b/unzip/test/test_0.c new file mode 100644 index 000000000..41279d388 --- /dev/null +++ b/unzip/test/test_0.c @@ -0,0 +1,58 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* + * This first test does basic sanity checks on the environment. For + * most of these, we just exit on failure. + */ +#if !defined(_WIN32) || defined(__CYGWIN__) +#define DEV_NULL "/dev/null" +#else +#define DEV_NULL "NUL" +#endif + +DEFINE_TEST(test_0) +{ + struct stat st; + + failure("File %s does not exist?!", testprog); + if (!assertEqualInt(0, stat(testprogfile, &st))) { + fprintf(stderr, + "\nFile %s does not exist; aborting test.\n\n", + testprog); + exit(1); + } + + failure("%s is not executable?!", testprog); + if (!assert((st.st_mode & 0111) != 0)) { + fprintf(stderr, + "\nFile %s not executable; aborting test.\n\n", + testprog); + exit(1); + } + + /* TODO: Ensure that our reference files are available. */ +} diff --git a/unzip/test/test_C.c b/unzip/test/test_C.c new file mode 100644 index 000000000..fc11b970c --- /dev/null +++ b/unzip/test/test_C.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test C arg - match case-insensitive */ +DEFINE_TEST(test_C) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -C %s test_basic/caps >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents CAPS\n", "test_basic/CAPS"); +} diff --git a/unzip/test/test_L.c b/unzip/test/test_L.c new file mode 100644 index 000000000..4815cb2b1 --- /dev/null +++ b/unzip/test/test_L.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test L arg - make names lowercase */ +DEFINE_TEST(test_L) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -L %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "test_basic/a"); + assertTextFileContents("contents b\n", "test_basic/b"); + assertTextFileContents("contents c\n", "test_basic/c"); + assertTextFileContents("contents CAPS\n", "test_basic/caps"); +} diff --git a/unzip/test/test_P_encryption.c b/unzip/test/test_P_encryption.c new file mode 100644 index 000000000..beabbaa64 --- /dev/null +++ b/unzip/test/test_P_encryption.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test P arg - password protected */ +DEFINE_TEST(test_P) +{ + const char *reffile = "test_encrypted.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -P password %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("plaintext\n", "encrypted/file.txt"); +} diff --git a/unzip/test/test_Z1.c b/unzip/test/test_Z1.c new file mode 100644 index 000000000..58dc75003 --- /dev/null +++ b/unzip/test/test_Z1.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test -Z1 arg - List filenames */ +DEFINE_TEST(test_Z1) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -Z1 %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertTextFileContents("test_basic/\ntest_basic/a\ntest_basic/b\ntest_basic/c\ntest_basic/CAPS\n", "test.out"); + assertEmptyFile("test.err"); +} diff --git a/unzip/test/test_basic.c b/unzip/test/test_basic.c new file mode 100644 index 000000000..e997755e7 --- /dev/null +++ b/unzip/test/test_basic.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* This test just does a basic zip decompression */ +DEFINE_TEST(test_basic) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "test_basic/a"); + assertTextFileContents("contents b\n", "test_basic/b"); + assertTextFileContents("contents c\n", "test_basic/c"); + assertTextFileContents("contents CAPS\n", "test_basic/CAPS"); +} diff --git a/unzip/test/test_basic.zip.uu b/unzip/test/test_basic.zip.uu new file mode 100644 index 000000000..b55aca950 --- /dev/null +++ b/unzip/test/test_basic.zip.uu @@ -0,0 +1,25 @@ +begin 644 test_basic.zip +M4$L#!!0``````,J0MU8````````````````+`"``=&5S=%]B87-I8R]55`T` +M!]PX;63U.6UDW#AM9'5X"P`!!.@#```$Z`,``%!+`P04``@`"``)C;=6```` +M```````+````#``@`'1E`L``03H`P``!.@#``!+SL\K2test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "foobar/test_basic/a"); + assertTextFileContents("contents b\n", "foobar/test_basic/b"); + assertTextFileContents("contents c\n", "foobar/test_basic/c"); + assertTextFileContents("contents CAPS\n", "foobar/test_basic/CAPS"); +} diff --git a/unzip/test/test_encrypted.zip.uu b/unzip/test/test_encrypted.zip.uu new file mode 100644 index 000000000..6aabeb9f5 --- /dev/null +++ b/unzip/test/test_encrypted.zip.uu @@ -0,0 +1,13 @@ +begin 644 test_encrypted.zip +M4$L#!!0``````'*1MU8````````````````*`"``96YC`L``03H`P``!.@#```!F0<``@!!10,(`*_-)-RYPDYFJ$Q9+L< +M'#C?XVBR9/=H?7U\LC!A^8<6[&CO#PM02P<(`````"@````*````4$L!`A0# +M%```````7!T +M960O9FEL92YT>'155`T`!Q@Z;608.FUD&#IM9'5X"P`!!.@#```$Z`,```&9 +?!P`"`$%%`P@`4$L%!@`````"``(`PP```-L````````` +` +end diff --git a/unzip/test/test_glob.c b/unzip/test/test_glob.c new file mode 100644 index 000000000..b334ce4bd --- /dev/null +++ b/unzip/test/test_glob.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test that the glob works */ +DEFINE_TEST(test_glob) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s %s test_*/[ab] >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "test_basic/a"); + assertTextFileContents("contents b\n", "test_basic/b"); + assertFileNotExists("test_basic/c"); + assertFileNotExists("test_basic/CAPS"); +} diff --git a/unzip/test/test_j.c b/unzip/test/test_j.c new file mode 100644 index 000000000..a449e0264 --- /dev/null +++ b/unzip/test/test_j.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test j arg - don't make directories */ +DEFINE_TEST(test_j) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -j %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "a"); + assertTextFileContents("contents b\n", "b"); + assertTextFileContents("contents c\n", "c"); + assertTextFileContents("contents CAPS\n", "CAPS"); +} diff --git a/unzip/test/test_n.c b/unzip/test/test_n.c new file mode 100644 index 000000000..4e893f04b --- /dev/null +++ b/unzip/test/test_n.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test n arg - don't overrite existing files */ +DEFINE_TEST(test_n) +{ + const char *reffile = "test_basic.zip"; + int r; + + assertMakeDir("test_basic", 0755); + assertMakeFile("test_basic/a", 0644, "orig a\n"); + assertMakeFile("test_basic/b", 0644, "orig b\n"); + + extract_reference_file(reffile); + r = systemf("%s -n %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("orig a\n", "test_basic/a"); + assertTextFileContents("orig b\n", "test_basic/b"); + assertTextFileContents("contents c\n", "test_basic/c"); + assertTextFileContents("contents CAPS\n", "test_basic/CAPS"); +} diff --git a/unzip/test/test_not_exist.c b/unzip/test/test_not_exist.c new file mode 100644 index 000000000..aa660dc64 --- /dev/null +++ b/unzip/test/test_not_exist.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test non existant file */ +DEFINE_TEST(test_not_exist) +{ + int r; + r = systemf("%s nonexist.zip >test.out 2>test.err", testprog); + assert(r != 0); + assertEmptyFile("test.out"); + assertNonEmptyFile("test.err"); +} diff --git a/unzip/test/test_o.c b/unzip/test/test_o.c new file mode 100644 index 000000000..af0c41286 --- /dev/null +++ b/unzip/test/test_o.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test o arg - overrite existing files */ +DEFINE_TEST(test_o) +{ + const char *reffile = "test_basic.zip"; + int r; + + assertMakeDir("test_basic", 0755); + assertMakeFile("test_basic/a", 0644, "orig a\n"); + assertMakeFile("test_basic/b", 0644, "orig b\n"); + + extract_reference_file(reffile); + r = systemf("%s -o %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "test_basic/a"); + assertTextFileContents("contents b\n", "test_basic/b"); + assertTextFileContents("contents c\n", "test_basic/c"); + assertTextFileContents("contents CAPS\n", "test_basic/CAPS"); +} diff --git a/unzip/test/test_p.c b/unzip/test/test_p.c new file mode 100644 index 000000000..f34a5eae8 --- /dev/null +++ b/unzip/test/test_p.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test p arg - Print to stdout */ +DEFINE_TEST(test_p) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -p %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertTextFileContents("contents a\ncontents b\ncontents c\ncontents CAPS\n", "test.out"); + assertEmptyFile("test.err"); +} diff --git a/unzip/test/test_q.c b/unzip/test/test_q.c new file mode 100644 index 000000000..9a532c888 --- /dev/null +++ b/unzip/test/test_q.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test q arg - Quiet */ +DEFINE_TEST(test_q) +{ + const char *reffile = "test_basic.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -q %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "test_basic/a"); + assertTextFileContents("contents b\n", "test_basic/b"); + assertTextFileContents("contents c\n", "test_basic/c"); + assertTextFileContents("contents CAPS\n", "test_basic/CAPS"); +} diff --git a/unzip/test/test_singlefile.c b/unzip/test/test_singlefile.c new file mode 100644 index 000000000..70c376eb3 --- /dev/null +++ b/unzip/test/test_singlefile.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Ensure single-file zips work */ +DEFINE_TEST(test_singlefile) +{ + const char *reffile = "test_singlefile.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s %s >test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("hello\n", "file.txt"); +} diff --git a/unzip/test/test_singlefile.zip.uu b/unzip/test/test_singlefile.zip.uu new file mode 100644 index 000000000..589e08f12 --- /dev/null +++ b/unzip/test/test_singlefile.zip.uu @@ -0,0 +1,8 @@ +begin 644 test_singlefile.zip +M4$L#!!0`"``(`&"6MU8```````````8````(`"``9FEL92YT>'155`T`!U1# +M;6140VUD5$-M9'5X"P`!!.@#```$Z`,``,M(Stest.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); +} diff --git a/unzip/test/test_t_bad.c b/unzip/test/test_t_bad.c new file mode 100644 index 000000000..f9afbb40d --- /dev/null +++ b/unzip/test/test_t_bad.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 Adrian Vovk + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "test.h" + +/* Test t arg - Test zip contents, but fail! */ +DEFINE_TEST(test_t_bad) +{ + const char *reffile = "test_t_bad.zip"; + int r; + + extract_reference_file(reffile); + r = systemf("%s -t %s >test.out 2>test.err", testprog, reffile); + assert(r != 0); + assertNonEmptyFile("test.out"); + assertNonEmptyFile("test.err"); +} diff --git a/unzip/test/test_t_bad.zip.uu b/unzip/test/test_t_bad.zip.uu new file mode 100644 index 000000000..ae6ad0792 --- /dev/null +++ b/unzip/test/test_t_bad.zip.uu @@ -0,0 +1,25 @@ +begin 644 test_t_bad.zip +M4$L#!!0``````,J0MU8````````````````+`"``=&5S=%]B87-I8R]55`T` +M!]PX;63U.6UDW#AM9'5X"P`!!.@#```$Z`,``%!+`P04``@`"``)C;=6```` +M```````+````#``@`'1E`L``03H`P``!.@#``!+SL\K2test.out 2>test.err", testprog, reffile); + assertEqualInt(0, r); + assertNonEmptyFile("test.out"); + assertEmptyFile("test.err"); + + assertTextFileContents("contents a\n", "test_basic/a"); + assertTextFileContents("contents b\n", "test_basic/b"); + assertFileNotExists("test_basic/c"); + assertTextFileContents("contents CAPS\n", "test_basic/CAPS"); +} -- 2.47.2