| /*- |
| * Copyright (c) 2003-2007,2013 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" |
| __FBSDID("$FreeBSD$"); |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| /* |
| * This is a somewhat tricky test that verifies the ability to |
| * write and read very large entries to zip archives. |
| * |
| * See test_tar_large.c for more information about the machinery |
| * being used here. |
| */ |
| |
| static size_t nullsize; |
| static void *nulldata; |
| |
| struct fileblock { |
| struct fileblock *next; |
| int size; |
| void *buff; |
| int64_t gap_size; /* Size of following gap */ |
| }; |
| |
| struct fileblocks { |
| int64_t filesize; |
| int64_t fileposition; |
| int64_t gap_remaining; |
| void *buff; |
| struct fileblock *first; |
| struct fileblock *current; |
| struct fileblock *last; |
| }; |
| |
| /* The following size definitions simplify things below. */ |
| #define KB ((int64_t)1024) |
| #define MB ((int64_t)1024 * KB) |
| #define GB ((int64_t)1024 * MB) |
| #define TB ((int64_t)1024 * GB) |
| |
| static int64_t memory_read_skip(struct archive *, void *, int64_t request); |
| static ssize_t memory_read(struct archive *, void *, const void **buff); |
| static ssize_t memory_write(struct archive *, void *, const void *, size_t); |
| |
| static int16_t le16(const void *_p) { |
| const uint8_t *p = _p; |
| return (0xff & (int16_t)p[0]) | ((0xff & (int16_t)p[1]) << 8); |
| } |
| |
| static int32_t le32(const void *_p) { |
| const uint8_t *p = _p; |
| int32_t v = 0xffff & (int32_t)le16(_p); |
| return v + ((0xffff & (int32_t)le16(p + 2)) << 16); |
| } |
| |
| static int64_t le64(const void *_p) { |
| const uint8_t *p = _p; |
| int64_t v = 0xffffffff & (int64_t)le32(_p); |
| return v + ((0xffffffff & (int64_t)le32(p + 4)) << 32); |
| } |
| |
| static ssize_t |
| memory_write(struct archive *a, void *_private, const void *buff, size_t size) |
| { |
| struct fileblocks *private = _private; |
| struct fileblock *block; |
| |
| (void)a; |
| |
| if ((const char *)nulldata <= (const char *)buff |
| && (const char *)buff < (const char *)nulldata + nullsize) { |
| /* We don't need to store a block of gap data. */ |
| private->last->gap_size += (int64_t)size; |
| } else { |
| /* Yes, we're assuming the very first write is metadata. */ |
| /* It's header or metadata, copy and save it. */ |
| block = (struct fileblock *)malloc(sizeof(*block)); |
| memset(block, 0, sizeof(*block)); |
| block->size = (int)size; |
| block->buff = malloc(size); |
| memcpy(block->buff, buff, size); |
| if (private->last == NULL) { |
| private->first = private->last = block; |
| } else { |
| private->last->next = block; |
| private->last = block; |
| } |
| block->next = NULL; |
| } |
| private->filesize += size; |
| return ((long)size); |
| } |
| |
| static ssize_t |
| memory_read(struct archive *a, void *_private, const void **buff) |
| { |
| struct fileblocks *private = _private; |
| ssize_t size; |
| |
| (void)a; |
| |
| while (private->current != NULL && private->buff == NULL && private->gap_remaining == 0) { |
| private->current = private->current->next; |
| if (private->current != NULL) { |
| private->buff = private->current->buff; |
| private->gap_remaining = private->current->gap_size; |
| } |
| } |
| |
| if (private->current == NULL) |
| return (0); |
| |
| /* If there's real data, return that. */ |
| if (private->buff != NULL) { |
| *buff = private->buff; |
| size = ((char *)private->current->buff + private->current->size) |
| - (char *)private->buff; |
| private->buff = NULL; |
| private->fileposition += size; |
| return (size); |
| } |
| |
| /* Big gap: too big to return all at once, so just return some. */ |
| if (private->gap_remaining > (int64_t)nullsize) { |
| private->gap_remaining -= nullsize; |
| *buff = nulldata; |
| private->fileposition += nullsize; |
| return (nullsize); |
| } |
| |
| /* Small gap: finish the gap and prep for next block. */ |
| if (private->gap_remaining > 0) { |
| size = (ssize_t)private->gap_remaining; |
| *buff = nulldata; |
| private->gap_remaining = 0; |
| private->fileposition += size; |
| |
| private->current = private->current->next; |
| if (private->current != NULL) { |
| private->buff = private->current->buff; |
| private->gap_remaining = private->current->gap_size; |
| } |
| |
| return (size); |
| } |
| fprintf(stderr, "\n\n\nInternal failure\n\n\n"); |
| exit(1); |
| } |
| |
| static int |
| memory_read_open(struct archive *a, void *_private) |
| { |
| struct fileblocks *private = _private; |
| |
| (void)a; /* UNUSED */ |
| |
| private->current = private->first; |
| private->fileposition = 0; |
| if (private->current != NULL) { |
| private->buff = private->current->buff; |
| private->gap_remaining = private->current->gap_size; |
| } |
| return (ARCHIVE_OK); |
| } |
| |
| static int64_t |
| memory_read_seek(struct archive *a, void *_private, int64_t offset, int whence) |
| { |
| struct fileblocks *private = _private; |
| |
| (void)a; |
| if (whence == SEEK_END) { |
| offset = private->filesize + offset; |
| } else if (whence == SEEK_CUR) { |
| offset = private->fileposition + offset; |
| } |
| |
| if (offset < 0) { |
| fprintf(stderr, "\n\n\nInternal failure: negative seek\n\n\n"); |
| exit(1); |
| } |
| |
| /* We've converted the request into a SEEK_SET. */ |
| private->fileposition = offset; |
| |
| /* Walk the block list to find the new position. */ |
| offset = 0; |
| private->current = private->first; |
| while (private->current != NULL) { |
| if (offset + private->current->size > private->fileposition) { |
| /* Position is in this block. */ |
| private->buff = (char *)private->current->buff |
| + private->fileposition - offset; |
| private->gap_remaining = private->current->gap_size; |
| return private->fileposition; |
| } |
| offset += private->current->size; |
| if (offset + private->current->gap_size > private->fileposition) { |
| /* Position is in this gap. */ |
| private->buff = NULL; |
| private->gap_remaining = private->current->gap_size |
| - (private->fileposition - offset); |
| return private->fileposition; |
| } |
| offset += private->current->gap_size; |
| /* Skip to next block. */ |
| private->current = private->current->next; |
| } |
| if (private->fileposition == private->filesize) { |
| return private->fileposition; |
| } |
| fprintf(stderr, "\n\n\nInternal failure: over-sized seek\n\n\n"); |
| exit(1); |
| } |
| |
| static int64_t |
| memory_read_skip(struct archive *a, void *_private, int64_t skip) |
| { |
| struct fileblocks *private = _private; |
| int64_t old_position = private->fileposition; |
| int64_t new_position = memory_read_seek(a, _private, skip, SEEK_CUR); |
| return (new_position - old_position); |
| } |
| |
| static struct fileblocks * |
| fileblocks_new(void) |
| { |
| struct fileblocks *fileblocks; |
| |
| fileblocks = calloc(1, sizeof(struct fileblocks)); |
| return fileblocks; |
| } |
| |
| static void |
| fileblocks_free(struct fileblocks *fileblocks) |
| { |
| while (fileblocks->first != NULL) { |
| struct fileblock *b = fileblocks->first; |
| fileblocks->first = fileblocks->first->next; |
| free(b->buff); |
| free(b); |
| } |
| free(fileblocks); |
| } |
| |
| |
| /* The sizes of the entries we're going to generate. */ |
| static int64_t test_sizes[] = { |
| /* Test for 32-bit signed overflow. */ |
| 2 * GB - 1, 2 * GB, 2 * GB + 1, |
| /* Test for 32-bit unsigned overflow. */ |
| 4 * GB - 1, 4 * GB, 4 * GB + 1, |
| /* And beyond ... because we can. */ |
| 16 * GB - 1, 16 * GB, 16 * GB + 1, |
| 64 * GB - 1, 64 * GB, 64 * GB + 1, |
| 256 * GB - 1, 256 * GB, 256 * GB + 1, |
| 1 * TB, |
| 0 |
| }; |
| |
| |
| static void |
| verify_large_zip(struct archive *a, struct fileblocks *fileblocks) |
| { |
| char namebuff[64]; |
| struct archive_entry *ae; |
| int i; |
| |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_set_options(a, "zip:ignorecrc32")); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_set_open_callback(a, memory_read_open)); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_set_read_callback(a, memory_read)); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_set_skip_callback(a, memory_read_skip)); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_set_seek_callback(a, memory_read_seek)); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_set_callback_data(a, fileblocks)); |
| assertEqualIntA(a, ARCHIVE_OK, archive_read_open1(a)); |
| |
| /* |
| * Read entries back. |
| */ |
| for (i = 0; test_sizes[i] > 0; i++) { |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_next_header(a, &ae)); |
| sprintf(namebuff, "file_%d", i); |
| assertEqualString(namebuff, archive_entry_pathname(ae)); |
| assertEqualInt(test_sizes[i], archive_entry_size(ae)); |
| } |
| assertEqualIntA(a, 0, archive_read_next_header(a, &ae)); |
| assertEqualString("lastfile", archive_entry_pathname(ae)); |
| |
| assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); |
| |
| /* Close out the archive. */ |
| assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a)); |
| } |
| |
| DEFINE_TEST(test_write_format_zip_large) |
| { |
| int i; |
| char namebuff[64]; |
| struct fileblocks *fileblocks = fileblocks_new(); |
| struct archive_entry *ae; |
| struct archive *a; |
| const char *p; |
| const char *cd_start, *zip64_eocd, *zip64_locator, *eocd; |
| int64_t cd_size; |
| char *buff; |
| int64_t filesize; |
| size_t writesize, buffsize, s; |
| |
| nullsize = (size_t)(1 * MB); |
| nulldata = malloc(nullsize); |
| memset(nulldata, 0xAA, nullsize); |
| |
| /* |
| * Open an archive for writing. |
| */ |
| a = archive_write_new(); |
| archive_write_set_format_zip(a); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_write_set_options(a, "zip:compression=store")); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_write_set_options(a, "zip:fakecrc32")); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_write_set_bytes_per_block(a, 0)); /* No buffering. */ |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_write_open(a, fileblocks, NULL, memory_write, NULL)); |
| |
| /* |
| * Write a series of large files to it. |
| */ |
| for (i = 0; test_sizes[i] != 0; i++) { |
| assert((ae = archive_entry_new()) != NULL); |
| sprintf(namebuff, "file_%d", i); |
| archive_entry_copy_pathname(ae, namebuff); |
| archive_entry_set_mode(ae, S_IFREG | 0755); |
| filesize = test_sizes[i]; |
| archive_entry_set_size(ae, filesize); |
| |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_write_header(a, ae)); |
| archive_entry_free(ae); |
| |
| /* |
| * Write the actual data to the archive. |
| */ |
| while (filesize > 0) { |
| writesize = nullsize; |
| if ((int64_t)writesize > filesize) |
| writesize = (size_t)filesize; |
| assertEqualIntA(a, (int)writesize, |
| (int)archive_write_data(a, nulldata, writesize)); |
| filesize -= writesize; |
| } |
| } |
| |
| assert((ae = archive_entry_new()) != NULL); |
| archive_entry_copy_pathname(ae, "lastfile"); |
| archive_entry_set_mode(ae, S_IFREG | 0755); |
| assertA(0 == archive_write_header(a, ae)); |
| archive_entry_free(ae); |
| |
| /* Close out the archive. */ |
| assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a)); |
| assertEqualInt(ARCHIVE_OK, archive_write_free(a)); |
| |
| /* |
| * Read back with seeking reader: |
| */ |
| a = archive_read_new(); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_support_format_zip_seekable(a)); |
| verify_large_zip(a, fileblocks); |
| assertEqualInt(ARCHIVE_OK, archive_read_free(a)); |
| |
| /* |
| * Read back with streaming reader: |
| */ |
| a = archive_read_new(); |
| assertEqualIntA(a, ARCHIVE_OK, |
| archive_read_support_format_zip_streamable(a)); |
| verify_large_zip(a, fileblocks); |
| assertEqualInt(ARCHIVE_OK, archive_read_free(a)); |
| |
| /* |
| * Manually verify some of the final bytes of the archives. |
| */ |
| /* Collect the final bytes together */ |
| #define FINAL_SIZE 8192 |
| buff = malloc(FINAL_SIZE); |
| buffsize = 0; |
| memory_read_open(NULL, fileblocks); |
| memory_read_seek(NULL, fileblocks, -FINAL_SIZE, SEEK_END); |
| while ((s = memory_read(NULL, fileblocks, (const void **)&p)) > 0) { |
| memcpy(buff + buffsize, p, s); |
| buffsize += s; |
| } |
| assertEqualInt(buffsize, FINAL_SIZE); |
| |
| p = buff + buffsize; |
| |
| /* Verify regular end-of-central-directory record */ |
| eocd = p - 22; |
| assertEqualMem(eocd, "PK\005\006\0\0\0\0", 8); |
| assertEqualMem(eocd + 8, "\021\0\021\0", 4); /* 17 entries total */ |
| cd_size = le32(eocd + 12); |
| /* Start of CD offset should be 0xffffffff */ |
| assertEqualMem(eocd + 16, "\xff\xff\xff\xff", 4); |
| assertEqualMem(eocd + 20, "\0\0", 2); /* No Zip comment */ |
| |
| /* Verify Zip64 locator */ |
| zip64_locator = p - 42; |
| assertEqualMem(zip64_locator, "PK\006\007\0\0\0\0", 8); |
| zip64_eocd = p - (fileblocks->filesize - le64(zip64_locator + 8)); |
| assertEqualMem(zip64_locator + 16, "\001\0\0\0", 4); |
| |
| /* Verify Zip64 end-of-cd record. */ |
| assert(zip64_eocd == p - 98); |
| assertEqualMem(zip64_eocd, "PK\006\006", 4); |
| assertEqualInt(44, le64(zip64_eocd + 4)); // Size of EoCD record - 12 |
| assertEqualMem(zip64_eocd + 12, "\055\0", 2); // Made by version: 45 |
| assertEqualMem(zip64_eocd + 14, "\055\0", 2); // Requires version: 45 |
| assertEqualMem(zip64_eocd + 16, "\0\0\0\0", 4); // This disk |
| assertEqualMem(zip64_eocd + 20, "\0\0\0\0", 4); // Total disks |
| assertEqualInt(17, le64(zip64_eocd + 24)); // Entries on this disk |
| assertEqualInt(17, le64(zip64_eocd + 32)); // Total entries |
| cd_size = le64(zip64_eocd + 40); |
| cd_start = p - (fileblocks->filesize - le64(zip64_eocd + 48)); |
| |
| assert(cd_start + cd_size == zip64_eocd); |
| |
| assertEqualInt(le64(zip64_eocd + 48) // Start of CD |
| + cd_size |
| + 56 // Size of Zip64 EOCD |
| + 20 // Size of Zip64 locator |
| + 22, // Size of EOCD |
| fileblocks->filesize); |
| |
| // TODO: Scan entire Central Directory, sanity-check all data |
| assertEqualMem(cd_start, "PK\001\002", 4); |
| |
| fileblocks_free(fileblocks); |
| free(buff); |
| free(nulldata); |
| } |