1b9128a37SMartin Matuska /*- 2b9128a37SMartin Matuska * Copyright (c) 2003-2023 Tim Kientzle 3b9128a37SMartin Matuska * Copyright (c) 2008 Anselm Strauss 4b9128a37SMartin Matuska * All rights reserved. 5b9128a37SMartin Matuska * 6b9128a37SMartin Matuska * Redistribution and use in source and binary forms, with or without 7b9128a37SMartin Matuska * modification, are permitted provided that the following conditions 8b9128a37SMartin Matuska * are met: 9b9128a37SMartin Matuska * 1. Redistributions of source code must retain the above copyright 10b9128a37SMartin Matuska * notice, this list of conditions and the following disclaimer. 11b9128a37SMartin Matuska * 2. Redistributions in binary form must reproduce the above copyright 12b9128a37SMartin Matuska * notice, this list of conditions and the following disclaimer in the 13b9128a37SMartin Matuska * documentation and/or other materials provided with the distribution. 14b9128a37SMartin Matuska * 15b9128a37SMartin Matuska * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 16b9128a37SMartin Matuska * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17b9128a37SMartin Matuska * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18b9128a37SMartin Matuska * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 19b9128a37SMartin Matuska * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20b9128a37SMartin Matuska * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21b9128a37SMartin Matuska * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22b9128a37SMartin Matuska * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23b9128a37SMartin Matuska * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24b9128a37SMartin Matuska * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25b9128a37SMartin Matuska */ 26b9128a37SMartin Matuska 27b9128a37SMartin Matuska #include "test.h" 28b9128a37SMartin Matuska 29b9128a37SMartin Matuska /* 30b9128a37SMartin Matuska * Detailed byte-for-byte verification of the format of a zip archive 31b9128a37SMartin Matuska * written in streaming mode WITHOUT Zip64 extensions enabled. 32b9128a37SMartin Matuska */ 33b9128a37SMartin Matuska 34b9128a37SMartin Matuska static unsigned long 35b9128a37SMartin Matuska bitcrc32(unsigned long c, void *_p, size_t s) 36b9128a37SMartin Matuska { 37b9128a37SMartin Matuska /* This is a drop-in replacement for crc32() from zlib. 38b9128a37SMartin Matuska * Libarchive should be able to correctly generate 39b9128a37SMartin Matuska * uncompressed zip archives (including correct CRCs) even 40b9128a37SMartin Matuska * when zlib is unavailable, and this function helps us verify 41b9128a37SMartin Matuska * that. Yes, this is very, very slow and unsuitable for 42b9128a37SMartin Matuska * production use, but it's correct, compact, and works well 43b9128a37SMartin Matuska * enough for this particular usage. Libarchive internally 44b9128a37SMartin Matuska * uses a much more efficient implementation. */ 45b9128a37SMartin Matuska const unsigned char *p = _p; 46b9128a37SMartin Matuska int bitctr; 47b9128a37SMartin Matuska 48b9128a37SMartin Matuska if (p == NULL) 49b9128a37SMartin Matuska return (0); 50b9128a37SMartin Matuska 51b9128a37SMartin Matuska for (; s > 0; --s) { 52b9128a37SMartin Matuska c ^= *p++; 53b9128a37SMartin Matuska for (bitctr = 8; bitctr > 0; --bitctr) { 54b9128a37SMartin Matuska if (c & 1) c = (c >> 1); 55b9128a37SMartin Matuska else c = (c >> 1) ^ 0xedb88320; 56b9128a37SMartin Matuska c ^= 0x80000000; 57b9128a37SMartin Matuska } 58b9128a37SMartin Matuska } 59b9128a37SMartin Matuska return (c); 60b9128a37SMartin Matuska } 61b9128a37SMartin Matuska 62b9128a37SMartin Matuska /* Quick and dirty: Read 2-byte and 4-byte integers from Zip file. */ 63b9128a37SMartin Matuska static unsigned i2(const unsigned char *p) { return ((p[0] & 0xff) | ((p[1] & 0xff) << 8)); } 64b9128a37SMartin Matuska static unsigned i4(const unsigned char *p) { return (i2(p) | (i2(p + 2) << 16)); } 65b9128a37SMartin Matuska 66b9128a37SMartin Matuska DEFINE_TEST(test_write_format_zip_stream) 67b9128a37SMartin Matuska { 68b9128a37SMartin Matuska struct archive *a; 69b9128a37SMartin Matuska struct archive_entry *ae; 70b9128a37SMartin Matuska size_t used, buffsize = 1000000; 71b9128a37SMartin Matuska unsigned long crc; 72b9128a37SMartin Matuska unsigned long compressed_size = 0; 73b9128a37SMartin Matuska int file_perm = 00644; 74*bd66c1b4SMartin Matuska #ifdef HAVE_ZLIB_H 75b9128a37SMartin Matuska int zip_version = 20; 76*bd66c1b4SMartin Matuska #else 77*bd66c1b4SMartin Matuska int zip_version = 10; 78*bd66c1b4SMartin Matuska #endif 79b9128a37SMartin Matuska int zip_compression = 8; 80b9128a37SMartin Matuska short file_uid = 10, file_gid = 20; 81b9128a37SMartin Matuska unsigned char *buff, *buffend, *p; 82b9128a37SMartin Matuska unsigned char *central_header, *local_header, *eocd, *eocd_record; 83b9128a37SMartin Matuska unsigned char *extension_start, *extension_end; 84b9128a37SMartin Matuska unsigned char *data_start, *data_end; 85b9128a37SMartin Matuska char file_data[] = {'1', '2', '3', '4', '5', '6', '7', '8'}; 86b9128a37SMartin Matuska const char *file_name = "file"; 87b9128a37SMartin Matuska 88b9128a37SMartin Matuska #ifndef HAVE_ZLIB_H 89*bd66c1b4SMartin Matuska zip_version = 10; 90b9128a37SMartin Matuska zip_compression = 0; 91b9128a37SMartin Matuska #endif 92b9128a37SMartin Matuska 93b9128a37SMartin Matuska buff = malloc(buffsize); 94b9128a37SMartin Matuska 95b9128a37SMartin Matuska /* Create a new archive in memory. */ 96b9128a37SMartin Matuska assert((a = archive_write_new()) != NULL); 97b9128a37SMartin Matuska assertEqualIntA(a, ARCHIVE_OK, archive_write_set_format_zip(a)); 98b9128a37SMartin Matuska assertEqualIntA(a, ARCHIVE_OK, 99b9128a37SMartin Matuska archive_write_set_options(a, "zip:!zip64")); 100b9128a37SMartin Matuska assertEqualIntA(a, ARCHIVE_OK, 101b9128a37SMartin Matuska archive_write_open_memory(a, buff, buffsize, &used)); 102b9128a37SMartin Matuska 103b9128a37SMartin Matuska assert((ae = archive_entry_new()) != NULL); 104b9128a37SMartin Matuska archive_entry_copy_pathname(ae, file_name); 105b9128a37SMartin Matuska archive_entry_set_mode(ae, AE_IFREG | file_perm); 106b9128a37SMartin Matuska archive_entry_set_uid(ae, file_uid); 107b9128a37SMartin Matuska archive_entry_set_gid(ae, file_gid); 108b9128a37SMartin Matuska archive_entry_set_mtime(ae, 0, 0); 109b9128a37SMartin Matuska assertEqualInt(0, archive_write_header(a, ae)); 110b9128a37SMartin Matuska archive_entry_free(ae); 111b9128a37SMartin Matuska assertEqualInt(8, archive_write_data(a, file_data, sizeof(file_data))); 112b9128a37SMartin Matuska assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a)); 113b9128a37SMartin Matuska assertEqualInt(ARCHIVE_OK, archive_write_free(a)); 114b9128a37SMartin Matuska buffend = buff + used; 115b9128a37SMartin Matuska dumpfile("constructed.zip", buff, used); 116b9128a37SMartin Matuska 117b9128a37SMartin Matuska /* Verify "End of Central Directory" record. */ 118b9128a37SMartin Matuska /* Get address of end-of-central-directory record. */ 119b9128a37SMartin Matuska eocd_record = p = buffend - 22; /* Assumes there is no zip comment field. */ 120b9128a37SMartin Matuska failure("End-of-central-directory begins with PK\\005\\006 signature"); 121b9128a37SMartin Matuska assertEqualMem(p, "PK\005\006", 4); 122b9128a37SMartin Matuska failure("This must be disk 0"); 123b9128a37SMartin Matuska assertEqualInt(i2(p + 4), 0); 124b9128a37SMartin Matuska failure("Central dir must start on disk 0"); 125b9128a37SMartin Matuska assertEqualInt(i2(p + 6), 0); 126b9128a37SMartin Matuska failure("All central dir entries are on this disk"); 127b9128a37SMartin Matuska assertEqualInt(i2(p + 8), i2(p + 10)); 128b9128a37SMartin Matuska eocd = buff + i4(p + 12) + i4(p + 16); 129b9128a37SMartin Matuska failure("no zip comment"); 130b9128a37SMartin Matuska assertEqualInt(i2(p + 20), 0); 131b9128a37SMartin Matuska 132b9128a37SMartin Matuska /* Get address of first entry in central directory. */ 133b9128a37SMartin Matuska central_header = p = buff + i4(buffend - 6); 134b9128a37SMartin Matuska failure("Central file record at offset %d should begin with" 135b9128a37SMartin Matuska " PK\\001\\002 signature", 136b9128a37SMartin Matuska i4(buffend - 10)); 137b9128a37SMartin Matuska 138b9128a37SMartin Matuska /* Verify file entry in central directory. */ 139b9128a37SMartin Matuska assertEqualMem(p, "PK\001\002", 4); /* Signature */ 140b9128a37SMartin Matuska assertEqualInt(i2(p + 4), 3 * 256 + zip_version); /* Version made by */ 141b9128a37SMartin Matuska assertEqualInt(i2(p + 6), zip_version); /* Version needed to extract */ 142b9128a37SMartin Matuska assertEqualInt(i2(p + 8), 8); /* Flags */ 143b9128a37SMartin Matuska assertEqualInt(i2(p + 10), zip_compression); /* Compression method */ 144b9128a37SMartin Matuska assertEqualInt(i2(p + 12), 0); /* File time */ 145b9128a37SMartin Matuska assertEqualInt(i2(p + 14), 33); /* File date */ 146b9128a37SMartin Matuska crc = bitcrc32(0, file_data, sizeof(file_data)); 147b9128a37SMartin Matuska assertEqualInt(i4(p + 16), crc); /* CRC-32 */ 148b9128a37SMartin Matuska compressed_size = i4(p + 20); /* Compressed size */ 149b9128a37SMartin Matuska assertEqualInt(i4(p + 24), sizeof(file_data)); /* Uncompressed size */ 150b9128a37SMartin Matuska assertEqualInt(i2(p + 28), strlen(file_name)); /* Pathname length */ 151b9128a37SMartin Matuska /* assertEqualInt(i2(p + 30), 28); */ /* Extra field length: See below */ 152b9128a37SMartin Matuska assertEqualInt(i2(p + 32), 0); /* File comment length */ 153b9128a37SMartin Matuska assertEqualInt(i2(p + 34), 0); /* Disk number start */ 154b9128a37SMartin Matuska assertEqualInt(i2(p + 36), 0); /* Internal file attrs */ 155b9128a37SMartin Matuska assertEqualInt(i4(p + 38) >> 16 & 01777, file_perm); /* External file attrs */ 156b9128a37SMartin Matuska assertEqualInt(i4(p + 42), 0); /* Offset of local header */ 157b9128a37SMartin Matuska assertEqualMem(p + 46, file_name, strlen(file_name)); /* Pathname */ 158b9128a37SMartin Matuska p = extension_start = central_header + 46 + strlen(file_name); 159b9128a37SMartin Matuska extension_end = extension_start + i2(central_header + 30); 160b9128a37SMartin Matuska 161b9128a37SMartin Matuska assertEqualInt(i2(p), 0x7875); /* 'ux' extension header */ 162b9128a37SMartin Matuska assertEqualInt(i2(p + 2), 11); /* 'ux' size */ 163b9128a37SMartin Matuska assertEqualInt(p[4], 1); /* 'ux' version */ 164b9128a37SMartin Matuska assertEqualInt(p[5], 4); /* 'ux' uid size */ 165b9128a37SMartin Matuska assertEqualInt(i4(p + 6), file_uid); /* 'Ux' UID */ 166b9128a37SMartin Matuska assertEqualInt(p[10], 4); /* 'ux' gid size */ 167b9128a37SMartin Matuska assertEqualInt(i4(p + 11), file_gid); /* 'Ux' GID */ 168b9128a37SMartin Matuska p += 4 + i2(p + 2); 169b9128a37SMartin Matuska 170b9128a37SMartin Matuska assertEqualInt(i2(p), 0x5455); /* 'UT' extension header */ 171b9128a37SMartin Matuska assertEqualInt(i2(p + 2), 5); /* 'UT' size */ 172b9128a37SMartin Matuska assertEqualInt(p[4], 1); /* 'UT' flags */ 173b9128a37SMartin Matuska assertEqualInt(i4(p + 5), 0); /* 'UT' mtime */ 174b9128a37SMartin Matuska p += 4 + i2(p + 2); 175b9128a37SMartin Matuska 176b9128a37SMartin Matuska /* Note: We don't expect to see zip64 extension in the central 177b9128a37SMartin Matuska * directory, since the writer knows the actual full size by 178b9128a37SMartin Matuska * the time it is ready to write the central directory and has 179b9128a37SMartin Matuska * no reason to insert it then. Info-Zip seems to do the same 180b9128a37SMartin Matuska * thing. */ 181b9128a37SMartin Matuska 182b9128a37SMartin Matuska /* Just in case: Report any extra extensions. */ 183b9128a37SMartin Matuska while (p < extension_end) { 184b9128a37SMartin Matuska failure("Unexpected extension 0x%04X", i2(p)); 185b9128a37SMartin Matuska assert(0); 186b9128a37SMartin Matuska p += 4 + i2(p + 2); 187b9128a37SMartin Matuska } 188b9128a37SMartin Matuska 189b9128a37SMartin Matuska /* Should have run exactly to end of extra data. */ 190b9128a37SMartin Matuska assert(p == extension_end); 191b9128a37SMartin Matuska 192b9128a37SMartin Matuska assert(p == eocd); 193b9128a37SMartin Matuska assert(p == eocd_record); 194b9128a37SMartin Matuska 195b9128a37SMartin Matuska /* Verify local header of file entry. */ 196b9128a37SMartin Matuska p = local_header = buff; 197b9128a37SMartin Matuska assertEqualMem(p, "PK\003\004", 4); /* Signature */ 198b9128a37SMartin Matuska assertEqualInt(i2(p + 4), zip_version); /* Version needed to extract */ 199b9128a37SMartin Matuska assertEqualInt(i2(p + 6), 8); /* Flags */ 200b9128a37SMartin Matuska assertEqualInt(i2(p + 8), zip_compression); /* Compression method */ 201b9128a37SMartin Matuska assertEqualInt(i2(p + 10), 0); /* File time */ 202b9128a37SMartin Matuska assertEqualInt(i2(p + 12), 33); /* File date */ 203b9128a37SMartin Matuska assertEqualInt(i4(p + 14), 0); /* CRC-32 */ 204b9128a37SMartin Matuska assertEqualInt(i4(p + 18), 0); /* Compressed size */ 205b9128a37SMartin Matuska assertEqualInt(i4(p + 22), 0); /* Uncompressed size */ 206b9128a37SMartin Matuska assertEqualInt(i2(p + 26), strlen(file_name)); /* Pathname length */ 207b9128a37SMartin Matuska assertEqualInt(i2(p + 28), 24); /* Extra field length */ 208b9128a37SMartin Matuska assertEqualMem(p + 30, file_name, strlen(file_name)); /* Pathname */ 209b9128a37SMartin Matuska p = extension_start = local_header + 30 + strlen(file_name); 210b9128a37SMartin Matuska extension_end = extension_start + i2(local_header + 28); 211b9128a37SMartin Matuska 212b9128a37SMartin Matuska assertEqualInt(i2(p), 0x7875); /* 'ux' extension header */ 213b9128a37SMartin Matuska assertEqualInt(i2(p + 2), 11); /* 'ux' size */ 214b9128a37SMartin Matuska assertEqualInt(p[4], 1); /* 'ux' version */ 215b9128a37SMartin Matuska assertEqualInt(p[5], 4); /* 'ux' uid size */ 216b9128a37SMartin Matuska assertEqualInt(i4(p + 6), file_uid); /* 'Ux' UID */ 217b9128a37SMartin Matuska assertEqualInt(p[10], 4); /* 'ux' gid size */ 218b9128a37SMartin Matuska assertEqualInt(i4(p + 11), file_gid); /* 'Ux' GID */ 219b9128a37SMartin Matuska p += 4 + i2(p + 2); 220b9128a37SMartin Matuska 221b9128a37SMartin Matuska assertEqualInt(i2(p), 0x5455); /* 'UT' extension header */ 222b9128a37SMartin Matuska assertEqualInt(i2(p + 2), 5); /* 'UT' size */ 223b9128a37SMartin Matuska assertEqualInt(p[4], 1); /* 'UT' flags */ 224b9128a37SMartin Matuska assertEqualInt(i4(p + 5), 0); /* 'UT' mtime */ 225b9128a37SMartin Matuska p += 4 + i2(p + 2); 226b9128a37SMartin Matuska 227b9128a37SMartin Matuska /* Just in case: Report any extra extensions. */ 228b9128a37SMartin Matuska while (p < extension_end) { 229b9128a37SMartin Matuska failure("Unexpected extension 0x%04X", i2(p)); 230b9128a37SMartin Matuska assert(0); 231b9128a37SMartin Matuska p += 4 + i2(p + 2); 232b9128a37SMartin Matuska } 233b9128a37SMartin Matuska 234b9128a37SMartin Matuska /* Should have run exactly to end of extra data. */ 235b9128a37SMartin Matuska assert(p == extension_end); 236b9128a37SMartin Matuska data_start = p; 237b9128a37SMartin Matuska 238b9128a37SMartin Matuska /* Data descriptor should follow compressed data. */ 239b9128a37SMartin Matuska while (p < central_header && memcmp(p, "PK\007\010", 4) != 0) 240b9128a37SMartin Matuska ++p; 241b9128a37SMartin Matuska data_end = p; 242b9128a37SMartin Matuska assertEqualInt(data_end - data_start, compressed_size); 243b9128a37SMartin Matuska assertEqualMem(p, "PK\007\010", 4); 244b9128a37SMartin Matuska assertEqualInt(i4(p + 4), crc); /* CRC-32 */ 245b9128a37SMartin Matuska assertEqualInt(i4(p + 8), compressed_size); /* compressed size */ 246b9128a37SMartin Matuska assertEqualInt(i4(p + 12), sizeof(file_data)); /* uncompressed size */ 247b9128a37SMartin Matuska 248b9128a37SMartin Matuska /* Central directory should immediately follow the data descriptor. */ 249b9128a37SMartin Matuska assert(p + 16 == central_header); 250b9128a37SMartin Matuska 251b9128a37SMartin Matuska free(buff); 252b9128a37SMartin Matuska } 253