/* * The extra data is stored as a list of * id1+size1+data1 + id2+size2+data2 ... * triplets. id and size are 2 bytes each. */ static void process_extra(const void* extra, struct zip* zip) { int offset = 0; const char *p = (const char *)extra; while (offset < zip->extra_length - 4) { unsigned short headerid = archive_le16dec(p + offset); unsigned short datasize = archive_le16dec(p + offset + 2); offset += 4; if (offset + datasize > zip->extra_length) break; #ifdef DEBUG fprintf(stderr, "Header id 0x%04x, length %d\n", headerid, datasize); #endif switch (headerid) { case 0x0001: /* Zip64 extended information extra field. */ if (datasize >= 8) zip->uncompressed_size = archive_le64dec(p + offset); if (datasize >= 16) zip->compressed_size = archive_le64dec(p + offset + 8); break; case 0x5455: { /* Extended time field "UT". */ int flags = p[offset]; offset++; datasize--; /* Flag bits indicate which dates are present. */ if (flags & 0x01) { #ifdef DEBUG fprintf(stderr, "mtime: %lld -> %d\n", (long long)zip->mtime, archive_le32dec(p + offset)); #endif if (datasize < 4) break; zip->mtime = archive_le32dec(p + offset); offset += 4; datasize -= 4; } if (flags & 0x02) { if (datasize < 4) break; zip->atime = archive_le32dec(p + offset); offset += 4; datasize -= 4; } if (flags & 0x04) { if (datasize < 4) break; zip->ctime = archive_le32dec(p + offset); offset += 4; datasize -= 4; } break; } case 0x7855: /* Info-ZIP Unix Extra Field (type 2) "Ux". */ #ifdef DEBUG fprintf(stderr, "uid %d gid %d\n", archive_le16dec(p + offset), archive_le16dec(p + offset + 2)); #endif if (datasize >= 2) zip->uid = archive_le16dec(p + offset); if (datasize >= 4) zip->gid = archive_le16dec(p + offset + 2); break; default: break; } offset += datasize; } #ifdef DEBUG if (offset != zip->extra_length) { fprintf(stderr, "Extra data field contents do not match reported size!"); } #endif }
int zip_read_file_header(struct archive_read *a, struct archive_entry *entry, struct zip *zip) { const struct zip_file_header *p; const void *h; if ((p = __archive_read_ahead(a, sizeof *p)) == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated ZIP file header"); return (ARCHIVE_FATAL); } zip->version = p->version[0]; zip->system = p->version[1]; zip->flags = archive_le16dec(p->flags); zip->compression = archive_le16dec(p->compression); if (zip->compression < sizeof(compression_names)/sizeof(compression_names[0])) zip->compression_name = compression_names[zip->compression]; else zip->compression_name = "??"; zip->mtime = zip_time(p->timedate); zip->ctime = 0; zip->atime = 0; zip->mode = 0; zip->uid = 0; zip->gid = 0; zip->crc32 = archive_le32dec(p->crc32); zip->filename_length = archive_le16dec(p->filename_length); zip->extra_length = archive_le16dec(p->extra_length); zip->uncompressed_size = archive_le32dec(p->uncompressed_size); zip->compressed_size = archive_le32dec(p->compressed_size); (a->decompressor->consume)(a, sizeof(struct zip_file_header)); /* Read the filename. */ if ((h = __archive_read_ahead(a, zip->filename_length)) == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated ZIP file header"); return (ARCHIVE_FATAL); } if (archive_string_ensure(&zip->pathname, zip->filename_length) == NULL) __archive_errx(1, "Out of memory"); archive_strncpy(&zip->pathname, h, zip->filename_length); (a->decompressor->consume)(a, zip->filename_length); archive_entry_set_pathname(entry, zip->pathname.s); if (zip->pathname.s[archive_strlen(&zip->pathname) - 1] == '/') zip->mode = AE_IFDIR | 0777; else zip->mode = AE_IFREG | 0777; /* Read the extra data. */ if ((h = __archive_read_ahead(a, zip->extra_length)) == NULL) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Truncated ZIP file header"); return (ARCHIVE_FATAL); } process_extra(h, zip); (a->decompressor->consume)(a, zip->extra_length); /* Populate some additional entry fields: */ archive_entry_set_mode(entry, zip->mode); archive_entry_set_uid(entry, zip->uid); archive_entry_set_gid(entry, zip->gid); archive_entry_set_mtime(entry, zip->mtime, 0); archive_entry_set_ctime(entry, zip->ctime, 0); archive_entry_set_atime(entry, zip->atime, 0); archive_entry_set_size(entry, zip->uncompressed_size); zip->entry_bytes_remaining = zip->compressed_size; zip->entry_offset = 0; /* If there's no body, force read_data() to return EOF immediately. */ if (0 == (zip->flags & ZIP_LENGTH_AT_END) && zip->entry_bytes_remaining < 1) zip->end_of_entry = 1; /* Set up a more descriptive format name. */ sprintf(zip->format_name, "ZIP %d.%d (%s)", zip->version / 10, zip->version % 10, zip->compression_name); a->archive.archive_format_name = zip->format_name; return (ARCHIVE_OK); }
/* * TODO: This is a performance sink because it forces the read core to * drop buffered data from the start of file, which will then have to * be re-read again if this bidder loses. * * We workaround this a little by passing in the best bid so far so * that later bidders can do nothing if they know they'll never * outbid. But we can certainly do better... */ static int archive_read_format_zip_seekable_bid(struct archive_read *a, int best_bid) { struct zip *zip = (struct zip *)a->format->data; int64_t filesize; const char *p; /* If someone has already bid more than 32, then avoid trashing the look-ahead buffers with a seek. */ if (best_bid > 32) return (-1); filesize = __archive_read_seek(a, -22, SEEK_END); /* If we can't seek, then we can't bid. */ if (filesize <= 0) return 0; /* TODO: More robust search for end of central directory record. */ if ((p = __archive_read_ahead(a, 22, NULL)) == NULL) return 0; /* First four bytes are signature for end of central directory record. Four zero bytes ensure this isn't a multi-volume Zip file (which we don't yet support). */ if (memcmp(p, "PK\005\006\000\000\000\000", 8) != 0) { int64_t i, tail; int found; /* * If there is a comment in end of central directory * record, 22 bytes are too short. we have to read more * to properly detect the record. Hopefully, a length * of the comment is not longer than 16362 bytes(16K-22). */ if (filesize + 22 > 1024 * 16) { tail = 1024 * 16; filesize = __archive_read_seek(a, tail * -1, SEEK_END); } else { tail = filesize + 22; filesize = __archive_read_seek(a, 0, SEEK_SET); } if (filesize < 0) return 0; if ((p = __archive_read_ahead(a, (size_t)tail, NULL)) == NULL) return 0; for (found = 0, i = 0;!found && i < tail - 22;) { switch (p[i]) { case 'P': if (memcmp(p+i, "PK\005\006\000\000\000\000", 8) == 0) { p += i; filesize += tail - (22 + archive_le16dec(p+20)); found = 1; } else i += 8; break; case 'K': i += 7; break; case 005: i += 6; break; case 006: i += 5; break; default: i += 1; break; } } if (!found) return 0; } /* Since we've already done the hard work of finding the end of central directory record, let's save the important information. */ zip->central_directory_entries = archive_le16dec(p + 10); zip->central_directory_size = archive_le32dec(p + 12); zip->central_directory_offset = archive_le32dec(p + 16); zip->end_of_central_directory_offset = filesize; /* Just one volume, so central dir must all be on this volume. */ if (zip->central_directory_entries != archive_le16dec(p + 8)) return 0; /* Central directory can't extend beyond end of this file. */ if (zip->central_directory_offset + (int64_t)zip->central_directory_size > filesize) return 0; /* This is just a tiny bit higher than the maximum returned by the streaming Zip bidder. This ensures that the more accurate seeking Zip parser wins whenever seek is available. */ return 32; }
static int slurp_central_directory(struct archive_read *a, struct zip *zip) { unsigned i; int64_t correction; static const struct archive_rb_tree_ops rb_ops = { &cmp_node, &cmp_key }; static const struct archive_rb_tree_ops rb_rsrc_ops = { &rsrc_cmp_node, &rsrc_cmp_key }; /* * Consider the archive file we are reading may be SFX. * So we have to calculate a SFX header size to revise * ZIP header offsets. */ correction = zip->end_of_central_directory_offset - (zip->central_directory_offset + zip->central_directory_size); /* The central directory offset is relative value, and so * we revise this offset for SFX. */ zip->central_directory_offset += correction; __archive_read_seek(a, zip->central_directory_offset, SEEK_SET); zip->offset = zip->central_directory_offset; __archive_rb_tree_init(&zip->tree, &rb_ops); __archive_rb_tree_init(&zip->tree_rsrc, &rb_rsrc_ops); zip->zip_entries = calloc(zip->central_directory_entries, sizeof(struct zip_entry)); for (i = 0; i < zip->central_directory_entries; ++i) { struct zip_entry *zip_entry = &zip->zip_entries[i]; size_t filename_length, extra_length, comment_length; uint32_t external_attributes; const char *name, *p, *r; if ((p = __archive_read_ahead(a, 46, NULL)) == NULL) return ARCHIVE_FATAL; if (memcmp(p, "PK\001\002", 4) != 0) { archive_set_error(&a->archive, -1, "Invalid central directory signature"); return ARCHIVE_FATAL; } zip->have_central_directory = 1; /* version = p[4]; */ zip_entry->system = p[5]; /* version_required = archive_le16dec(p + 6); */ zip_entry->flags = archive_le16dec(p + 8); zip_entry->compression = (char)archive_le16dec(p + 10); zip_entry->mtime = zip_time(p + 12); zip_entry->crc32 = archive_le32dec(p + 16); zip_entry->compressed_size = archive_le32dec(p + 20); zip_entry->uncompressed_size = archive_le32dec(p + 24); filename_length = archive_le16dec(p + 28); extra_length = archive_le16dec(p + 30); comment_length = archive_le16dec(p + 32); /* disk_start = archive_le16dec(p + 34); */ /* Better be zero. */ /* internal_attributes = archive_le16dec(p + 36); */ /* text bit */ external_attributes = archive_le32dec(p + 38); zip_entry->local_header_offset = archive_le32dec(p + 42) + correction; /* If we can't guess the mode, leave it zero here; when we read the local file header we might get more information. */ zip_entry->mode = 0; if (zip_entry->system == 3) { zip_entry->mode = external_attributes >> 16; } /* * Mac resource fork files are stored under the * "__MACOSX/" directory, so we should check if * it is. */ /* Make sure we have the file name. */ if ((p = __archive_read_ahead(a, 46 + filename_length, NULL)) == NULL) return ARCHIVE_FATAL; name = p + 46; r = rsrc_basename(name, filename_length); if (filename_length >= 9 && strncmp("__MACOSX/", name, 9) == 0) { /* If this file is not a resource fork nor * a directory. We should treat it as a non * resource fork file to expose it. */ if (name[filename_length-1] != '/' && (r - name < 3 || r[0] != '.' || r[1] != '_')) { __archive_rb_tree_insert_node(&zip->tree, &zip_entry->node); /* Expose its parent directories. */ expose_parent_dirs(zip, name, filename_length); } else { /* This file is a resource fork file or * a directory. */ archive_strncpy(&(zip_entry->rsrcname), name, filename_length); __archive_rb_tree_insert_node(&zip->tree_rsrc, &zip_entry->node); } } else { /* Generate resource fork name to find its resource * file at zip->tree_rsrc. */ archive_strcpy(&(zip_entry->rsrcname), "__MACOSX/"); archive_strncat(&(zip_entry->rsrcname), name, r - name); archive_strcat(&(zip_entry->rsrcname), "._"); archive_strncat(&(zip_entry->rsrcname), name + (r - name), filename_length - (r - name)); /* Register an entry to RB tree to sort it by * file offset. */ __archive_rb_tree_insert_node(&zip->tree, &zip_entry->node); } /* We don't read the filename until we get to the local file header. Reading it here would speed up table-of-contents operations (removing the need to find and read local file header to get the filename) at the cost of requiring a lot of extra space. */ /* We don't read the extra block here. We assume it will be duplicated at the local file header. */ __archive_read_consume(a, 46 + filename_length + extra_length + comment_length); }
static int archive_write_zip_finish_entry(struct archive_write *a) { struct zip *zip = a->format_data; int ret; #if HAVE_ZLIB_H if (zip->entry_compression == COMPRESSION_DEFLATE) { for (;;) { size_t remainder; ret = deflate(&zip->stream, Z_FINISH); if (ret == Z_STREAM_ERROR) return (ARCHIVE_FATAL); remainder = zip->len_buf - zip->stream.avail_out; ret = __archive_write_output(a, zip->buf, remainder); if (ret != ARCHIVE_OK) return (ret); zip->entry_compressed_written += remainder; zip->written_bytes += remainder; zip->stream.next_out = zip->buf; if (zip->stream.avail_out != 0) break; zip->stream.avail_out = (uInt)zip->len_buf; } deflateEnd(&zip->stream); } #endif /* Write trailing data descriptor. */ if ((zip->entry_flags & ZIP_ENTRY_FLAG_LENGTH_AT_END) != 0) { char d[24]; memcpy(d, "PK\007\010", 4); archive_le32enc(d + 4, zip->entry_crc32); if (zip->entry_uses_zip64) { archive_le64enc(d + 8, (uint64_t)zip->entry_compressed_written); archive_le64enc(d + 16, (uint64_t)zip->entry_uncompressed_written); ret = __archive_write_output(a, d, 24); zip->written_bytes += 24; } else { archive_le32enc(d + 8, (uint32_t)zip->entry_compressed_written); archive_le32enc(d + 12, (uint32_t)zip->entry_uncompressed_written); ret = __archive_write_output(a, d, 16); zip->written_bytes += 16; } if (ret != ARCHIVE_OK) return (ARCHIVE_FATAL); } /* Append Zip64 extra data to central directory information. */ if (zip->entry_compressed_written > ARCHIVE_LITERAL_LL(0xffffffff) || zip->entry_uncompressed_written > ARCHIVE_LITERAL_LL(0xffffffff) || zip->entry_offset > ARCHIVE_LITERAL_LL(0xffffffff)) { unsigned char zip64[32]; unsigned char *z = zip64, *zd; memcpy(z, "\001\000\000\000", 4); z += 4; if (zip->entry_uncompressed_written >= ARCHIVE_LITERAL_LL(0xffffffff)) { archive_le64enc(z, zip->entry_uncompressed_written); z += 8; } if (zip->entry_compressed_written >= ARCHIVE_LITERAL_LL(0xffffffff)) { archive_le64enc(z, zip->entry_compressed_written); z += 8; } if (zip->entry_offset >= ARCHIVE_LITERAL_LL(0xffffffff)) { archive_le64enc(z, zip->entry_offset); z += 8; } archive_le16enc(zip64 + 2, z - (zip64 + 4)); zd = cd_alloc(zip, z - zip64); if (zd == NULL) { archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data"); return (ARCHIVE_FATAL); } memcpy(zd, zip64, z - zip64); /* Zip64 means version needs to be set to at least 4.5 */ if (archive_le16dec(zip->file_header + 6) < 45) archive_le16enc(zip->file_header + 6, 45); } /* Fix up central directory file header. */ archive_le32enc(zip->file_header + 16, zip->entry_crc32); archive_le32enc(zip->file_header + 20, zipmin(zip->entry_compressed_written, ARCHIVE_LITERAL_LL(0xffffffff))); archive_le32enc(zip->file_header + 24, zipmin(zip->entry_uncompressed_written, ARCHIVE_LITERAL_LL(0xffffffff))); archive_le16enc(zip->file_header + 30, zip->central_directory_bytes - zip->file_header_extra_offset); archive_le32enc(zip->file_header + 42, zipmin(zip->entry_offset, ARCHIVE_LITERAL_LL(0xffffffff))); return (ARCHIVE_OK); }