diff --git a/README.md b/README.md index 7df2181..93f43d8 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,21 @@ It was the reason, why I decided to write zip module on top of the miniz. It req zip_close(zip); ``` +* List of all zip entries +```c + struct zip_t *zip = zip_open("foo.zip", 0, 'r'); + int i, n = zip_total_entries(zip); + for (i = 0; i < n; ++i) { + zip_entry_openbyindex(zip, i); + { + const char *name = zip_entry_name(zip); + int isdir = zip_entry_isdir(zip); + } + zip_entry_close(zip); + } + zip_close(zip); +``` + # Bindings * Compile zip library as a dynamic library. ```shell diff --git a/src/zip.c b/src/zip.c index a4717f3..ab6b62f 100644 --- a/src/zip.c +++ b/src/zip.c @@ -83,16 +83,16 @@ static int mkpath(const char *path) { } static char *strrpl(const char *str, size_t n, char oldchar, char newchar) { - char *rpl = (char *)malloc(sizeof(char) * (1 + n)); + char *rpl = (char *)calloc((1 + n), sizeof(char)); char *begin = rpl; char c; - while((c = *str++)) { + size_t i; + for(i = 0; (i < n) && (c = *str++); ++i) { if (c == oldchar) { c = newchar; } *rpl++ = c; } - *rpl = '\0'; return begin; } @@ -115,7 +115,6 @@ struct zip_t { mz_zip_archive archive; mz_uint level; struct zip_entry_t entry; - char mode; }; struct zip_t *zip_open(const char *zipname, int level, char mode) { @@ -136,7 +135,6 @@ struct zip_t *zip_open(const char *zipname, int level, char mode) { if (!zip) goto cleanup; zip->level = level; - zip->mode = mode; switch (mode) { case 'w': // Create a new archive. @@ -155,13 +153,11 @@ struct zip_t *zip_open(const char *zipname, int level, char mode) { // zip_archive reader goto cleanup; } - if (mode == 'a' && !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) { mz_zip_reader_end(&(zip->archive)); goto cleanup; } - break; default: @@ -220,7 +216,7 @@ int zip_entry_open(struct zip_t *zip, const char *entryname) { } pzip = &(zip->archive); - if (zip->mode == 'r') { + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { zip->entry.index = mz_zip_reader_locate_file(pzip, zip->entry.name, NULL, 0); if (zip->entry.index < 0) { goto cleanup; @@ -302,6 +298,58 @@ int zip_entry_open(struct zip_t *zip, const char *entryname) { return -1; } +int zip_entry_openbyindex(struct zip_t *zip, int index) { + mz_zip_archive *pZip = NULL; + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + pZip = &(zip->archive); + if (pZip->m_zip_mode != MZ_ZIP_MODE_READING) { + // open by index requires readonly mode + return -1; + } + + if (index < 0 || index >= pZip->m_total_files) { + // index out of range + return -1; + } + + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, index)); + if (!pHeader) { + // cannot find header in central directory + return -1; + } + + mz_uint namelen = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (!pFilename) { + // entry name is NULL + return -1; + } + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + zip->entry.name = strrpl(pFilename, namelen, '\\', '/'); + if (!zip->entry.name) { + // local entry name is NULL + return -1; + } + zip->entry.index = index; + + return 0; +} + int zip_entry_close(struct zip_t *zip) { mz_zip_archive *pzip = NULL; mz_uint level; @@ -317,12 +365,12 @@ int zip_entry_close(struct zip_t *zip) { goto cleanup; } - if (zip->mode == 'r') { + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { status = 0; goto cleanup; } - pzip = &(zip->archive); level = zip->level & 0xF; if (level) { done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH); @@ -401,6 +449,20 @@ int zip_entry_index(struct zip_t *zip) { return zip->entry.index; } +int zip_entry_isdir(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return -1; + } + + if (zip->entry.index < 0) { + // zip entry is not opened + return -1; + } + + return (int)mz_zip_reader_is_file_a_directory(&zip->archive, (mz_uint)zip->entry.index); +} + int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) { mz_uint level; mz_zip_archive *pzip = NULL; @@ -477,12 +539,12 @@ int zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) { return -1; } - if (zip->mode != 'r' || zip->entry.index < 0) { + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { // the entry is not found or we do not have read access return -1; } - pzip = &(zip->archive); idx = (mz_uint)zip->entry.index; if (mz_zip_reader_is_file_a_directory(pzip, idx)) { // the entry is a directory @@ -502,12 +564,12 @@ int zip_entry_fread(struct zip_t *zip, const char *filename) { return -1; } - if (zip->mode != 'r' || zip->entry.index < 0) { + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { // the entry is not found or we do not have read access return -1; } - pzip = &(zip->archive); idx = (mz_uint)zip->entry.index; if (mz_zip_reader_is_file_a_directory(pzip, idx)) { // the entry is a directory @@ -529,17 +591,14 @@ int zip_entry_extract(struct zip_t *zip, return -1; } - if (zip->mode != 'r' || zip->entry.index < 0) { + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { // the entry is not found or we do not have read access return -1; } - pzip = &(zip->archive); idx = (mz_uint)zip->entry.index; - - return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0)) - ? 0 - : -1; + return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0)) ? 0 : -1; } int zip_total_entries(struct zip_t *zip) { diff --git a/src/zip.h b/src/zip.h index 3926123..133f882 100644 --- a/src/zip.h +++ b/src/zip.h @@ -55,7 +55,10 @@ extern struct zip_t *zip_open(const char *zipname, int level, char mode); extern void zip_close(struct zip_t *zip); /* - Opens a new entry for writing in the zip archive. + Opens an entry by name in the zip archive. + For zip archive opened in 'w' or 'a' mode the function will append + a new entry. In readonly mode the function tries to locate the entry + in global dictionary. Args: zip: zip archive handler. @@ -66,6 +69,19 @@ extern void zip_close(struct zip_t *zip); */ extern int zip_entry_open(struct zip_t *zip, const char *entryname); +/* + Opens a new entry by index in the zip archive. + This function is only valid if zip archive was opened in 'r' (readonly) mode. + + Args: + zip: zip archive handler. + index: index in local dictionary. + + Returns: + The return code - 0 on success, negative number (< 0) on error. +*/ +extern int zip_entry_openbyindex(struct zip_t *zip, int index); + /* Closes a zip entry, flushes buffer and releases resources. @@ -105,6 +121,17 @@ extern const char *zip_entry_name(struct zip_t *zip); */ extern int zip_entry_index(struct zip_t *zip); +/* + Determines if the current zip entry is a directory entry. + + Args: + zip: zip archive handler. + + Returns: + The return code - 1 (true), 0 (false), negative number (< 0) on error. +*/ +extern int zip_entry_isdir(struct zip_t *zip); + /* Compresses an input buffer for the current zip entry. diff --git a/test/test.c b/test/test.c index 0f59a10..2dc32e3 100644 --- a/test/test.c +++ b/test/test.c @@ -159,6 +159,44 @@ static void test_entry_index(void) { zip_close(zip); } +static void test_entry_openbyindex(void) { + struct zip_t *zip = zip_open(ZIPNAME, 0, 'r'); + assert(zip != NULL); + + assert(0 == zip_entry_openbyindex(zip, 1)); + assert(1 == zip_entry_index(zip)); + + assert(0 == strcmp(zip_entry_name(zip), "test/test-2.txt")); + assert(0 == zip_entry_close(zip)); + + assert(0 == zip_entry_openbyindex(zip, 0)); + assert(0 == zip_entry_index(zip)); + assert(0 == strcmp(zip_entry_name(zip), "test/test-1.txt")); + assert(0 == zip_entry_close(zip)); + + zip_close(zip); +} + +static void test_list_entries(void) { + struct zip_t *zip = zip_open(ZIPNAME, 0, 'r'); + assert(zip != NULL); + + int i = 0, n = zip_total_entries(zip); + for (; i < n; ++i) { + assert(0 == zip_entry_openbyindex(zip, i)); + fprintf(stdout, "[%d]: %s", i, zip_entry_name(zip)); + if (zip_entry_isdir(zip)) { + fprintf(stdout, " (DIR)"); + } + fprintf(stdout, "\n"); + assert(0 == zip_entry_close(zip)); + } + + zip_close(zip); +} + + + int main(int argc, char *argv[]) { test_write(); test_append(); @@ -167,6 +205,8 @@ int main(int argc, char *argv[]) { test_total_entries(); test_entry_name(); test_entry_index(); + test_entry_openbyindex(); + test_list_entries(); return remove(ZIPNAME); }