From: Grzegorz Antoniak Date: Fri, 26 Apr 2019 04:54:56 +0000 (+0200) Subject: RAR5 reader: add support for 'version' extra field and ignore unknown fields. X-Git-Tag: v3.4.0~59^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4a94ef4eee112224ae19e05651caad28c7f04751;p=thirdparty%2Flibarchive.git RAR5 reader: add support for 'version' extra field and ignore unknown fields. This commit adds support for the VERSION extra field appended to FILE base block. This field allows to add version support for files inside the archive. If the file name is 'abc' and its version is 15, libarchive will unpack this file as 'abc;15'. Changing of file names is needed because there can be multiple files inside the archive with the same names and different versions. In order for the user to not be confused which file is which, RAR5 reader changes the name. Also this commit contains a unit test for VERSION extra field support. Another change this commit introduces is ignoring of unknown extra fields. Before applying the commit, RAR5 reader was failing to unpack the file if an unknown field was encountered. But since the reader knows the unknown field's size, it can skip it and ignore it, then proceed with parsing the structure. After applying this commit, RAR5 reader will skip and ignore unknown fields. Unknown fields that are skipped include fields in FILE's extra header, as well as unsupported REDIR types. --- diff --git a/libarchive/archive_read_support_format_rar5.c b/libarchive/archive_read_support_format_rar5.c index 5a130cca5..84d05c431 100644 --- a/libarchive/archive_read_support_format_rar5.c +++ b/libarchive/archive_read_support_format_rar5.c @@ -81,9 +81,13 @@ static unsigned char rar5_signature[] = { 243, 192, 211, 128, 187, 166, 160, 161 }; static const ssize_t rar5_signature_size = sizeof(rar5_signature); -/* static const size_t g_unpack_buf_chunk_size = 1024; */ static const size_t g_unpack_window_size = 0x20000; +/* These could have been static const's, but they aren't, because of + * Visual Studio. */ +#define MAX_NAME_IN_CHARS 2048 +#define MAX_NAME_IN_BYTES (4 * MAX_NAME_IN_CHARS) + struct file_header { ssize_t bytes_remaining; ssize_t unpacked_size; @@ -1213,6 +1217,59 @@ static int parse_htime_item(struct archive_read* a, char unix_time, return ARCHIVE_OK; } +static int parse_file_extra_version(struct archive_read* a, + struct archive_entry* e, ssize_t* extra_data_size) +{ + size_t flags = 0; + size_t version = 0; + size_t value_len = 0; + struct archive_string version_string; + struct archive_string name_utf8_string; + + /* Flags are ignored. */ + if(!read_var_sized(a, &flags, &value_len)) + return ARCHIVE_EOF; + + *extra_data_size -= value_len; + if(ARCHIVE_OK != consume(a, value_len)) + return ARCHIVE_EOF; + + if(!read_var_sized(a, &version, &value_len)) + return ARCHIVE_EOF; + + *extra_data_size -= value_len; + if(ARCHIVE_OK != consume(a, value_len)) + return ARCHIVE_EOF; + + /* extra_data_size should be zero here. */ + + const char* cur_filename = archive_entry_pathname_utf8(e); + if(cur_filename == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "Version entry without file name"); + return ARCHIVE_FATAL; + } + + archive_string_init(&version_string); + archive_string_init(&name_utf8_string); + + /* Prepare a ;123 suffix for the filename, where '123' is the version + * value of this file. */ + archive_string_sprintf(&version_string, ";%ld", version); + + /* Build the new filename. */ + archive_strcat(&name_utf8_string, cur_filename); + archive_strcat(&name_utf8_string, version_string.s); + + /* Apply the new filename into this file's context. */ + archive_entry_update_pathname_utf8(e, name_utf8_string.s); + + /* Free buffers. */ + archive_string_free(&version_string); + archive_string_free(&name_utf8_string); + return ARCHIVE_OK; +} + static int parse_file_extra_htime(struct archive_read* a, struct archive_entry* e, struct rar5* rar, ssize_t* extra_data_size) @@ -1270,7 +1327,7 @@ static int parse_file_extra_redir(struct archive_read* a, { uint64_t value_size = 0; size_t target_size = 0; - char target_utf8_buf[2048 * 4]; + char target_utf8_buf[MAX_NAME_IN_BYTES]; const uint8_t* p; if(!read_var(a, &rar->file.redir_type, &value_size)) @@ -1291,7 +1348,7 @@ static int parse_file_extra_redir(struct archive_read* a, if(!read_ahead(a, target_size, &p)) return ARCHIVE_EOF; - if(target_size > 2047) { + if(target_size > (MAX_NAME_IN_CHARS - 1)) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Link target is too long"); return ARCHIVE_FATAL; @@ -1327,12 +1384,7 @@ static int parse_file_extra_redir(struct archive_read* a, break; default: - /* Unknown redir type */ - archive_set_error(&a->archive, - ARCHIVE_ERRNO_FILE_FORMAT, - "Unsupported redir type: %d", - (int)rar->file.redir_type); - return ARCHIVE_FATAL; + /* Unknown redir type, skip it. */ break; } return ARCHIVE_OK; @@ -1444,22 +1496,21 @@ static int process_head_file_extra(struct archive_read* a, ret = parse_file_extra_htime(a, e, rar, &extra_data_size); break; case EX_REDIR: - ret = parse_file_extra_redir(a, e, rar, &extra_data_size); - break; + ret = parse_file_extra_redir(a, e, rar, &extra_data_size); + break; case EX_UOWNER: - ret = parse_file_extra_owner(a, e, &extra_data_size); - break; - case EX_CRYPT: - /* fallthrough */ + ret = parse_file_extra_owner(a, e, &extra_data_size); + break; case EX_VERSION: + ret = parse_file_extra_version(a, e, &extra_data_size); + break; + case EX_CRYPT: /* fallthrough */ case EX_SUBDATA: /* fallthrough */ default: - archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, - "Unknown extra field in file/service block: 0x%x", - (int) extra_field_id); - return ARCHIVE_FATAL; + /* Skip unsupported entry. */ + return consume(a, extra_data_size); } } @@ -1484,7 +1535,7 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, uint64_t unpacked_size; uint32_t mtime = 0, crc = 0; int c_method = 0, c_version = 0, is_dir; - char name_utf8_buf[2048 * 4]; + char name_utf8_buf[MAX_NAME_IN_BYTES]; const uint8_t* p; archive_entry_clear(entry); @@ -1603,7 +1654,7 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, if(!read_ahead(a, name_size, &p)) return ARCHIVE_EOF; - if(name_size > 2047) { + if(name_size > (MAX_NAME_IN_CHARS - 1)) { archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, "Filename is too long"); @@ -1623,6 +1674,8 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, return ARCHIVE_EOF; } + archive_entry_update_pathname_utf8(entry, name_utf8_buf); + if(extra_data_size > 0) { int ret = process_head_file_extra(a, entry, rar, extra_data_size); @@ -1651,8 +1704,6 @@ static int process_head_file(struct archive_read* a, struct rar5* rar, rar->file.stored_crc32 = crc; } - archive_entry_update_pathname_utf8(entry, name_utf8_buf); - if(!rar->cstate.switch_multivolume) { /* Do not reinitialize unpacking state if we're switching archives. */ rar->cstate.block_parsing_finished = 1; diff --git a/libarchive/test/test_read_format_rar5.c b/libarchive/test/test_read_format_rar5.c index 045172bab..9b684a73a 100644 --- a/libarchive/test/test_read_format_rar5.c +++ b/libarchive/test/test_read_format_rar5.c @@ -933,3 +933,20 @@ DEFINE_TEST(test_read_format_rar5_hardlink) EPILOGUE(); } + +DEFINE_TEST(test_read_format_rar5_extra_field_version) +{ + PROLOGUE("test_read_format_rar5_extra_field_version.rar"); + + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("bin/2to3;1", archive_entry_pathname(ae)); + assertA(0 == extract_one(a, ae, 0xF24181B7)); + + assertA(0 == archive_read_next_header(a, &ae)); + assertEqualString("bin/2to3", archive_entry_pathname(ae)); + assertA(0 == extract_one(a, ae, 0xF24181B7)); + + assertA(ARCHIVE_EOF == archive_read_next_header(a, &ae)); + + EPILOGUE(); +} diff --git a/libarchive/test/test_read_format_rar5_extra_field_version.rar.uu b/libarchive/test/test_read_format_rar5_extra_field_version.rar.uu new file mode 100644 index 000000000..f9a5ce692 --- /dev/null +++ b/libarchive/test/test_read_format_rar5_extra_field_version.rar.uu @@ -0,0 +1,10 @@ +begin 644 test_read_format_rar5_extra_field_version.rar +M4F%R(1H'`0`SDK7E"@$%!@`%`0&`@`"BNGU4(0(#!%D&7^V#`I?:-URW@4'R +M@`,!"&)I;B\R=&\S`P0``<7)5B550C]U!#WY13&:5TJ$=$IZ(*3`C\#F0%O\ +M)$)*@]X[RK.Z.G*HUT;V5FO&;:X/FDW,W`95I8T%@@C:DD="_8Z.9+CQH^IG +M8!ZF0N)JSY2?R(W@25K`U&W)Q1X"`MD`!M\`[8,"E]HW7+>!0?*``P$(8FEN +M+S)T;S/%R58E54(_=00]^44QFE=*A'1*>B"DP(_`YD!;_"1"2H/>.\JSNCIR +MJ-=&]E9KQFVN#YI-S-P&5:6-!8((VI)'0OV.CF2X\:/J9V`>ID+B:L^4G\B- +,X$E:P!UW5E$#!00` +` +end