off64_t zip_find_end_of_last_directory_entry(ar_stream *stream, struct zip_eocd64 *eocd) { uint8_t data[ZIP_DIR_ENTRY_FIXED_SIZE]; uint64_t i; if (!ar_seek(stream, eocd->dir_offset, SEEK_SET)) return -1; for (i = 0; i < eocd->numentries; i++) { if (ar_read(stream, data, sizeof(data)) != sizeof(data)) return -1; if (uint32le(data + 0) != SIG_CENTRAL_DIRECTORY) return -1; if (!ar_skip(stream, uint16le(data + 28) + uint16le(data + 30) + uint16le(data + 32))) return -1; } return ar_tell(stream); }
static bool zip_parse_extra_fields(ar_archive_zip *zip, struct zip_entry *entry) { uint8_t *extra; uint16_t idx; if (!entry->extralen) return true; /* read ZIP64 values where needed */ if (!ar_skip(zip->super.stream, entry->namelen)) return false; extra = malloc(entry->extralen); if (!extra || ar_read(zip->super.stream, extra, entry->extralen) != entry->extralen) { free(extra); return false; } for (idx = 0; idx + 4 < entry->extralen; idx += 4 + uint16le(&extra[idx + 2])) { if (uint16le(&extra[idx]) == 0x0001) { uint16_t size = uint16le(&extra[idx + 2]); uint16_t offset = 0; if (entry->uncompressed == UINT32_MAX && offset + 8 <= size) { entry->uncompressed = uint64le(&extra[idx + 4 + offset]); offset += 8; } if (entry->datasize == UINT32_MAX && offset + 8 <= size) { entry->datasize = uint64le(&extra[idx + 4 + offset]); offset += 8; } if (entry->header_offset == UINT32_MAX && offset + 8 <= size) { entry->header_offset = (off64_t)uint64le(&extra[idx + 4 + offset]); offset += 8; } if (entry->disk == UINT16_MAX && offset + 4 <= size) { entry->disk = uint32le(&extra[idx + 4 + offset]); offset += 4; } break; } } free(extra); return true; }
bool zip_parse_end_of_central_directory(ar_stream *stream, struct zip_eocd64 *eocd) { uint8_t data[56]; if (ar_read(stream, data, ZIP_END_OF_CENTRAL_DIR_SIZE) != ZIP_END_OF_CENTRAL_DIR_SIZE) return false; eocd->signature = uint32le(data + 0); eocd->diskno = uint16le(data + 4); eocd->diskno_dir = uint16le(data + 6); eocd->numentries_disk = uint16le(data + 8); eocd->numentries = uint16le(data + 10); eocd->dir_size = uint32le(data + 12); eocd->dir_offset = uint32le(data + 16); eocd->commentlen = uint16le(data + 20); if (eocd->signature != SIG_END_OF_CENTRAL_DIRECTORY) return false; /* try to locate the ZIP64 end of central directory */ if (!ar_skip(stream, -42)) return eocd->dir_size < 20; if (ar_read(stream, data, 20) != 20) return false; if (uint32le(data + 0) != SIG_END_OF_CENTRAL_DIRECTORY_64_LOCATOR) return true; if ((eocd->diskno != UINT16_MAX && uint32le(data + 4) != eocd->diskno) || uint32le(data + 16) != 1) { warn("Archive spanning isn't supported"); return false; } if (!ar_seek(stream, (off64_t)uint64le(data + 8), SEEK_SET)) return false; if (ar_read(stream, data, 56) != 56) return false; /* use data from ZIP64 end of central directory (when necessary) */ eocd->signature = uint32le(data + 0); eocd->version = uint16le(data + 12); eocd->min_version = uint16le(data + 14); if (eocd->diskno == UINT16_MAX) eocd->diskno = uint32le(data + 16); if (eocd->diskno_dir == UINT16_MAX) eocd->diskno_dir = uint32le(data + 20); if (eocd->numentries_disk == UINT16_MAX) eocd->numentries_disk = uint64le(data + 24); if (eocd->numentries == UINT16_MAX) eocd->numentries = uint64le(data + 32); if (eocd->dir_size == UINT32_MAX) eocd->dir_size = uint64le(data + 40); if (eocd->dir_offset == UINT32_MAX) eocd->dir_offset = (off64_t)uint64le(data + 48); if (eocd->signature != SIG_END_OF_CENTRAL_DIRECTORY_64) return false; if (eocd->diskno != eocd->diskno_dir || eocd->numentries != eocd->numentries_disk) { warn("Archive spanning isn't supported"); return false; } if (uint64le(data + 4) > 44) log("ZIP64 extensible data sector present @" PRIi64, ar_tell(stream)); return true; }
static bool rar_parse_entry(ar_archive *ar) { ar_archive_rar *rar = (ar_archive_rar *)ar; struct rar_header header; struct rar_entry entry; /* without solid data, most/all previous files have to be decompressed again */ bool has_solid_data = rar->super.entry_offset != 0 && rar->uncomp.initialized && rar->progr.data_left == 0; if (rar->super.entry_offset != 0) { if (!ar_seek(rar->super.stream, rar->super.entry_offset + rar->super.entry_size_block, SEEK_SET)) { warn("Couldn't seek to offset %" PRIuPTR, rar->super.entry_offset + rar->super.entry_size_block); return false; } } for (;;) { rar->super.entry_offset = ar_tell(rar->super.stream); rar->super.entry_size_block = 0; rar->super.entry_size_uncompressed = 0; if (!rar_parse_header(&rar->super, &header)) return false; switch (header.type) { case TYPE_MAIN_HEADER: if ((header.flags & MHD_PASSWORD)) { warn("Encrypted archives aren't supported"); return false; } ar_skip(rar->super.stream, 6 /* reserved data */); if ((header.flags & MHD_ENCRYPTVER)) { log("MHD_ENCRYPTVER is set"); ar_skip(rar->super.stream, 1); } if ((header.flags & MHD_COMMENT)) log("MHD_COMMENT is set"); if (ar_tell(rar->super.stream) - rar->super.entry_offset > header.size) { warn("Invalid RAR header size: %" PRIuPTR, header.size); return false; } rar->archive_flags = header.flags; break; case TYPE_FILE_ENTRY: if (!rar_parse_header_entry(rar, &header, &entry)) return false; if ((header.flags & LHD_PASSWORD)) warn("Encrypted entries will fail to uncompress"); if ((header.flags & LHD_DIRECTORY) == LHD_DIRECTORY) { log("Skipping directory entry \"%s\"", rar_get_name(&rar->super)); break; } if ((header.flags & (LHD_SPLIT_BEFORE | LHD_SPLIT_AFTER))) warn("Splitting files isn't really supported"); // TODO: handle multi-part files (only needed for split files)? rar->super.entry_size_block = header.size + (size_t)header.datasize; rar->super.entry_size_uncompressed = (size_t)entry.size; if (rar->super.entry_size_block < rar->entry.header_size) { warn("Integer overflow due to overly large data size"); return false; } if (!has_solid_data || !rar->entry.restart_solid || rar->entry.method == METHOD_STORE) rar_clear_uncompress(&rar->uncomp); else rar->entry.restart_solid = false; #ifdef DEBUG // TODO: CRC checks don't always hold (claim in XADRARParser.m @readBlockHeader) if (!rar_check_header_crc(&rar->super)) warn("Invalid header checksum @%" PRIuPTR, rar->super.entry_offset); #endif if (!ar_seek(rar->super.stream, rar->super.entry_offset + rar->entry.header_size, SEEK_SET)) { warn("Couldn't seek to offset %" PRIuPTR, rar->super.entry_offset + rar->entry.header_size); return false; } return true; case TYPE_NEWSUB: log("Skipping newsub header @%" PRIuPTR, rar->super.entry_offset); break; case TYPE_END_OF_ARCHIVE: rar->super.at_eof = true; return false; default: log("Unknown RAR header type %02x", header.type); break; } #ifdef DEBUG // TODO: CRC checks don't always hold (claim in XADRARParser.m @readBlockHeader) if (!rar_check_header_crc(&rar->super)) warn("Invalid header checksum @%" PRIuPTR, rar->super.entry_offset); #endif if (!ar_seek(rar->super.stream, rar->super.entry_offset + header.size + (ptrdiff_t)header.datasize, SEEK_SET)) { warn("Couldn't seek to offset %" PRIu64, rar->super.entry_offset + header.size + header.datasize); return false; } if (ar_tell(rar->super.stream) <= rar->super.entry_offset) { warn("Integer overflow due to overly large data size"); return false; } } }
static bool rar_parse_entry(ar_archive *ar, off64_t offset) { ar_archive_rar *rar = (ar_archive_rar *)ar; struct rar_header header; struct rar_entry entry; bool out_of_order = offset != ar->entry_offset_next; if (!ar_seek(ar->stream, offset, SEEK_SET)) { warn("Couldn't seek to offset %" PRIi64, offset); return false; } for (;;) { ar->entry_offset = ar_tell(ar->stream); ar->entry_size_uncompressed = 0; if (!rar_parse_header(ar, &header)) return false; ar->entry_offset_next = ar->entry_offset + header.size + header.datasize; if (ar->entry_offset_next < ar->entry_offset + header.size) { warn("Integer overflow due to overly large data size"); return false; } switch (header.type) { case TYPE_MAIN_HEADER: if ((header.flags & MHD_PASSWORD)) { warn("Encrypted archives aren't supported"); return false; } ar_skip(ar->stream, 6 /* reserved data */); if ((header.flags & MHD_ENCRYPTVER)) { log("MHD_ENCRYPTVER is set"); ar_skip(ar->stream, 1); } if ((header.flags & MHD_COMMENT)) log("MHD_COMMENT is set"); if (ar_tell(ar->stream) - ar->entry_offset > header.size) { warn("Invalid RAR header size: %d", header.size); return false; } rar->archive_flags = header.flags; break; case TYPE_FILE_ENTRY: if (!rar_parse_header_entry(rar, &header, &entry)) return false; if ((header.flags & LHD_PASSWORD)) warn("Encrypted entries will fail to uncompress"); if ((header.flags & LHD_DIRECTORY) == LHD_DIRECTORY) { if (header.datasize == 0) { log("Skipping directory entry \"%s\"", rar_get_name(ar)); break; } warn("Can't skip directory entries containing data"); } if ((header.flags & (LHD_SPLIT_BEFORE | LHD_SPLIT_AFTER))) warn("Splitting files isn't really supported"); ar->entry_size_uncompressed = (size_t)entry.size; ar->entry_filetime = ar_conv_dosdate_to_filetime(entry.dosdate); if (!rar->entry.solid || rar->entry.method == METHOD_STORE || out_of_order) { rar_clear_uncompress(&rar->uncomp); memset(&rar->solid, 0, sizeof(rar->solid)); } else { br_clear_leftover_bits(&rar->uncomp); } rar->solid.restart = rar->entry.solid && (out_of_order || !rar->solid.part_done); rar->solid.part_done = !ar->entry_size_uncompressed; rar->progress.data_left = (size_t)header.datasize; rar->progress.bytes_done = 0; rar->progress.crc = 0; /* TODO: CRC checks don't always hold (claim in XADRARParser.m @readBlockHeader) */ if (!rar_check_header_crc(ar)) warn("Invalid header checksum @%" PRIi64, ar->entry_offset); if (ar_tell(ar->stream) != ar->entry_offset + rar->entry.header_size) { warn("Couldn't seek to offset %" PRIi64, ar->entry_offset + rar->entry.header_size); return false; } return true; case TYPE_NEWSUB: log("Skipping newsub header @%" PRIi64, ar->entry_offset); break; case TYPE_END_OF_ARCHIVE: ar->at_eof = true; return false; default: log("Unknown RAR header type %02x", header.type); break; } /* TODO: CRC checks don't always hold (claim in XADRARParser.m @readBlockHeader) */ if (!rar_check_header_crc(ar)) warn("Invalid header checksum @%" PRIi64, ar->entry_offset); if (!ar_seek(ar->stream, ar->entry_offset_next, SEEK_SET)) { warn("Couldn't seek to offset %" PRIi64, ar->entry_offset_next); return false; } } }