From 09787ac1c85c45c03d45a4ad53eb9b08263cbb15 Mon Sep 17 00:00:00 2001 From: Enzo Evers Date: Fri, 27 Dec 2024 12:06:42 +0100 Subject: [PATCH] dev: Zip multiple files --- CoDeLib/CMakeLists.txt | 4 +- CoDeLib/CustomDeflate.md | 5 +- CoDeLib/RaiiString/src/RaiiString.c | 3 +- CoDeLib/Test/CMakeLists.txt | 3 + CoDeLib/Test/src/TestRaiiString.c | 8 +- CoDeLib/Test/src/TestUnZipMinizip.c | 4 + .../Test/src/TestUnZipMinizipInflateZlib.c | 4 + CoDeLib/Test/src/TestZipMinizip.c | 180 +++++ CoDeLib/Test/src/TestZipMinizip.h | 3 + CoDeLib/Test/src/TestZipMinizipUnZipMinizip.c | 680 ++++++++++++++++++ CoDeLib/Test/src/TestZipMinizipUnZipMinizip.h | 3 + CoDeLib/Test/src/main.c | 6 + CoDeLib/UnZip_minizip/src/UnZip_minizip.c | 9 +- CoDeLib/Zip_minizip/CMakeLists.txt | 16 + CoDeLib/Zip_minizip/src/Zip_minizip.c | 311 ++++++++ CoDeLib/include/CoDeLib/IZip.h | 26 + .../include/CoDeLib/Zip_minizip/Zip_minizip.h | 5 + 17 files changed, 1259 insertions(+), 11 deletions(-) create mode 100644 CoDeLib/Test/src/TestZipMinizip.c create mode 100644 CoDeLib/Test/src/TestZipMinizip.h create mode 100644 CoDeLib/Test/src/TestZipMinizipUnZipMinizip.c create mode 100644 CoDeLib/Test/src/TestZipMinizipUnZipMinizip.h create mode 100644 CoDeLib/Zip_minizip/CMakeLists.txt create mode 100644 CoDeLib/Zip_minizip/src/Zip_minizip.c create mode 100644 CoDeLib/include/CoDeLib/IZip.h create mode 100644 CoDeLib/include/CoDeLib/Zip_minizip/Zip_minizip.h diff --git a/CoDeLib/CMakeLists.txt b/CoDeLib/CMakeLists.txt index 8aa32cb5..3f69534a 100644 --- a/CoDeLib/CMakeLists.txt +++ b/CoDeLib/CMakeLists.txt @@ -25,6 +25,7 @@ set(COMMON_HEADERS ${CoDeLib_PUBLIC_INCLUDE_PATH}/IDeflate.h ${CoDeLib_PUBLIC_INCLUDE_PATH}/IInflate.h ${CoDeLib_PUBLIC_INCLUDE_PATH}/IUnZip.h + ${CoDeLib_PUBLIC_INCLUDE_PATH}/IZip.h ) install(FILES ${COMMON_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib) @@ -32,12 +33,13 @@ add_subdirectory(Deflate_zlib) add_subdirectory(Inflate_zlib) add_subdirectory(FileUtils) add_subdirectory(UnZip_minizip) +add_subdirectory(Zip_minizip) add_subdirectory(RaiiString) add_subdirectory(ZipContentInfo) add_subdirectory(Test) install( - TARGETS CoDeLib Deflate_zlib Inflate_zlib UnZip_minizip RaiiString ZipContentInfo FileUtils + TARGETS CoDeLib Deflate_zlib Inflate_zlib UnZip_minizip Zip_minizip RaiiString ZipContentInfo FileUtils EXPORT CoDeLibTargets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/CoDeLib/CustomDeflate.md b/CoDeLib/CustomDeflate.md index ab327d3b..0d72a6e0 100644 --- a/CoDeLib/CustomDeflate.md +++ b/CoDeLib/CustomDeflate.md @@ -5,4 +5,7 @@ # refs - https://pnrsolution.org/Datacenter/Vol4/Issue1/58.pdf -- https://nachtimwald.com/2019/09/08/making-minizip-easier-to-use/ \ No newline at end of file +- https://nachtimwald.com/2019/09/08/making-minizip-easier-to-use/ +- https://pkwaredownloads.blob.core.windows.net/pkware-general/Documentation/APPNOTE-6.3.9.TXT +- https://github.com/zlib-ng/minizip-ng/blob/cf5404bb714ee11ca2fdc3168d9770a11ec8b576/test/fuzz/zip_fuzzer.c#L100 +- https://stackoverflow.com/questions/12609747/traversing-a-filesystem-with-fts3 \ No newline at end of file diff --git a/CoDeLib/RaiiString/src/RaiiString.c b/CoDeLib/RaiiString/src/RaiiString.c index 6a412de3..8b214d73 100644 --- a/CoDeLib/RaiiString/src/RaiiString.c +++ b/CoDeLib/RaiiString/src/RaiiString.c @@ -9,8 +9,7 @@ RaiiString RaiiStringCreateFromCString(const char *pCString) { RaiiString newRaiistring = {NULL, 0}; - if (lengthWithoutTermination == 0 || - lengthWithoutTermination == MAX_CSTRING_INCLUDING_TERMINATION_LENGTH) { + if (lengthWithoutTermination == MAX_CSTRING_INCLUDING_TERMINATION_LENGTH) { return newRaiistring; } diff --git a/CoDeLib/Test/CMakeLists.txt b/CoDeLib/Test/CMakeLists.txt index 8b78df59..543a3a24 100644 --- a/CoDeLib/Test/CMakeLists.txt +++ b/CoDeLib/Test/CMakeLists.txt @@ -4,7 +4,9 @@ add_executable(CoDeLib_Test src/TestDeflateInflateZlib.c src/TestFileUtils.c src/TestUnZipMinizip.c + src/TestZipMinizip.c src/TestUnZipMinizipInflateZlib.c + src/TestZipMinizipUnZipMinizip.c src/TestZipContentInfo.c ) @@ -27,6 +29,7 @@ target_link_libraries(CoDeLib_Test PRIVATE Deflate_zlib) target_link_libraries(CoDeLib_Test PRIVATE Inflate_zlib) target_link_libraries(CoDeLib_Test PRIVATE FileUtils) target_link_libraries(CoDeLib_Test PRIVATE UnZip_minizip) +target_link_libraries(CoDeLib_Test PRIVATE Zip_minizip) target_link_libraries(CoDeLib_Test PRIVATE RaiiString) target_link_libraries(CoDeLib_Test PRIVATE ZipContentInfo) diff --git a/CoDeLib/Test/src/TestRaiiString.c b/CoDeLib/Test/src/TestRaiiString.c index 6f97fb47..96ae7551 100644 --- a/CoDeLib/Test/src/TestRaiiString.c +++ b/CoDeLib/Test/src/TestRaiiString.c @@ -93,11 +93,11 @@ TEST( TEST( TestRaiiString, - test_RaiiStringCreateFromCString_SetsLengthOfZeroAndNullptrIfProvidedEmptyString) { + test_RaiiStringCreateFromCString_SetsExpectedLengthAndPtrIfProvidedEmptyString) { const char *pCString = ""; raiiString = RaiiStringCreateFromCString(pCString); - TEST_ASSERT_NULL(raiiString.pString); - TEST_ASSERT_EQUAL(0, raiiString.lengthWithTermination); + TEST_ASSERT_NOT_NULL(raiiString.pString); + TEST_ASSERT_EQUAL(1, raiiString.lengthWithTermination); } //============================== @@ -297,7 +297,7 @@ TEST_GROUP_RUNNER(TestRaiiString) { test_RaiiStringCreateFromCString_SetsZeroLengthAndNullptrIfStringLengthIsGreaterThanMaxTotalLength); RUN_TEST_CASE( TestRaiiString, - test_RaiiStringCreateFromCString_SetsLengthOfZeroAndNullptrIfProvidedEmptyString); + test_RaiiStringCreateFromCString_SetsExpectedLengthAndPtrIfProvidedEmptyString); // RaiiStringClean() RUN_TEST_CASE(TestRaiiString, diff --git a/CoDeLib/Test/src/TestUnZipMinizip.c b/CoDeLib/Test/src/TestUnZipMinizip.c index 2e034631..e43860df 100644 --- a/CoDeLib/Test/src/TestUnZipMinizip.c +++ b/CoDeLib/Test/src/TestUnZipMinizip.c @@ -36,6 +36,10 @@ static RaiiString g_pathToMultiTextFileAndSubDirZipStore; static RaiiString g_pathToMultiTextFileAndSubDirZipSource; TEST_SETUP(TestUnZipMinizip) { + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } + g_someUnZippedDirPath = RaiiStringCreateFromCString("SomePath/someUnZippedDirPath.zip"); g_someZipPath = RaiiStringCreateFromCString("SomePath/someZipPath.zip"); diff --git a/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c b/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c index 3d71af4b..276e3178 100644 --- a/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c +++ b/CoDeLib/Test/src/TestUnZipMinizipInflateZlib.c @@ -27,6 +27,10 @@ static RaiiString g_pathToMultiTextFileAndSubDirZipDeflate; static RaiiString g_pathToMultiTextFileAndSubDirZipSource; TEST_SETUP(TestUnZipMinizipInflateZlib) { + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } + g_someUnZippedDirPath = RaiiStringCreateFromCString("SomePath/someUnZippedDirPath.zip"); g_someZipPath = RaiiStringCreateFromCString("SomePath/someZipPath.zip"); diff --git a/CoDeLib/Test/src/TestZipMinizip.c b/CoDeLib/Test/src/TestZipMinizip.c new file mode 100644 index 00000000..e185f0f1 --- /dev/null +++ b/CoDeLib/Test/src/TestZipMinizip.c @@ -0,0 +1,180 @@ +#include "TestZipMinizip.h" +#include "unity_fixture.h" +#include +#include +#include +#include +#include +#include +#include + +static char *g_pFullPathToBenchmarkTestFiles; + +void SetupTestZipMinizip(char *pFullPathToBenchmarkTestFiles) { + g_pFullPathToBenchmarkTestFiles = pFullPathToBenchmarkTestFiles; +} + +TEST_GROUP(TestZipMinizip); + +static RaiiString g_pathToSmallBasicTextFileZipDeflate; +static RaiiString g_pathToSmallBasicTextFileZipSource; +static RaiiString g_pathToMultiTextFileZipSource; +static RaiiString g_pathToMultiTextFileAndSubDirZipSource; + +TEST_SETUP(TestZipMinizip) { + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } + + g_pathToSmallBasicTextFileZipDeflate = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipDeflate, + "/SmallBasicTextFileZip/" + "SmallBasicTextFile_deflate.zip"); + + g_pathToSmallBasicTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipSource, + "/SmallBasicTextFileZip/"); + + g_pathToMultiTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileZipSource, + "/MultiTextFileZip/"); + + g_pathToMultiTextFileAndSubDirZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileAndSubDirZipSource, + "/MultiTextFileAndSubDirZip/"); +} + +TEST_TEAR_DOWN(TestZipMinizip) { + RaiiStringClean(&g_pathToSmallBasicTextFileZipDeflate); + RaiiStringClean(&g_pathToSmallBasicTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipSource); + + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } +} + +//============================== +// Zip() +//============================== + +TEST(TestZipMinizip, test_Zip_ReturnsErrorIfOutputZipPathIsNullptr) { + const char *pInputPathArray[] = { + "123", + "asdf", + }; + + const ZIP_RETURN_CODES statusZip = + zip_minizip.Zip(NULL, &pInputPathArray[0], 2); + TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip); +} + +TEST(TestZipMinizip, test_Zip_ReturnsErrorIfInputPathArrayIsNullptr) { + const char dummyString[] = "123"; + + const ZIP_RETURN_CODES statusZip = + zip_minizip.Zip(&dummyString[0], NULL, 2); + TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip); +} + +TEST(TestZipMinizip, test_Zip_ReturnsErrorIfProvidedSizeIsZero) { + const char dummyString[] = "123"; + const char *pInputPathArray[] = { + "123", + "asdf", + }; + + const ZIP_RETURN_CODES statusZip = + zip_minizip.Zip(&dummyString[0], &pInputPathArray[0], 0); + TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip); +} + +TEST(TestZipMinizip, test_Zip_ReturnsErrorIfZipFileAlreadyExists) { + RAII_STRING dummyZipPath = RaiiStringCreateFromCString("./tmp/"); + RecursiveMkdir(dummyZipPath.pString); + + RaiiStringAppend_cString(&dummyZipPath, "SomeZip.zip"); + FILE *pDummyFile = fopen(dummyZipPath.pString, "w"); + TEST_ASSERT_NOT_NULL(pDummyFile); + fclose(pDummyFile); + + const char *pInputPathArray[] = { + "123", + }; + + const ZIP_RETURN_CODES statusZip = + zip_minizip.Zip(dummyZipPath.pString, &pInputPathArray[0], 1); + TEST_ASSERT_EQUAL(ZIP_ERROR, statusZip); +} + +TEST(TestZipMinizip, + test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingRelativePath) { + RAII_STRING inputTextFilePath = RaiiStringCreateFromCString( + g_pathToSmallBasicTextFileZipSource.pString); + RaiiStringAppend_cString(&inputTextFilePath, "SmallBasicTextFile.txt"); + + const char *pInputPathArray[] = { + inputTextFilePath.pString, + }; + + RAII_STRING outputZipPath = + RaiiStringCreateFromCString("./tmp/SmallBasicTextFile.zip"); + + TEST_ASSERT_FALSE(PathExists(outputZipPath.pString)); + + const ZIP_RETURN_CODES statusZip = + zip_minizip.Zip(outputZipPath.pString, &pInputPathArray[0], 1); + TEST_ASSERT_EQUAL(ZIP_SUCCESS, statusZip); + + TEST_ASSERT_TRUE(PathExists(outputZipPath.pString)); +} + +TEST(TestZipMinizip, + test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingAbsolutePath) { + RAII_STRING inputTextFilePath = RaiiStringCreateFromCString( + g_pathToSmallBasicTextFileZipSource.pString); + RaiiStringAppend_cString(&inputTextFilePath, "SmallBasicTextFile.txt"); + + const char *pInputPathArray[] = { + inputTextFilePath.pString, + }; + + char cwdBuffer[MAX_PATH_LENGTH_WTH_TERMINATOR]; + GetCurrentWorkingDirectory(&cwdBuffer[0], MAX_PATH_LENGTH_WTH_TERMINATOR); + + RAII_STRING outputZipPath = RaiiStringCreateFromCString(&cwdBuffer[0]); + RaiiStringAppend_cString(&outputZipPath, "tmp/SmallBasicTextFile.zip"); + + TEST_ASSERT_FALSE(PathExists(outputZipPath.pString)); + + const ZIP_RETURN_CODES statusZip = + zip_minizip.Zip(outputZipPath.pString, &pInputPathArray[0], 1); + TEST_ASSERT_EQUAL(ZIP_SUCCESS, statusZip); + + TEST_ASSERT_TRUE(PathExists(outputZipPath.pString)); +} + +//============================== +// TEST_GROUP_RUNNER +//============================== + +TEST_GROUP_RUNNER(TestZipMinizip) { + // Zip() + RUN_TEST_CASE(TestZipMinizip, + test_Zip_ReturnsErrorIfOutputZipPathIsNullptr); + RUN_TEST_CASE(TestZipMinizip, + test_Zip_ReturnsErrorIfInputPathArrayIsNullptr); + RUN_TEST_CASE(TestZipMinizip, test_Zip_ReturnsErrorIfProvidedSizeIsZero); + RUN_TEST_CASE(TestZipMinizip, test_Zip_ReturnsErrorIfZipFileAlreadyExists); + RUN_TEST_CASE( + TestZipMinizip, + test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingRelativePath); + RUN_TEST_CASE( + TestZipMinizip, + test_Zip_CreatesZipFileAfterZippingInCorrectPathProvidingAbsolutePath); +} diff --git a/CoDeLib/Test/src/TestZipMinizip.h b/CoDeLib/Test/src/TestZipMinizip.h new file mode 100644 index 00000000..30abb2fb --- /dev/null +++ b/CoDeLib/Test/src/TestZipMinizip.h @@ -0,0 +1,3 @@ +#pragma once + +void SetupTestZipMinizip(char *pFullPathToBenchmarkTestFiles); diff --git a/CoDeLib/Test/src/TestZipMinizipUnZipMinizip.c b/CoDeLib/Test/src/TestZipMinizipUnZipMinizip.c new file mode 100644 index 00000000..2b212859 --- /dev/null +++ b/CoDeLib/Test/src/TestZipMinizipUnZipMinizip.c @@ -0,0 +1,680 @@ +#include "TestZipMinizipUnZipMinizip.h" +#include "unity_fixture.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static char *g_pFullPathToBenchmarkTestFiles; + +void SetupTestZipMinizipUnZipMinizip(char *pFullPathToBenchmarkTestFiles) { + g_pFullPathToBenchmarkTestFiles = pFullPathToBenchmarkTestFiles; +} + +TEST_GROUP(TestZipMinizipUnZipMinizip); + +static RaiiString g_pathToSmallBasicTextFileZipSource; +static RaiiString g_pathToMultiTextFileZipSource; +static RaiiString g_pathToMultiTextFileAndSubDirZipSource; + +TEST_SETUP(TestZipMinizipUnZipMinizip) { + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } + + g_pathToSmallBasicTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToSmallBasicTextFileZipSource, + "/SmallBasicTextFileZip/"); + + g_pathToMultiTextFileZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileZipSource, + "/MultiTextFileZip/"); + + g_pathToMultiTextFileAndSubDirZipSource = + RaiiStringCreateFromCString(g_pFullPathToBenchmarkTestFiles); + RaiiStringAppend_cString(&g_pathToMultiTextFileAndSubDirZipSource, + "/MultiTextFileAndSubDirZip/"); +} + +TEST_TEAR_DOWN(TestZipMinizipUnZipMinizip) { + RaiiStringClean(&g_pathToSmallBasicTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileZipSource); + RaiiStringClean(&g_pathToMultiTextFileAndSubDirZipSource); + + if (PathExists("./tmp/")) { + TEST_ASSERT_TRUE(RecursiveRmdir("./tmp/")); + } +} + +void TestZipMinizipUnZipMinizip_Store( + const RaiiString *const inputPathArray, const size_t inputPathArraySize, + const RaiiString *const sourceFilePathArrayToCompare, + const size_t sourceFilePathArrayToCompareSize, + const RaiiString *const unZippedFilePathArrayToCompare, + const size_t unZippedFilePathArrayToCompareSize, + const RaiiString *const pZippedResultPath, + const RaiiString *const pUnZippedResultPath) { + + TEST_ASSERT_EQUAL(sourceFilePathArrayToCompareSize, + unZippedFilePathArrayToCompareSize); + //---------- + // Generic setup + //---------- + + const char *charInputPathArray[inputPathArraySize]; + for (size_t i = 0; i < inputPathArraySize; ++i) { + charInputPathArray[i] = inputPathArray[i].pString; + } + + //---------- + // Zipping + //---------- + + const ZIP_RETURN_CODES statusZip = zip_minizip.Zip( + pZippedResultPath->pString, &charInputPathArray[0], inputPathArraySize); + + TEST_ASSERT_EQUAL(ZIP_SUCCESS, statusZip); + + //---------- + // Un-zipping + //---------- + + RAII_ZIPCONTENTINFO zipInfo = ZipContentInfoCreate(pZippedResultPath); + + const UNZIP_RETURN_CODES statusUnZip = + unzip_minizip.UnZip(&zipInfo, pUnZippedResultPath); + + TEST_ASSERT_EQUAL(UNZIP_SUCCESS, statusUnZip); + + //---------- + // Check if files are identical + //---------- + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + FILE *pFileSource = NULL; + FILE *pFileUnzipped = NULL; + + OpenFileWithMode(&pFileSource, &sourceFilePathArrayToCompare[i], "rb"); + OpenFileWithMode(&pFileUnzipped, &unZippedFilePathArrayToCompare[i], + "rb"); + + TEST_ASSERT_TRUE(FilesAreEqual(pFileSource, pFileUnzipped)); + + fclose(pFileSource); + fclose(pFileUnzipped); + } + + //---------- + // Clean up + //---------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + charInputPathArray[i] = NULL; + } +} + +//============================== +// Zip() + UnZip() +//============================== + +TEST(TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_SingleFile) { + + //-------------------- + // Setup + //-------------------- + + // clang-format off + + //----- Input path to zip ----- + + const size_t inputPathArraySize = 1; + RaiiString inputPathArray[inputPathArraySize]; + + for (size_t i = 0; i < inputPathArraySize; ++i) { + inputPathArray[i] = RaiiStringCreateFromCString(g_pathToSmallBasicTextFileZipSource.pString); + } + + RaiiStringAppend_cString(&inputPathArray[0], "SmallBasicTextFile.txt"); + + //----- Source files to compare with ----- + + const size_t sourceFilePathArrayToCompareSize = 1; + RaiiString sourceFilePathArrayToCompare[sourceFilePathArrayToCompareSize]; + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + sourceFilePathArrayToCompare[i] = RaiiStringCreateFromCString(g_pathToSmallBasicTextFileZipSource.pString); + } + + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[0], "SmallBasicTextFile.txt"); + + //----- Unzipped files to compare with ----- + + RAII_STRING zippedResultPath = RaiiStringCreateFromCString("./tmp/out.zip"); + RAII_STRING unZippedResultPath = RaiiStringCreateFromCString("./tmp/out_unzipped/"); + + const size_t unZippedFilePathArrayToCompareSize = sourceFilePathArrayToCompareSize; + RaiiString unZippedFilePathArrayToCompare[unZippedFilePathArrayToCompareSize]; + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + unZippedFilePathArrayToCompare[i] = RaiiStringCreateFromCString(unZippedResultPath.pString); + } + + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[0], "SmallBasicTextFile.txt"); + + // clang-format on + + //-------------------- + // Test + //-------------------- + + TestZipMinizipUnZipMinizip_Store( + &inputPathArray[0], inputPathArraySize, + &sourceFilePathArrayToCompare[0], sourceFilePathArrayToCompareSize, + &unZippedFilePathArrayToCompare[0], unZippedFilePathArrayToCompareSize, + &zippedResultPath, &unZippedResultPath); + + //-------------------- + // Cleanup + //-------------------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + RaiiStringClean(&inputPathArray[i]); + } + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&sourceFilePathArrayToCompare[i]); + } + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&unZippedFilePathArrayToCompare[i]); + } +} + +TEST(TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_MultiFile) { + + //-------------------- + // Setup + //-------------------- + + // clang-format off + + //----- Input path to zip ----- + + const size_t inputPathArraySize = 3; + RaiiString inputPathArray[inputPathArraySize]; + + for (size_t i = 0; i < inputPathArraySize; ++i) { + inputPathArray[i] = RaiiStringCreateFromCString(g_pathToMultiTextFileZipSource.pString); + } + + RaiiStringAppend_cString(&inputPathArray[0], "TextFileOne.txt"); + RaiiStringAppend_cString(&inputPathArray[1], "TextFileTwo.txt"); + RaiiStringAppend_cString(&inputPathArray[2], "ThirdTextFile.txt"); + + //----- Source files to compare with ----- + + const size_t sourceFilePathArrayToCompareSize = 3; + RaiiString sourceFilePathArrayToCompare[sourceFilePathArrayToCompareSize]; + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + sourceFilePathArrayToCompare[i] = RaiiStringCreateFromCString(g_pathToMultiTextFileZipSource.pString); + } + + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[0], "TextFileOne.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[1], "TextFileTwo.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[2], "ThirdTextFile.txt"); + + //----- Unzipped files to compare with ----- + + RAII_STRING zippedResultPath = RaiiStringCreateFromCString("./tmp/out.zip"); + RAII_STRING unZippedResultPath = RaiiStringCreateFromCString("./tmp/out_unzipped/"); + + const size_t unZippedFilePathArrayToCompareSize = sourceFilePathArrayToCompareSize; + RaiiString unZippedFilePathArrayToCompare[unZippedFilePathArrayToCompareSize]; + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + unZippedFilePathArrayToCompare[i] = RaiiStringCreateFromCString(unZippedResultPath.pString); + } + + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[0], "TextFileOne.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[1], "TextFileTwo.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[2], "ThirdTextFile.txt"); + + // clang-format on + + //-------------------- + // Test + //-------------------- + + TestZipMinizipUnZipMinizip_Store( + &inputPathArray[0], inputPathArraySize, + &sourceFilePathArrayToCompare[0], sourceFilePathArrayToCompareSize, + &unZippedFilePathArrayToCompare[0], unZippedFilePathArrayToCompareSize, + &zippedResultPath, &unZippedResultPath); + + //-------------------- + // Cleanup + //-------------------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + RaiiStringClean(&inputPathArray[i]); + } + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&sourceFilePathArrayToCompare[i]); + } + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&unZippedFilePathArrayToCompare[i]); + } +} + +TEST(TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_MultiFileAndSubDir) { + + //-------------------- + // Setup + //-------------------- + + // clang-format off + + //----- Input path to zip ----- + + const size_t inputPathArraySize = 3; + RaiiString inputPathArray[inputPathArraySize]; + + for (size_t i = 0; i < inputPathArraySize; ++i) { + inputPathArray[i] = RaiiStringCreateFromCString(g_pathToMultiTextFileAndSubDirZipSource.pString); + } + + RaiiStringAppend_cString(&inputPathArray[0], "DirFileOne/"); + RaiiStringAppend_cString(&inputPathArray[1], "DirOfDirs/"); + RaiiStringAppend_cString(&inputPathArray[2], "TextFile.txt"); + + //----- Source files to compare with ----- + + const size_t sourceFilePathArrayToCompareSize = 5; + RaiiString sourceFilePathArrayToCompare[sourceFilePathArrayToCompareSize]; + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + sourceFilePathArrayToCompare[i] = RaiiStringCreateFromCString(g_pathToMultiTextFileAndSubDirZipSource.pString); + } + + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[0], "DirFileOne/TextFileOne.txt"), + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[1], "DirOfDirs/OtherDir/DummyFile.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[2], "DirOfDirs/OtherDir/FilleInDirectory.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[3], "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[4], "TextFile.txt"); + + //----- Unzipped files to compare with ----- + + RAII_STRING zippedResultPath = RaiiStringCreateFromCString("./tmp/out.zip"); + RAII_STRING unZippedResultPath = RaiiStringCreateFromCString("./tmp/out_unzipped/"); + + const size_t unZippedFilePathArrayToCompareSize = sourceFilePathArrayToCompareSize; + RaiiString unZippedFilePathArrayToCompare[unZippedFilePathArrayToCompareSize]; + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + unZippedFilePathArrayToCompare[i] = RaiiStringCreateFromCString(unZippedResultPath.pString); + } + + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[0], "DirFileOne/TextFileOne.txt"), + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[1], "DirOfDirs/OtherDir/DummyFile.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[2], "DirOfDirs/OtherDir/FilleInDirectory.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[3], "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[4], "TextFile.txt"); + + // clang-format on + + //-------------------- + // Test + //-------------------- + + TestZipMinizipUnZipMinizip_Store( + &inputPathArray[0], inputPathArraySize, + &sourceFilePathArrayToCompare[0], sourceFilePathArrayToCompareSize, + &unZippedFilePathArrayToCompare[0], unZippedFilePathArrayToCompareSize, + &zippedResultPath, &unZippedResultPath); + + //-------------------- + // Cleanup + //-------------------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + RaiiStringClean(&inputPathArray[i]); + } + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&sourceFilePathArrayToCompare[i]); + } + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&unZippedFilePathArrayToCompare[i]); + } +} + +TEST( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_MultiFileAndSubDirDifferentBaseDir) { + // TEST_IGNORE_MESSAGE("Not implemented yet"); + + //-------------------- + // Setup + //-------------------- + + // clang-format off + + //----- Input path to zip ----- + + const size_t inputPathArraySize = 2; + RaiiString inputPathArray[inputPathArraySize]; + + inputPathArray[0] = RaiiStringCreateFromCString(g_pathToMultiTextFileAndSubDirZipSource.pString); + inputPathArray[1] = RaiiStringCreateFromCString(g_pathToSmallBasicTextFileZipSource.pString); + + RaiiStringAppend_cString(&inputPathArray[0], "DirOfDirs/"); + RaiiStringAppend_cString(&inputPathArray[1], "SmallBasicTextFile.txt"); + + //----- Source files to compare with ----- + + const size_t sourceFilePathArrayToCompareSize = 4; + RaiiString sourceFilePathArrayToCompare[sourceFilePathArrayToCompareSize]; + + for (size_t i = 0; i < 3; ++i) { + sourceFilePathArrayToCompare[i] = RaiiStringCreateFromCString(g_pathToMultiTextFileAndSubDirZipSource.pString); + } + sourceFilePathArrayToCompare[3] = RaiiStringCreateFromCString(g_pathToSmallBasicTextFileZipSource.pString); + + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[0], "DirOfDirs/OtherDir/DummyFile.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[1], "DirOfDirs/OtherDir/FilleInDirectory.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[2], "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"); + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[3], "SmallBasicTextFile.txt"); + + //----- Unzipped files to compare with ----- + + RAII_STRING zippedResultPath = RaiiStringCreateFromCString("./tmp/out.zip"); + RAII_STRING unZippedResultPath = RaiiStringCreateFromCString("./tmp/out_unzipped/"); + + const size_t unZippedFilePathArrayToCompareSize = sourceFilePathArrayToCompareSize; + RaiiString unZippedFilePathArrayToCompare[unZippedFilePathArrayToCompareSize]; + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + unZippedFilePathArrayToCompare[i] = RaiiStringCreateFromCString(unZippedResultPath.pString); + } + + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[0], "DirOfDirs/OtherDir/DummyFile.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[1], "DirOfDirs/OtherDir/FilleInDirectory.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[2], "DirOfDirs/SubDirWithCopyOfTopLevelFile/TextFile.txt"); + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[3], "SmallBasicTextFile.txt"); + + // clang-format on + + //-------------------- + // Test + //-------------------- + + TestZipMinizipUnZipMinizip_Store( + &inputPathArray[0], inputPathArraySize, + &sourceFilePathArrayToCompare[0], sourceFilePathArrayToCompareSize, + &unZippedFilePathArrayToCompare[0], unZippedFilePathArrayToCompareSize, + &zippedResultPath, &unZippedResultPath); + + //-------------------- + // Cleanup + //-------------------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + RaiiStringClean(&inputPathArray[i]); + } + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&sourceFilePathArrayToCompare[i]); + } + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&unZippedFilePathArrayToCompare[i]); + } +} + +TEST( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_CanZipAndUnZipFileOf1GB) { +#if !defined(NDEBUG) + TEST_IGNORE_MESSAGE("Not running in Debug mode"); +#endif + + //-------------------- + // Setup + //-------------------- + + //----- Genereate 1GB file ----- + + const char *const pRelativePathToTmp = "./tmp/"; + const char *const pFileName = "1GB.txt"; + RAII_STRING fileWith1GB = RaiiStringCreateFromCString(pRelativePathToTmp); + RaiiStringAppend_cString(&fileWith1GB, pFileName); + + RecursiveMkdir(pRelativePathToTmp); + + FILE *pFile = NULL; + OpenFileWithMode(&pFile, &fileWith1GB, "wb"); + + const uint64_t oneGB = 1000000000; + const uint64_t bufSize = 5000; + + char buf[bufSize]; + memset(buf, 'a', bufSize - 1); + buf[bufSize - 1] = '\0'; + + for (uint64_t i = 0; i < oneGB; i += bufSize) { + fwrite(buf, 1, bufSize, pFile); + } + + fclose(pFile); + + // clang-format off + + //----- Input path to zip ----- + + const size_t inputPathArraySize = 1; + RaiiString inputPathArray[inputPathArraySize]; + + for (size_t i = 0; i < inputPathArraySize; ++i) { + inputPathArray[i] = RaiiStringCreateFromCString(pRelativePathToTmp); + } + + RaiiStringAppend_cString(&inputPathArray[0], pFileName); + + //----- Source files to compare with ----- + + const size_t sourceFilePathArrayToCompareSize = 1; + RaiiString sourceFilePathArrayToCompare[sourceFilePathArrayToCompareSize]; + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + sourceFilePathArrayToCompare[i] = RaiiStringCreateFromCString(pRelativePathToTmp); + } + + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[0], pFileName); + + //----- Unzipped files to compare with ----- + + RAII_STRING zippedResultPath = RaiiStringCreateFromCString("./tmp/out.zip"); + RAII_STRING unZippedResultPath = RaiiStringCreateFromCString("./tmp/out_unzipped/"); + + const size_t unZippedFilePathArrayToCompareSize = sourceFilePathArrayToCompareSize; + RaiiString unZippedFilePathArrayToCompare[unZippedFilePathArrayToCompareSize]; + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + unZippedFilePathArrayToCompare[i] = RaiiStringCreateFromCString(unZippedResultPath.pString); + } + + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[0], pFileName); + + // clang-format on + + //-------------------- + // Test + //-------------------- + + TestZipMinizipUnZipMinizip_Store( + &inputPathArray[0], inputPathArraySize, + &sourceFilePathArrayToCompare[0], sourceFilePathArrayToCompareSize, + &unZippedFilePathArrayToCompare[0], unZippedFilePathArrayToCompareSize, + &zippedResultPath, &unZippedResultPath); + + //-------------------- + // Cleanup + //-------------------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + RaiiStringClean(&inputPathArray[i]); + } + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&sourceFilePathArrayToCompare[i]); + } + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&unZippedFilePathArrayToCompare[i]); + } +} + +TEST( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_CanZipAndUnZipFileOf6GB) { + + // TODO: Be more selective on when to run this test since it takes too long + // for unit tests +#if !defined(NDEBUG) + TEST_IGNORE_MESSAGE("Not running in Debug mode"); +#endif + + //-------------------- + // Setup + //-------------------- + + //----- Genereate 6GB file ----- + + const char *const pRelativePathToTmp = "./tmp/"; + const char *const pFileName = "6GB.txt"; + RAII_STRING fileWith1GB = RaiiStringCreateFromCString(pRelativePathToTmp); + RaiiStringAppend_cString(&fileWith1GB, pFileName); + + RecursiveMkdir(pRelativePathToTmp); + + FILE *pFile = NULL; + OpenFileWithMode(&pFile, &fileWith1GB, "wb"); + + const uint64_t sixGB = 6000000000; + const uint64_t bufSize = 5000; + + char buf[bufSize]; + memset(buf, 'a', bufSize - 1); + buf[bufSize - 1] = '\0'; + + for (uint64_t i = 0; i < sixGB; i += bufSize) { + fwrite(buf, 1, bufSize, pFile); + } + + fclose(pFile); + + // clang-format off + + //----- Input path to zip ----- + + const size_t inputPathArraySize = 1; + RaiiString inputPathArray[inputPathArraySize]; + + for (size_t i = 0; i < inputPathArraySize; ++i) { + inputPathArray[i] = RaiiStringCreateFromCString(pRelativePathToTmp); + } + + RaiiStringAppend_cString(&inputPathArray[0], pFileName); + + //----- Source files to compare with ----- + + const size_t sourceFilePathArrayToCompareSize = 1; + RaiiString sourceFilePathArrayToCompare[sourceFilePathArrayToCompareSize]; + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + sourceFilePathArrayToCompare[i] = RaiiStringCreateFromCString(pRelativePathToTmp); + } + + RaiiStringAppend_cString(&sourceFilePathArrayToCompare[0], pFileName); + + //----- Unzipped files to compare with ----- + + RAII_STRING zippedResultPath = RaiiStringCreateFromCString("./tmp2/out.zip"); + RAII_STRING unZippedResultPath = RaiiStringCreateFromCString("./tmp2/out_unzipped/"); + + const size_t unZippedFilePathArrayToCompareSize = sourceFilePathArrayToCompareSize; + RaiiString unZippedFilePathArrayToCompare[unZippedFilePathArrayToCompareSize]; + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + unZippedFilePathArrayToCompare[i] = RaiiStringCreateFromCString(unZippedResultPath.pString); + } + + RaiiStringAppend_cString(&unZippedFilePathArrayToCompare[0], pFileName); + + // clang-format on + + //-------------------- + // Test + //-------------------- + + TestZipMinizipUnZipMinizip_Store( + &inputPathArray[0], inputPathArraySize, + &sourceFilePathArrayToCompare[0], sourceFilePathArrayToCompareSize, + &unZippedFilePathArrayToCompare[0], unZippedFilePathArrayToCompareSize, + &zippedResultPath, &unZippedResultPath); + + //-------------------- + // Cleanup + //-------------------- + + for (size_t i = 0; i < inputPathArraySize; ++i) { + RaiiStringClean(&inputPathArray[i]); + } + + for (size_t i = 0; i < sourceFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&sourceFilePathArrayToCompare[i]); + } + + for (size_t i = 0; i < unZippedFilePathArrayToCompareSize; ++i) { + RaiiStringClean(&unZippedFilePathArrayToCompare[i]); + } +} + +//============================== +// TEST_GROUP_RUNNER +//============================== + +TEST_GROUP_RUNNER(TestZipMinizipUnZipMinizip) { + // Zip() + UnZip() + RUN_TEST_CASE( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_SingleFile); + RUN_TEST_CASE( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_MultiFile); + RUN_TEST_CASE( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_MultiFileAndSubDir); + RUN_TEST_CASE( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_MultiFileAndSubDirDifferentBaseDir); + RUN_TEST_CASE( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_CanZipAndUnZipFileOf1GB); + RUN_TEST_CASE( + TestZipMinizipUnZipMinizip, + test_ZippingAndUnzippingRawTextGivesIdenticalOutput_CanZipAndUnZipFileOf6GB); +} diff --git a/CoDeLib/Test/src/TestZipMinizipUnZipMinizip.h b/CoDeLib/Test/src/TestZipMinizipUnZipMinizip.h new file mode 100644 index 00000000..afb65aa6 --- /dev/null +++ b/CoDeLib/Test/src/TestZipMinizipUnZipMinizip.h @@ -0,0 +1,3 @@ +#pragma once + +void SetupTestZipMinizipUnZipMinizip(char *pFullPathToBenchmarkTestFiles); diff --git a/CoDeLib/Test/src/main.c b/CoDeLib/Test/src/main.c index cfe9cf7d..d273bde8 100644 --- a/CoDeLib/Test/src/main.c +++ b/CoDeLib/Test/src/main.c @@ -4,6 +4,8 @@ #include "TestFileUtils.h" #include "TestUnZipMinizip.h" #include "TestUnZipMinizipInflateZlib.h" +#include "TestZipMinizip.h" +#include "TestZipMinizipUnZipMinizip.h" #include #include @@ -12,7 +14,9 @@ static void RunAllTests(void) { RUN_TEST_GROUP(TestDeflateInflateZlib); RUN_TEST_GROUP(TestFileUtils); RUN_TEST_GROUP(TestUnZipMinizip); + RUN_TEST_GROUP(TestZipMinizip); RUN_TEST_GROUP(TestUnZipMinizipInflateZlib); + RUN_TEST_GROUP(TestZipMinizipUnZipMinizip); RUN_TEST_GROUP(TestZipContentInfo); } @@ -35,7 +39,9 @@ int main(int argc, const char **argv) { SetupTestFileUtils(fullPathToBenchmarkTestFiles.pString, currentWorkingDirectory.pString); SetupTestUnZipMinizip(fullPathToBenchmarkTestFiles.pString); + SetupTestZipMinizip(fullPathToBenchmarkTestFiles.pString); SetupTestUnZipMinizipInflateZlib(fullPathToBenchmarkTestFiles.pString); + SetupTestZipMinizipUnZipMinizip(fullPathToBenchmarkTestFiles.pString); return UnityMain(argc, argv, RunAllTests); } diff --git a/CoDeLib/UnZip_minizip/src/UnZip_minizip.c b/CoDeLib/UnZip_minizip/src/UnZip_minizip.c index c495df91..cd1c9b38 100644 --- a/CoDeLib/UnZip_minizip/src/UnZip_minizip.c +++ b/CoDeLib/UnZip_minizip/src/UnZip_minizip.c @@ -103,9 +103,12 @@ UnZip(ZipContentInfo *const pZipInfo, const RaiiString *const pOutputDirPath) { int readCount = 0; do { - char buffer[256]; - readCount = - unzReadCurrentFile(uzfile, buffer, sizeof(buffer) - 1); + const uint32_t bufSize = 1024; + char buffer[bufSize]; + readCount = unzReadCurrentFile(uzfile, buffer, bufSize - 1); + + // It is safe to index with `readCount`. Because it + // will be at most `bufSize - 1`. buffer[readCount] = '\0'; if (readCount < 0) { status = UNZIP_ERROR; diff --git a/CoDeLib/Zip_minizip/CMakeLists.txt b/CoDeLib/Zip_minizip/CMakeLists.txt new file mode 100644 index 00000000..01a53c21 --- /dev/null +++ b/CoDeLib/Zip_minizip/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(Zip_minizip STATIC + src/Zip_minizip.c) + +target_include_directories(Zip_minizip PUBLIC + $ + $ +) + +find_package(MINIZIP REQUIRED) +target_link_libraries(Zip_minizip PRIVATE MINIZIP::minizip) + +set(Zip_minizip_PUBLIC_INCLUDE_PATH ${CoDeLib_PUBLIC_INCLUDE_PATH}/Zip_minizip) +set(Zip_minizip_PUBLIC_HEADERS + ${Zip_minizip_PUBLIC_INCLUDE_PATH}/Zip_minizip.h +) +install(FILES ${Zip_minizip_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CoDeLib/Zip_minizip) diff --git a/CoDeLib/Zip_minizip/src/Zip_minizip.c b/CoDeLib/Zip_minizip/src/Zip_minizip.c new file mode 100644 index 00000000..d6c8dfa4 --- /dev/null +++ b/CoDeLib/Zip_minizip/src/Zip_minizip.c @@ -0,0 +1,311 @@ +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +// minizip +#include +#include + +//============================== +// Helper functions +//============================== + +/* +@brief Zips the content of a path into a zip file. It also handles directories +by recursively going through them. +@param zFile The zip file to write to. This should already have been opened with +zipOpen64. +@param pInputPath The full path to zip. This can be a file or a directory path. +@param level This is the level at which the file or directory is in the zip +file. +*/ +ZIP_RETURN_CODES ZipPath(zipFile zFile, const char *const pInputPath, + uint8_t level); + +#ifdef _WIN32 +ZIP_RETURN_CODES ZipDirectory_Windows(zipFile zFile, + const char *const pInputPath, + uint8_t level); +#else +ZIP_RETURN_CODES ZipDirectory_Posix(zipFile zFile, + const char *const pInputPath); +#endif +//============================== +// Interface functions +//============================== + +ZIP_RETURN_CODES +Zip(const char *pOutputZipPath, const char **pInputPathArray, + const size_t inputPathArraySize) { + if (pOutputZipPath == NULL || pInputPathArray == NULL || + inputPathArraySize == 0) { + return ZIP_ERROR; + } + + if (PathExists(pOutputZipPath)) { + return ZIP_ERROR; + } + + ZIP_RETURN_CODES returnCode = ZIP_SUCCESS; + + // It doesn't matter that the path contains the file name. This is + // simply ignored. + RecursiveMkdir(pOutputZipPath); + + const int append = 0; + zipFile zFile = zipOpen64(pOutputZipPath, append); + if (zFile == NULL) { + // TODO: make the string handling safer by limiting the size + printf("Could not open %s for zipping\n", pOutputZipPath); + zipClose(zFile, NULL); + return false; + } + + for (size_t i = 0; i < inputPathArraySize && returnCode == ZIP_SUCCESS; + ++i) { + const size_t pathLength = + strnlen(pInputPathArray[i], MAX_PATH_LENGTH_WTH_TERMINATOR); + if (pathLength == MAX_PATH_LENGTH_WTH_TERMINATOR) { + returnCode = ZIP_ERROR; + break; + } + + const char lastChar = pInputPathArray[i][pathLength - 1]; + bool isDir = lastChar == '/' || lastChar == '\\'; + + if (!PathExists(pInputPathArray[i])) { + returnCode = ZIP_ERROR; + break; + } + + if (!isDir) { + returnCode = ZipPath(zFile, pInputPathArray[i], 0); + } else { +#ifdef _WIN32 + returnCode = ZipDirectory_Windows(zFile, pInputPathArray[i], 0); +#else + returnCode = ZipDirectory_Posix(zFile, pInputPathArray[i]); +#endif + } + } + + const char *global_comment = NULL; + zipClose(zFile, global_comment); + + return returnCode; +} + +const struct IZip zip_minizip = { + .Zip = Zip, +}; + +//============================== +// Helper functions +//============================== + +ZIP_RETURN_CODES ZipPath(zipFile zFile, const char *const pInputPath, + uint8_t level) { + ZIP_RETURN_CODES returnCode = ZIP_SUCCESS; + + RAII_STRING pathToSaveInZip = RaiiStringCreateFromCString(""); + + RAII_STRING tmpInputPath = RaiiStringCreateFromCString(pInputPath); + for (size_t i = 0; i <= level; ++i) { + char lastPartOfPath[MAX_PATH_LENGTH_WTH_TERMINATOR]; + size_t startIndexOfLastPart = + ExtractLastPartOfPath(tmpInputPath.pString, lastPartOfPath, + MAX_PATH_LENGTH_WTH_TERMINATOR); + + if (startIndexOfLastPart == SIZE_MAX) { + return ZIP_ERROR; + } + + char tmpBuf[MAX_PATH_LENGTH_WTH_TERMINATOR]; + const size_t charsWritten = + snprintf(&tmpBuf[0], MAX_PATH_LENGTH_WTH_TERMINATOR, "%s%s", + lastPartOfPath, pathToSaveInZip.pString); + if (charsWritten < strlen(lastPartOfPath)) { + return ZIP_ERROR; + } + RaiiStringClean(&pathToSaveInZip); + pathToSaveInZip = RaiiStringCreateFromCString(tmpBuf); + + tmpInputPath.pString[startIndexOfLastPart] = '\0'; + tmpInputPath.lengthWithTermination = startIndexOfLastPart + 1; + } + RaiiStringClean(&tmpInputPath); + + RAII_STRING inputPath = RaiiStringCreateFromCString(pInputPath); + + const size_t lastPartOfPathLenght = + pathToSaveInZip.lengthWithTermination - 1; + if (lastPartOfPathLenght == MAX_PATH_LENGTH_WTH_TERMINATOR) { + return ZIP_ERROR; + } + + const char lastChar = pathToSaveInZip.pString[lastPartOfPathLenght - 1]; + bool isDir = lastChar == '/' || lastChar == '\\'; + + size_t fileSize = 0; + + if (!isDir) { + FILE *pFile = NULL; + OpenFileWithMode(&pFile, &inputPath, "rb"); + fileSize = GetFileSizeInBytes(pFile); + fclose(pFile); + } else if (lastChar == '\\') { + // Zip requeres '/' as a separator to indicate directories + pathToSaveInZip.pString[lastPartOfPathLenght - 1] = '/'; + } + + const int compressoinsMethod = MZ_COMPRESS_METHOD_STORE; + const int compressionLevel = 0; + // Don't store as raw file. Otherwise the CRC is not properly handled. + const int raw = 0; + const int requiresZip64 = (fileSize > 0xffffffff) ? 1 : 0; + + int status = zipOpenNewFileInZip2_64( + zFile, pathToSaveInZip.pString, NULL, NULL, 0, NULL, 0, NULL, + compressoinsMethod, compressionLevel, raw, requiresZip64); + + if (status != MZ_OK) { + zipCloseFileInZip(zFile); + return ZIP_ERROR; + } + + if (!isDir) { + FILE *pFile = NULL; + OpenFileWithMode(&pFile, &inputPath, "rb"); + + const size_t bufSize = 1024; + char buf[bufSize]; + + bool stopWriting = false; + do { + size_t charsToWrite = fread(buf, 1, bufSize, pFile); + + if (charsToWrite != bufSize) { + if (feof(pFile)) { + stopWriting = true; + } else if (ferror(pFile)) { + returnCode = ZIP_ERROR; + break; + } + } + + status = zipWriteInFileInZip(zFile, buf, charsToWrite); + if (status != MZ_OK) { + returnCode = ZIP_ERROR; + break; + } + } while (!stopWriting); + + fclose(pFile); + } + + zipCloseFileInZip(zFile); + return returnCode; +} + +#ifdef _WIN32 +ZIP_RETURN_CODES ZipDirectory_Windows(zipFile zFile, + const char *const pInputPath, + uint8_t level) { + ZIP_RETURN_CODES returnCode = ZIP_SUCCESS; + + // Add the direcotry to the zip + returnCode = ZipPath(zFile, pInputPath, level); + if (returnCode != ZIP_SUCCESS) { + return ZIP_ERROR; + } + + RAII_STRING searchPath = RaiiStringCreateFromCString(pInputPath); + RaiiStringAppend_cString(&searchPath, "*"); + + WIN32_FIND_DATA findFileData; + HANDLE hFind = FindFirstFile(searchPath.pString, &findFileData); + if (hFind == INVALID_HANDLE_VALUE) { + return ZIP_ERROR; + } + + do { + const char *const name = findFileData.cFileName; + + // Skip the special entries "." and ".." + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + continue; + } + + // Construct the full path of the current file or directory + RAII_STRING fullPath = RaiiStringCreateFromCString(pInputPath); + RaiiStringAppend_cString(&fullPath, name); + + // Check if the entry is a directory + if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // Directory found does not have a trailing '/' or '\\' by default + RaiiStringAppend_cString(&fullPath, "/"); + returnCode = + ZipDirectory_Windows(zFile, fullPath.pString, level + 1); + if (returnCode != ZIP_SUCCESS) { + break; + } + } else { + returnCode = ZipPath(zFile, fullPath.pString, level + 1); + if (returnCode != ZIP_SUCCESS) { + break; + } + } + } while (FindNextFile(hFind, &findFileData) != 0); + + FindClose(hFind); + + return returnCode; +} + +#else + +ZIP_RETURN_CODES ZipDirectory_Posix(zipFile zFile, + const char *const pInputPath) { + ZIP_RETURN_CODES returnCode = ZIP_SUCCESS; + + // TODO: Determine if pInputPath should be converted to an + // absolute path. + RAII_STRING inputPathCopy = RaiiStringCreateFromCString(pInputPath); + char *const inputPathArray[] = {inputPathCopy.pString, NULL}; + FTS *pFileSystem = + fts_open(&inputPathArray[0], FTS_COMFOLLOW | FTS_NOCHDIR, NULL); + if (pFileSystem == NULL) { + return ZIP_ERROR; + } + + FTSENT *node = fts_read(pFileSystem); + while (node != NULL && returnCode == ZIP_SUCCESS) { + const char *const pFpath = node->fts_path; + const int level = node->fts_level; + const int typeflag = node->fts_info; + + RAII_STRING fpath = RaiiStringCreateFromCString(pFpath); + + if (typeflag == FTS_D && + fpath.pString[fpath.lengthWithTermination - 2] != '/') { + RaiiStringAppend_cString(&fpath, "/"); + } + + if (typeflag == FTS_F || typeflag == FTS_D) { + returnCode = ZipPath(zFile, fpath.pString, level); + } + + node = fts_read(pFileSystem); + } + fts_close(pFileSystem); + + return returnCode; +} +#endif diff --git a/CoDeLib/include/CoDeLib/IZip.h b/CoDeLib/include/CoDeLib/IZip.h new file mode 100644 index 00000000..4d773525 --- /dev/null +++ b/CoDeLib/include/CoDeLib/IZip.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +typedef enum { ZIP_SUCCESS, ZIP_ERROR } ZIP_RETURN_CODES; + +struct IZip { + /*! + * @brief Zips the input file(s) and write the output to the output zip + * file. + * @param pOutputZipPath The path to the output zip file. If the directory + * does not exist, it will be created. + * @param pInputPathArray The array of paths to the files or directories to + * be zipped. Both absolute and relative paths are supported. Path to + * directories will zip all files and sub-directories in the directory. Path + * to files will zip the file without any directories. The array can be a + * mix of absolute, relative, directies and files. + * @param inputPathArraySize The number of elements in pInputPathArray. + * @return ZIP_SUCCESS if the zipping was successful, ZIP_ERROR + * otherwise. + */ + ZIP_RETURN_CODES(*Zip) + (const char *pOutputZipPath, const char **pInputPathArray, + const size_t inputPathArraySize); +}; diff --git a/CoDeLib/include/CoDeLib/Zip_minizip/Zip_minizip.h b/CoDeLib/include/CoDeLib/Zip_minizip/Zip_minizip.h new file mode 100644 index 00000000..86f52414 --- /dev/null +++ b/CoDeLib/include/CoDeLib/Zip_minizip/Zip_minizip.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const struct IZip zip_minizip;