From 472c3df1d85652dc5252b39a8b8fcf72d22c5f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kuba=20Podg=C3=B3rski?= Date: Sat, 17 Feb 2024 20:49:53 +0100 Subject: [PATCH] Open archive from C stream (FILE *) --- .github/workflows/build.yml | 19 +----- src/zip.c | 111 ++++++++++++++++++++++++++++++++---- src/zip.h | 44 ++++++++++++++ test/test_extract.c | 36 ++++++++++++ test/test_open.c | 22 ++++++- 5 files changed, 204 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 795eafe3..d61ce4b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,21 +35,6 @@ jobs: - name: Test run: cmake --build build --target test - # freebsd: - # runs-on: macos-latest - # steps: - # - uses: actions/checkout@v2 - # - name: Build and Test - # uses: vmactions/freebsd-vm@v0.1.2 - # with: - # usesh: true - # prepare: pkg install -y cmake tree lang/gcc - # run: | - # cmake . - # cmake --build . - # tree -sha . - # ctest -VV - windows-msvc: runs-on: "windows-latest" steps: @@ -76,4 +61,6 @@ jobs: cmake --build build tree /a /f build - name: Test - run: cmake --build build --target test + run: | + cd build + ctest -VV -C "Debug" --output-on-failure diff --git a/src/zip.c b/src/zip.c index 2480b71d..455c91bf 100644 --- a/src/zip.c +++ b/src/zip.c @@ -654,27 +654,28 @@ static ssize_t zip_entry_setbyindex(struct zip_t *zip, return 0; } -static ssize_t zip_mem_move(void *pBuf, size_t bufSize, const mz_uint64 to, const mz_uint64 from, const size_t length) { +static ssize_t zip_mem_move(void *pBuf, size_t bufSize, const mz_uint64 to, + const mz_uint64 from, const size_t length) { uint8_t *dst = NULL, *src = NULL, *end = NULL; - if(!pBuf) { + if (!pBuf) { return ZIP_EINVIDX; } end = (uint8_t *)pBuf + bufSize; - if(to > bufSize) { + if (to > bufSize) { return ZIP_EINVIDX; } - if(from > bufSize) { + if (from > bufSize) { return ZIP_EINVIDX; } dst = (uint8_t *)pBuf + to; src = (uint8_t *)pBuf + from; - if(((dst + length) > end) || ((src + length) > end)) { + if (((dst + length) > end) || ((src + length) > end)) { return ZIP_EINVIDX; } @@ -719,11 +720,12 @@ static ssize_t zip_files_move(struct zip_t *zip, mz_uint64 writen_num, while ((mz_int64)length > 0) { move_count = (length >= page_size) ? page_size : length; - if(pState->m_pFile) { - n = zip_file_move(pState->m_pFile, writen_num, read_num, move_count, move_buf, - page_size); - } else if(pState->m_pMem) { - n = zip_mem_move(pState->m_pMem, pState->m_mem_size, writen_num, read_num, move_count); + if (pState->m_pFile) { + n = zip_file_move(pState->m_pFile, writen_num, read_num, move_count, + move_buf, page_size); + } else if (pState->m_pMem) { + n = zip_mem_move(pState->m_pMem, pState->m_mem_size, writen_num, read_num, + move_count); } else { return ZIP_ENOFILE; } @@ -869,7 +871,7 @@ static ssize_t zip_entries_delete_mark(struct zip_t *zip, mz_zip_internal_state *pState = zip->archive.m_pState; zip->archive.m_zip_mode = MZ_ZIP_MODE_WRITING; - if(pState->m_pFile) { + if (pState->m_pFile) { if (MZ_FSEEK64(pState->m_pFile, 0, SEEK_SET)) { CLEANUP(deleted_entry_flag_array); return ZIP_ENOENT; @@ -1887,6 +1889,93 @@ void zip_stream_close(struct zip_t *zip) { } } +struct zip_t *zip_cstream_open(FILE *stream, int level, char mode) { + int errnum = 0; + return zip_cstream_openwitherror(stream, level, mode, &errnum); +} + +struct zip_t *zip_cstream_openwitherror(FILE *stream, int level, char mode, + int *errnum) { + struct zip_t *zip = NULL; + *errnum = 0; + if (!stream) { + // zip archive stream is NULL + *errnum = ZIP_ENOFILE; + goto cleanup; + } + + if (level < 0) + level = MZ_DEFAULT_LEVEL; + if ((level & 0xF) > MZ_UBER_COMPRESSION) { + // Wrong compression level + *errnum = ZIP_EINVLVL; + goto cleanup; + } + + zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t)); + if (!zip) { + // out of memory + *errnum = ZIP_EOOMEM; + goto cleanup; + } + + zip->level = (mz_uint)level; + switch (mode) { + case 'w': + // Create a new archive. + if (!mz_zip_writer_init_cfile(&(zip->archive), stream, + MZ_ZIP_FLAG_WRITE_ZIP64)) { + // Cannot initialize zip_archive writer + *errnum = ZIP_EWINIT; + goto cleanup; + } + break; + + case 'r': + if (!mz_zip_reader_init_cfile( + &(zip->archive), stream, 0, + zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize + // zip_archive reader + *errnum = ZIP_ERINIT; + goto cleanup; + } + break; + + case 'a': + case 'd': + if (!mz_zip_reader_init_cfile( + &(zip->archive), stream, 0, + zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize + // zip_archive reader + *errnum = ZIP_ERINIT; + goto cleanup; + } + if ((mode == 'a' || mode == 'd')) { + if (!mz_zip_writer_init_from_reader_v2_noreopen(&(zip->archive), NULL, + 0)) { + *errnum = ZIP_EWRINIT; + mz_zip_reader_end(&(zip->archive)); + goto cleanup; + } + } + break; + + default: + *errnum = ZIP_EINVMODE; + goto cleanup; + } + + return zip; + +cleanup: + CLEANUP(zip); + return NULL; +} + +void zip_cstream_close(struct zip_t *zip) { zip_close(zip); } + int zip_create(const char *zipname, const char *filenames[], size_t len) { int err = 0; size_t i; diff --git a/src/zip.h b/src/zip.h index 324904ca..92056d5c 100644 --- a/src/zip.h +++ b/src/zip.h @@ -13,6 +13,7 @@ #define ZIP_H #include +#include #include #include @@ -500,6 +501,49 @@ extern ZIP_EXPORT ssize_t zip_stream_copy(struct zip_t *zip, void **buf, */ extern ZIP_EXPORT void zip_stream_close(struct zip_t *zip); +/** + * Opens zip archive from existing FILE stream with compression level using the + * given mode. The stream will not be closed when calling zip_close. + * + * @param stream C FILE stream. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. This mode should be equivalent to the mode + * provided when opening the file. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * + * @return the zip archive handler or NULL on error + */ +extern ZIP_EXPORT struct zip_t *zip_cstream_open(FILE *stream, int level, + char mode); + +/** + * Opens zip archive from existing FILE stream with compression level using the + * given mode. The function additionally returns @param errnum - The stream will + * not be closed when calling zip_close. + * + * @param stream C FILE stream. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * @param errnum 0 on success, negative number (< 0) on error. + * + * @return the zip archive handler or NULL on error + */ +extern ZIP_EXPORT struct zip_t * +zip_cstream_openwitherror(FILE *stream, int level, char mode, int *errnum); + +/** + * Closes the zip archive, releases resources - always finalize. + * This function is an alias for zip_close function. + * + * @param zip zip archive handler. + */ +extern ZIP_EXPORT void zip_cstream_close(struct zip_t *zip); + /** * Creates a new archive and puts files into a single zip archive. * diff --git a/test/test_extract.c b/test/test_extract.c index 416365e1..ed833c7f 100644 --- a/test/test_extract.c +++ b/test/test_extract.c @@ -144,11 +144,47 @@ MU_TEST(test_extract_stream) { fclose(fp); } +MU_TEST(test_extract_cstream) { + struct buffer_t buf; + FILE *ZIPFILE = fopen(ZIPNAME, "r"); + + struct zip_t *zip = zip_cstream_open(ZIPFILE, 0, 'r'); + mu_check(zip != NULL); + + memset((void *)&buf, 0, sizeof(struct buffer_t)); + + mu_assert_int_eq(0, zip_entry_open(zip, "test/test-1.txt")); + mu_assert_int_eq(0, zip_entry_extract(zip, on_extract, &buf)); + mu_assert_int_eq(strlen(TESTDATA1), buf.size); + mu_assert_int_eq(0, strncmp(buf.data, TESTDATA1, buf.size)); + mu_assert_int_eq(0, zip_entry_close(zip)); + + free(buf.data); + buf.data = NULL; + buf.size = 0; + + memset((void *)&buf, 0, sizeof(struct buffer_t)); + + mu_assert_int_eq(0, zip_entry_open(zip, "dotfiles/.test")); + mu_assert_int_eq(0, zip_entry_extract(zip, on_extract, &buf)); + mu_assert_int_eq(strlen(TESTDATA2), buf.size); + mu_assert_int_eq(0, strncmp(buf.data, TESTDATA2, buf.size)); + mu_assert_int_eq(0, zip_entry_close(zip)); + + free(buf.data); + buf.data = NULL; + buf.size = 0; + fclose(ZIPFILE); + + zip_cstream_close(zip); +} + MU_TEST_SUITE(test_extract_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_extract); MU_RUN_TEST(test_extract_stream); + MU_RUN_TEST(test_extract_cstream); } int main(int argc, char *argv[]) { diff --git a/test/test_open.c b/test/test_open.c index 41ab0597..3d86f752 100644 --- a/test/test_open.c +++ b/test/test_open.c @@ -49,11 +49,31 @@ MU_TEST(test_stream_openwitherror) { zip_stream_close(zip); } +MU_TEST(test_cstream_openwitherror) { + int errnum; + FILE *ZIPFILE = NULL; + + struct zip_t *zip = zip_cstream_openwitherror( + ZIPFILE, ZIP_DEFAULT_COMPRESSION_LEVEL, 'r', &errnum); + mu_check(zip == NULL); + mu_assert_int_eq(ZIP_ENOFILE, errnum); + + ZIPFILE = fopen(ZIPNAME, "w"); + zip = zip_cstream_openwitherror(ZIPFILE, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w', + &errnum); + mu_check(zip != NULL); + mu_assert_int_eq(0, errnum); + + zip_cstream_close(zip); + fclose(ZIPFILE); +} + MU_TEST_SUITE(test_entry_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_openwitherror); MU_RUN_TEST(test_stream_openwitherror); + MU_RUN_TEST(test_cstream_openwitherror); } #define UNUSED(x) (void)x @@ -65,4 +85,4 @@ int main(int argc, char *argv[]) { MU_RUN_SUITE(test_entry_suite); MU_REPORT(); return MU_EXIT_CODE; -} \ No newline at end of file +}