Kaizen/external/unarr/zip/zip.c

219 lines
7.3 KiB
C

/* Copyright 2015 the unarr project authors (see AUTHORS file).
License: LGPLv3 */
#include "zip.h"
static void zip_close(ar_archive *ar)
{
ar_archive_zip *zip = (ar_archive_zip *)ar;
free(zip->entry.name);
free(zip->entry.raw_name);
zip_clear_uncompress(&zip->uncomp);
}
static bool zip_parse_local_entry(ar_archive *ar, off64_t offset)
{
ar_archive_zip *zip = (ar_archive_zip *)ar;
struct zip_entry entry;
offset = zip_find_next_local_file_entry(ar->stream, offset);
if (offset < 0) {
if (ar->entry_offset_next)
ar->at_eof = true;
else
warn("Work around failed, no entries found in this file");
return false;
}
if (!ar_seek(ar->stream, offset, SEEK_SET)) {
warn("Couldn't seek to offset %" PRIi64, offset);
return false;
}
if (!zip_parse_local_file_entry(zip, &entry))
return false;
ar->entry_offset = offset;
ar->entry_offset_next = offset + ZIP_LOCAL_ENTRY_FIXED_SIZE + entry.namelen + entry.extralen + (off64_t)entry.datasize;
if (ar->entry_offset_next <= ar->entry_offset) {
warn("Compressed size is too large (%" PRIu64 ")", entry.datasize);
return false;
}
ar->entry_size_uncompressed = (size_t)entry.uncompressed;
ar->entry_filetime = ar_conv_dosdate_to_filetime(entry.dosdate);
zip->entry.offset = offset;
zip->entry.method = entry.method;
zip->entry.flags = entry.flags;
zip->entry.crc = entry.crc;
free(zip->entry.name);
zip->entry.name = NULL;
free(zip->entry.raw_name);
zip->entry.raw_name = NULL;
zip->entry.dosdate = entry.dosdate;
zip->progress.crc = 0;
zip->progress.bytes_done = 0;
zip->progress.data_left = (size_t)entry.datasize;
zip_clear_uncompress(&zip->uncomp);
if (entry.datasize == 0 && ar_entry_get_name(ar) &&
zip->entry.name != NULL && *zip->entry.name &&
zip->entry.name[strlen(zip->entry.name) - 1] == '/') {
log("Skipping directory entry \"%s\"", zip->entry.name);
return zip_parse_local_entry(ar, ar->entry_offset_next);
}
if (entry.datasize == 0 && entry.uncompressed == 0 && (entry.flags & (1 << 3))) {
warn("Deferring sizes to data descriptor isn't supported");
ar->entry_size_uncompressed = 1;
}
return true;
}
static bool zip_parse_entry(ar_archive *ar, off64_t offset)
{
ar_archive_zip *zip = (ar_archive_zip *)ar;
struct zip_entry entry;
if (offset >= zip->dir.end_offset) {
ar->at_eof = true;
return false;
}
if (!ar_seek(ar->stream, offset, SEEK_SET)) {
warn("Couldn't seek to offset %" PRIi64, offset);
return false;
}
if (!zip_parse_directory_entry(zip, &entry)) {
warn("Couldn't read directory entry @%" PRIi64, offset);
return false;
}
ar->entry_offset = offset;
ar->entry_offset_next = offset + ZIP_DIR_ENTRY_FIXED_SIZE + entry.namelen + entry.extralen + entry.commentlen;
ar->entry_size_uncompressed = (size_t)entry.uncompressed;
ar->entry_filetime = ar_conv_dosdate_to_filetime(entry.dosdate);
zip->entry.offset = entry.header_offset;
zip->entry.method = entry.method;
zip->entry.flags = entry.flags;
zip->entry.crc = entry.crc;
free(zip->entry.name);
zip->entry.name = NULL;
free(zip->entry.raw_name);
zip->entry.raw_name = NULL;
zip->entry.dosdate = entry.dosdate;
zip->progress.crc = 0;
zip->progress.bytes_done = 0;
zip->progress.data_left = (size_t)entry.datasize;
zip_clear_uncompress(&zip->uncomp);
if (entry.datasize == 0 && ((entry.version >> 8) == 0 || (entry.version >> 8) == 3) && (entry.attr_external & 0x40000010)) {
log("Skipping directory entry \"%s\"", zip_get_name(ar, false));
return zip_parse_entry(ar, ar->entry_offset_next);
}
return true;
}
static bool zip_copy_stored(ar_archive_zip *zip, void *buffer, size_t count)
{
if (count > zip->progress.data_left) {
warn("Unexpected EOS in stored data");
return false;
}
if (ar_read(zip->super.stream, buffer, count) != count) {
warn("Unexpected EOF in stored data");
return false;
}
zip->progress.data_left -= count;
zip->progress.bytes_done += count;
return true;
}
static bool zip_uncompress(ar_archive *ar, void *buffer, size_t count)
{
ar_archive_zip *zip = (ar_archive_zip *)ar;
if (zip->progress.bytes_done == 0) {
if ((zip->entry.flags & ((1 << 0) | (1 << 6)))) {
warn("Encrypted archives aren't supported");
return false;
}
if (!zip_seek_to_compressed_data(zip)) {
warn("Couldn't find data for file");
return false;
}
}
if (count > ar->entry_size_uncompressed - zip->progress.bytes_done) {
warn("Requesting too much data (%" PRIuPTR " < %" PRIuPTR ")", ar->entry_size_uncompressed - zip->progress.bytes_done, count);
return false;
}
if (zip->entry.method == METHOD_STORE) {
if (!zip_copy_stored(zip, buffer, count))
return false;
}
else if (zip->deflatedonly && zip->entry.method != METHOD_DEFLATE) {
warn("Only store and deflate compression methods are allowed");
return false;
}
else {
if (!zip_uncompress_part(zip, buffer, count))
return false;
}
zip->progress.crc = ar_crc32(zip->progress.crc, buffer, count);
if (zip->progress.bytes_done < ar->entry_size_uncompressed)
return true;
if (zip->uncomp.initialized ? !zip->uncomp.input.at_eof || zip->uncomp.input.bytes_left : zip->progress.data_left)
log("Compressed block has more data than required");
if (zip->progress.crc != zip->entry.crc) {
warn("Checksum of extracted data doesn't match");
return false;
}
return true;
}
static size_t zip_get_global_comment(ar_archive *ar, void *buffer, size_t count)
{
ar_archive_zip *zip = (ar_archive_zip *)ar;
if (!zip->comment_size)
return 0;
if (!buffer)
return zip->comment_size;
if (!ar_seek(ar->stream, zip->comment_offset, SEEK_SET))
return 0;
if (count > zip->comment_size)
count = zip->comment_size;
return ar_read(ar->stream, buffer, count);
}
ar_archive *ar_open_zip_archive(ar_stream *stream, bool deflatedonly)
{
ar_archive *ar;
ar_archive_zip *zip;
struct zip_eocd64 eocd = { 0 };
off64_t offset = zip_find_end_of_central_directory(stream);
if (offset < 0)
return NULL;
if (!ar_seek(stream, offset, SEEK_SET))
return NULL;
if (!zip_parse_end_of_central_directory(stream, &eocd))
return NULL;
ar = ar_open_archive(stream, sizeof(ar_archive_zip), zip_close, zip_parse_entry, zip_get_name, zip_uncompress, zip_get_global_comment, eocd.dir_offset);
if (!ar)
return NULL;
zip = (ar_archive_zip *)ar;
zip->dir.end_offset = zip_find_end_of_last_directory_entry(stream, &eocd);
if (zip->dir.end_offset < 0) {
warn("Couldn't read central directory @%" PRIi64 ", trying to work around...", eocd.dir_offset);
ar->parse_entry = zip_parse_local_entry;
ar->entry_offset_first = ar->entry_offset_next = 0;
}
zip->deflatedonly = deflatedonly;
zip->comment_offset = offset + ZIP_END_OF_CENTRAL_DIR_SIZE;
zip->comment_size = eocd.commentlen;
return ar;
}