[libziparchive] add an option to start iteration with functor

To reduce the seeks for local file headers in large APK files, we can
specify entry prefix/suffix when we call StartIteration(). However,
some use cases need additional name matches that is outside the
prefix/suffix matches.

Adding a new option to StartIteration, which allows additional functor
that restricts the iteration to customized name matching schemes.

Test: atest ziparchive-tests
BUG: 151676293
Change-Id: Iff45e083b334602f183c05cb39ba521e7070252c
This commit is contained in:
Songchun Fan 2020-03-24 09:15:51 -07:00
parent 18c00858d2
commit c33f5260ea
3 changed files with 81 additions and 27 deletions

View file

@ -187,6 +187,15 @@ int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
const std::string_view optional_prefix = "",
const std::string_view optional_suffix = "");
/*
* Start iterating over all entries of a zip file. Use the matcher functor to
* restrict iteration to entry names that make the functor return true.
*
* Returns 0 on success and negative values on failure.
*/
int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
std::function<bool(std::string_view entry_name)> matcher);
/*
* Advance to the next element in the zipfile in iteration order.
*

View file

@ -32,6 +32,7 @@
#include <unistd.h>
#include <memory>
#include <optional>
#include <vector>
#if defined(__APPLE__)
@ -675,31 +676,40 @@ static int32_t FindEntry(const ZipArchive* archive, std::string_view entryName,
struct IterationHandle {
ZipArchive* archive;
std::string prefix;
std::string suffix;
std::function<bool(std::string_view)> matcher;
uint32_t position = 0;
IterationHandle(ZipArchive* archive, std::string_view in_prefix, std::string_view in_suffix)
: archive(archive), prefix(in_prefix), suffix(in_suffix) {}
IterationHandle(ZipArchive* archive, std::function<bool(std::string_view)> in_matcher)
: archive(archive), matcher(std::move(in_matcher)) {}
bool Match(std::string_view entry_name) const { return matcher(entry_name); }
};
int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
const std::string_view optional_prefix,
const std::string_view optional_suffix) {
if (archive == nullptr || archive->cd_entry_map == nullptr) {
ALOGW("Zip: Invalid ZipArchiveHandle");
return kInvalidHandle;
}
if (optional_prefix.size() > static_cast<size_t>(UINT16_MAX) ||
optional_suffix.size() > static_cast<size_t>(UINT16_MAX)) {
ALOGW("Zip: prefix/suffix too long");
return kInvalidEntryName;
}
auto matcher = [prefix = std::string(optional_prefix),
suffix = std::string(optional_suffix)](std::string_view name) mutable {
return android::base::StartsWith(name, prefix) && android::base::EndsWith(name, suffix);
};
return StartIteration(archive, cookie_ptr, std::move(matcher));
}
int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
std::function<bool(std::string_view)> matcher) {
if (archive == nullptr || archive->cd_entry_map == nullptr) {
ALOGW("Zip: Invalid ZipArchiveHandle");
return kInvalidHandle;
}
archive->cd_entry_map->ResetIteration();
*cookie_ptr = new IterationHandle(archive, optional_prefix, optional_suffix);
*cookie_ptr = new IterationHandle(archive, matcher);
return 0;
}
@ -749,8 +759,7 @@ int32_t Next(void* cookie, ZipEntry* data, std::string_view* name) {
auto entry = archive->cd_entry_map->Next(archive->central_directory.GetBasePtr());
while (entry != std::pair<std::string_view, uint64_t>()) {
const auto [entry_name, offset] = entry;
if (android::base::StartsWith(entry_name, handle->prefix) &&
android::base::EndsWith(entry_name, handle->suffix)) {
if (handle->Match(entry_name)) {
const int error = FindEntry(archive, entry_name, offset, data);
if (!error && name) {
*name = entry_name;

View file

@ -227,6 +227,22 @@ TEST(ziparchive, Iteration_std_string_view) {
CloseArchive(handle);
}
static void AssertIterationNames(void* iteration_cookie,
const std::vector<std::string>& expected_names_sorted) {
ZipEntry data;
std::vector<std::string> names;
std::string name;
for (size_t i = 0; i < expected_names_sorted.size(); ++i) {
ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
names.push_back(name);
}
// End of iteration.
ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
// Assert that the names are as expected.
std::sort(names.begin(), names.end());
ASSERT_EQ(expected_names_sorted, names);
}
static void AssertIterationOrder(const std::string_view prefix, const std::string_view suffix,
const std::vector<std::string>& expected_names_sorted) {
ZipArchiveHandle handle;
@ -234,23 +250,19 @@ static void AssertIterationOrder(const std::string_view prefix, const std::strin
void* iteration_cookie;
ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, prefix, suffix));
ZipEntry data;
std::vector<std::string> names;
std::string name;
for (size_t i = 0; i < expected_names_sorted.size(); ++i) {
ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
names.push_back(name);
}
// End of iteration.
ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
AssertIterationNames(iteration_cookie, expected_names_sorted);
CloseArchive(handle);
}
// Assert that the names are as expected.
std::sort(names.begin(), names.end());
ASSERT_EQ(expected_names_sorted, names);
static void AssertIterationOrderWithMatcher(std::function<bool(std::string_view)> matcher,
const std::vector<std::string>& expected_names_sorted) {
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
void* iteration_cookie;
ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, matcher));
AssertIterationNames(iteration_cookie, expected_names_sorted);
CloseArchive(handle);
}
TEST(ziparchive, Iteration) {
@ -279,6 +291,30 @@ TEST(ziparchive, IterationWithPrefixAndSuffix) {
AssertIterationOrder("b", ".txt", kExpectedMatchesSorted);
}
TEST(ziparchive, IterationWithAdditionalMatchesExactly) {
static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt"};
auto matcher = [](std::string_view name) { return name == "a.txt"; };
AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted);
}
TEST(ziparchive, IterationWithAdditionalMatchesWithSuffix) {
static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt", "b.txt", "b/c.txt",
"b/d.txt"};
auto matcher = [](std::string_view name) {
return name == "a.txt" || android::base::EndsWith(name, ".txt");
};
AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted);
}
TEST(ziparchive, IterationWithAdditionalMatchesWithPrefixAndSuffix) {
static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt", "b/c.txt", "b/d.txt"};
auto matcher = [](std::string_view name) {
return name == "a.txt" ||
(android::base::EndsWith(name, ".txt") && android::base::StartsWith(name, "b/"));
};
AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted);
}
TEST(ziparchive, IterationWithBadPrefixAndSuffix) {
ZipArchiveHandle handle;
ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));