Skip to content

Commit 942b90e

Browse files
committed
Enable ArchiveStartOffset for nested archive subfiles
Introduced the ability to specify how the start of an archive should be located when opening a subfile that is itself an archive. This allows using `ArchiveStartOffset::None` to search for the archive header within the subfile's stream, providing greater flexibility for handling nested archives.
1 parent e3119ae commit 942b90e

5 files changed

Lines changed: 158 additions & 13 deletions

File tree

include/bit7z/bitarchivereader.hpp

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,21 +212,65 @@ class BitArchiveReader final : public BitAbstractArchiveOpener, public BitInputA
212212
const tstring& password = {}
213213
);
214214

215+
/**
216+
* @brief Constructs a BitArchiveReader object, opening the main subfile of the given archive.
217+
*
218+
* @note The constructor will throw an exception if the format of the input archive doesn't have a main subfile.
219+
*
220+
* @param lib the 7z library used.
221+
* @param inArchive the input archive containing a main subfile.
222+
* @param archiveStart whether to search for the archive's start throughout the entire subfile stream
223+
* or only at the beginning.
224+
* @param format the format of the main subfile.
225+
* @param password (optional) the password needed for opening the main subfile.
226+
*
227+
* @throws BitException if the format of the input archive doesn't have a main subfile.
228+
*/
229+
BitArchiveReader(
230+
const Bit7zLibrary& lib,
231+
const BitInputArchive& inArchive,
232+
ArchiveStartOffset archiveStart,
233+
const BitInFormat& format BIT7Z_DEFAULT_FORMAT,
234+
const tstring& password = {}
235+
);
236+
237+
/**
238+
* @brief Constructs a BitArchiveReader object, opening the subfile at the specified index of the given archive.
239+
*
240+
* @param lib the 7z library used.
241+
* @param inArchive the input archive containing the desired subfile.
242+
* @param subfileIndex the index of the subfile within the input archive.
243+
* @param format the format of the subfile.
244+
* @param password (optional) the password needed for opening the subfile.
245+
*
246+
* @throws BitException if the format of the input archive doesn't support subfile streams.
247+
*/
248+
BitArchiveReader(
249+
const Bit7zLibrary& lib,
250+
const BitInputArchive& inArchive,
251+
std::uint32_t subfileIndex,
252+
const BitInFormat& format BIT7Z_DEFAULT_FORMAT,
253+
const tstring& password = {}
254+
);
255+
215256
/**
216257
* @brief Constructs a BitArchiveReader object, opening the subfile at the specified index of the given archive.
217258
*
218259
* @param lib the 7z library used.
219260
* @param inArchive the input archive containing the desired subfile.
220261
* @param subfileIndex the index of the subfile within the input archive.
262+
* @param archiveStart whether to search for the archive's start throughout the entire subfile stream
263+
* or only at the beginning.
221264
* @param format the format of the subfile.
222265
* @param password (optional) the password needed for opening the subfile.
223266
*
224-
* @throws BitException if the format of the input archive doesn't support subfile streams,
267+
* @throws BitException if the format of the input archive doesn't support subfile streams.
225268
*/
226269
BitArchiveReader(
227270
const Bit7zLibrary& lib,
228271
const BitInputArchive& inArchive,
229272
std::uint32_t subfileIndex,
273+
ArchiveStartOffset archiveStart,
230274
const BitInFormat& format BIT7Z_DEFAULT_FORMAT,
231275
const tstring& password = {}
232276
);

include/bit7z/bitinputarchive.hpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -669,12 +669,11 @@ class BitInputArchive {
669669
BIT7Z_NODISCARD auto mainSubfileIndex() const -> std::uint32_t;
670670

671671
protected:
672-
explicit BitInputArchive( const BitAbstractArchiveHandler& handler, const BitInputArchive& parentArchive );
673-
674672
explicit BitInputArchive(
675673
const BitAbstractArchiveHandler& handler,
676674
const BitInputArchive& parentArchive,
677-
std::uint32_t index
675+
std::uint32_t subfileIndex,
676+
ArchiveStartOffset archiveStart
678677
);
679678

680679
BIT7Z_NODISCARD

src/bitarchivereader.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,33 @@ BitArchiveReader::BitArchiveReader(
8484
const BitInputArchive& inArchive,
8585
const BitInFormat& format,
8686
const tstring& password
87-
) : BitAbstractArchiveOpener{ lib, format, password }, BitInputArchive{ *this, inArchive } {}
87+
) : BitArchiveReader{ lib, inArchive, ArchiveStartOffset::FileStart, format, password } {}
88+
89+
BitArchiveReader::BitArchiveReader(
90+
const Bit7zLibrary& lib,
91+
const BitInputArchive& inArchive,
92+
ArchiveStartOffset archiveStart,
93+
const BitInFormat& format,
94+
const tstring& password
95+
) : BitArchiveReader{ lib, inArchive, inArchive.mainSubfileIndex(), archiveStart, format, password } {}
8896

8997
BitArchiveReader::BitArchiveReader(
9098
const Bit7zLibrary& lib,
9199
const BitInputArchive& inArchive,
92100
std::uint32_t subfileIndex,
93101
const BitInFormat& format,
94102
const tstring& password
95-
) : BitAbstractArchiveOpener{ lib, format, password }, BitInputArchive{ *this, inArchive, subfileIndex } {}
103+
) : BitArchiveReader{ lib, inArchive, subfileIndex, ArchiveStartOffset::FileStart, format, password } {}
104+
105+
BitArchiveReader::BitArchiveReader(
106+
const Bit7zLibrary& lib,
107+
const BitInputArchive& inArchive,
108+
std::uint32_t subfileIndex,
109+
ArchiveStartOffset archiveStart,
110+
const BitInFormat& format,
111+
const tstring& password
112+
) : BitAbstractArchiveOpener{ lib, format, password },
113+
BitInputArchive{ *this, inArchive, subfileIndex, archiveStart } {}
96114

97115
auto BitArchiveReader::archiveProperties() const -> std::map< BitProperty, BitPropVariant > {
98116
std::map< BitProperty, BitPropVariant > result;

src/bitinputarchive.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,18 +196,16 @@ BitInputArchive::BitInputArchive( const BitAbstractArchiveHandler& handler, cons
196196
mInArchive = arc.Detach();
197197
}
198198

199-
BitInputArchive::BitInputArchive( const BitAbstractArchiveHandler& handler, const BitInputArchive& parentArchive )
200-
: BitInputArchive{ handler, parentArchive, parentArchive.mainSubfileIndex() } {}
201-
202199
BitInputArchive::BitInputArchive(
203200
const BitAbstractArchiveHandler& handler,
204201
const BitInputArchive& parentArchive,
205-
std::uint32_t index
202+
std::uint32_t subfileIndex,
203+
ArchiveStartOffset archiveStart
206204
) : mDetectedFormat{ &handler.format() },
207205
mArchiveHandler{ handler },
208-
mArchivePath{ parentArchive.itemAt( index ).path() } {
209-
const CMyComPtr< IInStream > subStream = parentArchive.getSubfileStream( index );
210-
mInArchive = openArchiveStream( fs::path{}, subStream, ArchiveStartOffset::FileStart );
206+
mArchivePath{ parentArchive.itemAt( subfileIndex ).path() } {
207+
const CMyComPtr< IInStream > subStream = parentArchive.getSubfileStream( subfileIndex );
208+
mInArchive = openArchiveStream( fs::path{}, subStream, archiveStart );
211209
}
212210

213211
auto BitInputArchive::archiveProperty( BitProperty property ) const -> BitPropVariant {

tests/src/test_bitinputarchive.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,6 +2244,92 @@ TEMPLATE_TEST_CASE(
22442244
}
22452245
}
22462246

2247+
// NOLINTNEXTLINE(*-err58-cpp)
2248+
TEST_CASE(
2249+
"BitInputArchive: Reading the main subfile of an archive with a specified archive start offset",
2250+
"[bitinputarchive]"
2251+
) {
2252+
const TestDirectory testDir{ fs::path{ test_archives_dir } / "extraction" / "split" };
2253+
2254+
// A split archive exposes its reassembled content as the main subfile; here that content is a 7z
2255+
// archive located at the start of the subfile stream, so it can be opened both by checking only
2256+
// the file start and by scanning the whole subfile stream.
2257+
const fs::path splitArcFileName = "clouds.jpg.7z.001";
2258+
const BitArchiveReader splitArchive( test::sevenzipLib(), splitArcFileName.string< tchar >(), BitFormat::Split );
2259+
2260+
const auto archiveStart = GENERATE( ArchiveStartOffset::FileStart, ArchiveStartOffset::None );
2261+
2262+
const BitArchiveReader innerArchive( test::sevenzipLib(), splitArchive, archiveStart, BitFormat::SevenZip );
2263+
REQUIRE( innerArchive.itemsCount() == singleFileContent().fileCount );
2264+
REQUIRE_ARCHIVE_TESTS( innerArchive );
2265+
}
2266+
2267+
// NOLINTNEXTLINE(*-err58-cpp)
2268+
TEMPLATE_TEST_CASE(
2269+
"BitInputArchive: Reading a subfile by index of an archive with a specified archive start offset",
2270+
"[bitinputarchive]",
2271+
tstring,
2272+
buffer_t,
2273+
stream_t
2274+
) {
2275+
const TestDirectory testDir{ fs::path{ test_archives_dir } / "extraction" / "nested" };
2276+
2277+
const fs::path arcFileName = "multiple_nested2.tar";
2278+
2279+
TestType inputArchive{};
2280+
getInputArchive( arcFileName, inputArchive );
2281+
const Bit7zLibrary lib{ test::sevenzipLibPath() };
2282+
2283+
// The outer Tar archive stores nested archives as its items; Tar supports retrieving each item's
2284+
// stream, which can then be opened as a nested archive at the specified archive start offset.
2285+
const BitArchiveReader outerArchive( lib, inputArchive, BitFormat::Tar );
2286+
2287+
const auto archiveStart = GENERATE( ArchiveStartOffset::FileStart, ArchiveStartOffset::None );
2288+
2289+
SECTION( "Opening the nested 7z subfile" ) {
2290+
const BitArchiveReader innerArchive( lib, outerArchive, 1U, archiveStart, BitFormat::SevenZip );
2291+
REQUIRE_NOTHROW( innerArchive.test() );
2292+
}
2293+
2294+
SECTION( "Opening the nested zip subfile" ) {
2295+
const BitArchiveReader innerArchive( lib, outerArchive, 2U, archiveStart, BitFormat::Zip );
2296+
REQUIRE_NOTHROW( innerArchive.test() );
2297+
}
2298+
}
2299+
2300+
// NOLINTNEXTLINE(*-err58-cpp)
2301+
TEST_CASE(
2302+
"BitInputArchive: Reading a subfile whose archive data does not start at the subfile stream start",
2303+
"[bitinputarchive]"
2304+
) {
2305+
const TestDirectory testDir{ fs::path{ test_archives_dir } / "extraction" / "nested" };
2306+
2307+
const fs::path arcFileName = "multiple_nested2.tar";
2308+
2309+
const Bit7zLibrary lib{ test::sevenzipLibPath() };
2310+
2311+
// TODO: Add some fixture archives with an embedded archive at a non-zero offset *within* a subfile stream.
2312+
// Until then, build one at runtime: an outer Tar whose only item is multiple_nested2.tar (itself a Tar
2313+
// holding a nested 7z). The Tar handler returns that item's bytes verbatim, so within the subfile stream
2314+
// the nested 7z starts well after offset 0 (which is just a Tar header).
2315+
// This way we can test a case where the two ArchiveStartOffset values behave differently, proving that
2316+
// FileStart restricts the scan.
2317+
buffer_t outerArchiveBuffer;
2318+
BitArchiveWriter writer{ lib, BitFormat::Tar };
2319+
writer.addFile( arcFileName.string< tchar >() );
2320+
writer.compressTo( outerArchiveBuffer );
2321+
2322+
const BitArchiveReader outerArchive( lib, outerArchiveBuffer, BitFormat::Tar );
2323+
2324+
// Scanning the whole subfile stream (None) finds the nested 7z archive, so the opening succeeds...
2325+
const BitArchiveReader innerArchive( lib, outerArchive, 0U, ArchiveStartOffset::None, BitFormat::SevenZip );
2326+
REQUIRE_NOTHROW( innerArchive.test() );
2327+
2328+
// ...while checking only the file start of the same subfile stream (FileStart) sees a Tar header
2329+
// instead of a 7z signature, so the opening must fail.
2330+
REQUIRE_THROWS( BitArchiveReader( lib, outerArchive, 0U, ArchiveStartOffset::FileStart, BitFormat::SevenZip ) );
2331+
}
2332+
22472333
// NOLINTNEXTLINE(*-err58-cpp)
22482334
TEMPLATE_TEST_CASE(
22492335
"BitInputArchive: Reading a nested archive with wrong extension",

0 commit comments

Comments
 (0)