Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c33f5260 authored by Songchun Fan's avatar Songchun Fan
Browse files

[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
parent 18c00858
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -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.
 *
+21 −12
Original line number Diff line number Diff line
@@ -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;
+47 −11
Original line number Diff line number Diff line
@@ -227,32 +227,44 @@ TEST(ziparchive, Iteration_std_string_view) {
  CloseArchive(handle);
}

static void AssertIterationOrder(const std::string_view prefix, const std::string_view suffix,
static void AssertIterationNames(void* iteration_cookie,
                                 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, 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));
  CloseArchive(handle);

  // 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;
  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));

  void* iteration_cookie;
  ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, prefix, suffix));
  AssertIterationNames(iteration_cookie, expected_names_sorted);
  CloseArchive(handle);
}

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) {
  static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt", "b.txt", "b/", "b/c.txt",
                                                                  "b/d.txt"};
@@ -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));