diff --git a/README.md b/README.md index 5940fdc6..8d9efe51 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,27 @@ zip_stream_close(zip); free(buf); ``` +* Extract a partial zip entry + +```c +unsigned char buf[16]; +size_t bufsize = sizeof(buf); + +struct zip_t *zip = zip_open("foo.zip", 0, 'r'); +{ + zip_entry_open(zip, "foo-1.txt"); + { + size_t offset = 4; + ssize_t nread = zip_entry_noallocreadwithoffset(zip, offset, bufsize, (void *)buf); + } + + zip_entry_close(zip); +} +zip_close(zip); + +free(buf); +``` + * List of all zip entries ```c diff --git a/src/zip.c b/src/zip.c index f1238231..d00cece2 100644 --- a/src/zip.c +++ b/src/zip.c @@ -114,7 +114,7 @@ struct zip_entry_mark_t { size_t lf_length; }; -static const char *const zip_errlist[33] = { +static const char *const zip_errlist[35] = { NULL, "not initialized\0", "invalid entry name\0", @@ -148,6 +148,8 @@ static const char *const zip_errlist[33] = { "cannot initialize reader\0", "cannot initialize writer\0", "cannot initialize writer from reader\0", + "invalid argument\0", + "cannot initialize reader iterator\0", }; const char *zip_strerror(int errnum) { @@ -1654,6 +1656,75 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { return (ssize_t)zip->entry.uncomp_size; } +ssize_t zip_entry_noallocreadwithoffset(struct zip_t *zip, size_t offset, + size_t size, void *buf) { + mz_zip_archive *pzip = NULL; + + if (!zip) { + // zip_t handler is not initialized + return (ssize_t)ZIP_ENOINIT; + } + + if (offset >= (size_t)zip->entry.uncomp_size) { + return (ssize_t)ZIP_EINVAL; + } + + if ((offset + size) > (size_t)zip->entry.uncomp_size) { + size = (ssize_t)zip->entry.uncomp_size - offset; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || + zip->entry.index < (ssize_t)0) { + // the entry is not found or we do not have read access + return (ssize_t)ZIP_ENOENT; + } + + mz_zip_reader_extract_iter_state *iter = + mz_zip_reader_extract_iter_new(pzip, (mz_uint)zip->entry.index, 0); + if (!iter) { + return (ssize_t)ZIP_ENORITER; + } + + mz_uint8 *writebuf = (mz_uint8 *)buf; + size_t file_offset = 0; + size_t write_cursor = 0; + size_t to_read = size; + + // iterate until the requested offset is in range + while (file_offset < zip->entry.uncomp_size && to_read > 0) { + size_t nread = mz_zip_reader_extract_iter_read( + iter, (void *)&writebuf[write_cursor], to_read); + + if (nread == 0) + break; + + if (offset < (file_offset + nread)) { + size_t read_cursor = offset - file_offset; + MZ_ASSERT(read_cursor < size); + size_t read_size = nread - read_cursor; + + if (to_read < read_size) + read_size = to_read; + MZ_ASSERT(read_size <= size); + + // If it's an unaligned read (i.e. the first one) + if (read_cursor != 0) { + memmove(&writebuf[write_cursor], &writebuf[read_cursor], read_size); + } + + write_cursor += read_size; + offset += read_size; + to_read -= read_size; + } + + file_offset += nread; + } + + mz_zip_reader_extract_iter_free(iter); + return (ssize_t)write_cursor; +} + int zip_entry_fread(struct zip_t *zip, const char *filename) { mz_zip_archive *pzip = NULL; mz_uint idx; diff --git a/src/zip.h b/src/zip.h index dce99ffb..3c0f3c6a 100644 --- a/src/zip.h +++ b/src/zip.h @@ -96,6 +96,8 @@ typedef long ssize_t; /* byte count or error */ #define ZIP_ERINIT -30 // cannot initialize reader #define ZIP_EWINIT -31 // cannot initialize writer #define ZIP_EWRINIT -32 // cannot initialize writer from reader +#define ZIP_EINVAL -33 // invalid argument +#define ZIP_ENORITER -34 // cannot initialize reader iterator /** * Looks up the error message string corresponding to an error number. @@ -373,6 +375,27 @@ extern ZIP_EXPORT ssize_t zip_entry_read(struct zip_t *zip, void **buf, extern ZIP_EXPORT ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize); +/** + * Extracts the part of the current zip entry into a memory buffer using no + * memory allocation for the buffer. + * + * @param zip zip archive handler. + * @param offset the offset of the entry (in bytes). + * @param size requested number of bytes (in bytes). + * @param buf preallocated output buffer. + * + * @note the iterator api uses an allocation to create its state + * @note each call will iterate from the start of the entry + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a negative number (< 0) on error (e.g. offset is too + * large). + */ +extern ZIP_EXPORT ssize_t zip_entry_noallocreadwithoffset(struct zip_t *zip, + size_t offset, + size_t size, + void *buf); + /** * Extracts the current zip entry into output file. * diff --git a/test/test_read.c b/test/test_read.c index f3731bda..472601c6 100644 --- a/test/test_read.c +++ b/test/test_read.c @@ -129,11 +129,53 @@ MU_TEST(test_noallocread) { zip_close(zip); } +MU_TEST(test_noallocreadwithoffset) { + size_t expected_size = strlen(TESTDATA2); + char *expected_data = calloc(expected_size, sizeof(char)); + + struct zip_t *zip = zip_open(ZIPNAME, 0, 'r'); + mu_check(zip != NULL); + mu_assert_int_eq(1, zip_is64(zip)); + + mu_assert_int_eq(0, zip_entry_open(zip, "test/test-2.txt")); + zip_entry_noallocread(zip, (void *)expected_data, expected_size); + + // Read the file in different chunk sizes + for (size_t i = 1; i <= expected_size; ++i) { + size_t buflen = i; + char *tmpbuf = calloc(buflen, sizeof(char)); + + for (size_t j = 0; j < expected_size; ++j) { + // we test starting from different offsets, to make sure we hit the + // "unaligned" code path + size_t offset = j; + while (offset < expected_size) { + + ssize_t nread = + zip_entry_noallocreadwithoffset(zip, offset, buflen, tmpbuf); + + mu_assert(nread <= buflen, "too many bytes read"); + mu_assert(0u != nread, "no bytes read"); + + // check the data + for (ssize_t j = 0; j < nread; ++j) { + mu_assert_int_eq(expected_data[offset + j], tmpbuf[j]); + } + + offset += nread; + } + } + } + + zip_close(zip); +} + MU_TEST_SUITE(test_read_suite) { MU_SUITE_CONFIGURE(&test_setup, &test_teardown); MU_RUN_TEST(test_read); MU_RUN_TEST(test_noallocread); + MU_RUN_TEST(test_noallocreadwithoffset); } #define UNUSED(x) (void)x