static void rar_close(ar_archive *ar) { ar_archive_rar *rar = (ar_archive_rar *)ar; free(rar->entry.name); free(rar->entry.name_w); rar_clear_uncompress(&rar->uncomp); }
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; } } }