fix(extract): handle multi-volume split segments in scan and extract

unrar in RAR_OM_EXTRACT mode presents a file that spans volumes as
several header segments with the SAME name, flagged RHDF_SPLITBEFORE/
SPLITAFTER. The scan dedup rejected the continuation segments as
"duplicate entry name" and the whole multi-volume extract failed.

Fix: segments after the first (RHDF_SPLITBEFORE set) skip the
normalize/dedup/size/limit accounting in scan (the first segment already
carries the full size) and, in extract, still call RARProcessFile(EXTRACT)
to drive the volume chain but are not counted or size-accumulated.

Verified against real fixtures (tests/fixtures-real/, generated by
tests/make-rar-fixtures.bat on the user's RAR 7.23): a 512 KB file split
across vol.part1/2/3.rar extracts whole (status ZIPX_OK, big.bin + root.txt).
Host suite now 70 ZIP + 24 RAR checks, 0 failures.
This commit is contained in:
Songlx516 committed 2026-09-05 23:18:18 +08:00
1 parent 91757bfd02
commit 452b9b0187
2 files changed
+30 -1

No files matched your search

+27
View File
@@ -660,6 +660,21 @@ scan_archive(HANDLE hArc, rarx_ctx_t *c) {
break;
}
/* Multi-volume: a file spanning volumes is presented as several header
segments with the SAME name. Segments after the first carry
RHDF_SPLITBEFORE; they must drive the engine forward (SKIP) but must
not be counted, deduped or size-accumulated again. The first segment
already carries the full file size. */
if(hdr.Flags & RHDF_SPLITBEFORE) {
rc = RARProcessFile(hArc, RAR_SKIP, NULL, NULL);
if(rc != ERAR_SUCCESS && rc != ERAR_END_ARCHIVE) {
ret = -1;
rar_translate_error(rc, hdr.FileName, c);
break;
}
continue;
}
if(normalize_name(hdr.FileName, name, sizeof(name), &is_dir, &depth, c)) {
ret = -1;
break;
@@ -765,6 +780,18 @@ extract_archive(HANDLE hArc, rarx_ctx_t *c) {
}
is_dir = (hdr.Flags & RHDF_DIRECTORY) ? 1 : 0;
/* Split continuation segments still need RARProcessFile(EXTRACT) so the
volume chain is driven and the file is completed, but only the first
segment is counted / reported / size-accumulated. */
if(hdr.Flags & RHDF_SPLITBEFORE) {
rc = RARProcessFile(hArc, RAR_EXTRACT, c->staging, NULL);
if(rc != ERAR_SUCCESS) {
rar_translate_error(rc, hdr.FileName, c);
ret = -1;
break;
}
continue;
}
rc = RARProcessFile(hArc, RAR_EXTRACT, c->staging, NULL);
if(rc != ERAR_SUCCESS) {
rar_translate_error(rc, hdr.FileName, c);
+3 -1
View File
@@ -384,7 +384,9 @@ test_real_archives(void) {
if(st == ZIPX_OK) {
snprintf(checkp, sizeof(checkp), "%s/root.txt", dst);
check(exists(checkp), " root.txt present across volumes");
check(res.entries_done >= 3, " entries_done >= 3");
snprintf(checkp, sizeof(checkp), "%s/big.bin", dst);
check(exists(checkp), " big.bin (split file) present and whole");
check(res.entries_done >= 2, " entries_done >= 2");
} else {
printf(" message=%s\n", res.message);
}