]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
RAR5 reader: add support for 'version' extra field and ignore unknown fields. 1182/head
authorGrzegorz Antoniak <ga@anadoxin.org>
Fri, 26 Apr 2019 04:54:56 +0000 (06:54 +0200)
committerGrzegorz Antoniak <ga@anadoxin.org>
Sat, 27 Apr 2019 05:41:40 +0000 (07:41 +0200)
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.

libarchive/archive_read_support_format_rar5.c
libarchive/test/test_read_format_rar5.c
libarchive/test/test_read_format_rar5_extra_field_version.rar.uu [new file with mode: 0644]

index 5a130cca5379c1225eb1505bdc9fc66baf8f0ea1..84d05c431e8bca0b07c386c9935eff86cf085231 100644 (file)
 
 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;
index 045172bab6adaac58f2fcff0965646275eef3969..9b684a73a9e3306d57730a9e445ef919de60da63 100644 (file)
@@ -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 (file)
index 0000000..f9a5ce6
--- /dev/null
@@ -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