Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions extra_modules/archive/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ cc_library(
"@abseil-cpp//absl/strings",
"@libarchive//libarchive",
"@xff_extras_api//:license_notice_cc",
"@xff_extras_api//:member_path_cc",
],
alwayslink = True,
)
Expand Down Expand Up @@ -100,3 +101,41 @@ cc_test(
"@xff_extras_api//:vfs_cc",
],
)

# PHP's NATIVE phar format, which libarchive does not read at all: a PHP stub ending in
# `__HALT_COMPILER();` followed by a binary manifest and the member data. Same `Member` shape as the
# libarchive reader, so the VFS backend and the walk stay format-agnostic. The tar- and zip-based
# phar variants need nothing here - they are ordinary tars / zips for `:archive_reader_cc`.
cc_library(
name = "phar_reader_cc",
srcs = ["phar_reader.cc"],
hdrs = ["phar_reader.h"],
include_prefix = "xff/archive",
visibility = ["//visibility:public"],
deps = [
":archive_reader_cc",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
"@xff_extras_api//:member_path_cc",
],
)

# There is no phar WRITER to lean on, so the test builds containers byte by byte: the fixture is the
# format specification, and every field offset the reader relies on is pinned by a case that would
# read garbage if it were wrong (a non-empty alias, a second member's data offset, a lying member
# count).
cc_test(
name = "phar_reader_test",
size = "small",
srcs = ["phar_reader_test.cc"],
deps = [
":archive_reader_cc",
":phar_reader_cc",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/strings",
"@googletest//:gtest",
"@googletest//:gtest_main",
"@helly25_mbo//mbo/testing:status_cc",
],
)
22 changes: 4 additions & 18 deletions extra_modules/archive/archive_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "xff/archive/member_path.h"
#include "xff/license/notice.h"

namespace xff::archive {
Expand Down Expand Up @@ -144,22 +145,7 @@ absl::StatusOr<std::vector<Member>> ListMembersOfFile(std::string_view path) {
return ReadMembers(handle.get());
}

namespace {

// The member name in comparable form. Tar writes the SAME member several ways: `dir/x` and `./dir/x`
// for a file, and a directory as `dir/` with a trailing slash - so a lookup for `dir` must find it
// (and then be told it has no content, rather than "no such member").
std::string_view NormalizedMemberName(std::string_view path) {
while (path.starts_with("./")) {
path.remove_prefix(2);
}
while (path.size() > 1 && path.ends_with('/')) {
path.remove_suffix(1);
}
return path;
}

} // namespace
namespace {} // namespace

absl::StatusOr<std::string> ReadMemberOfFile(std::string_view path, std::string_view member, std::uint64_t max_bytes) {
const ArchivePtr handle = NewReader();
Expand All @@ -170,7 +156,7 @@ absl::StatusOr<std::string> ReadMemberOfFile(std::string_view path, std::string_
if (::archive_read_open_filename(handle.get(), path_string.c_str(), kBlockSize) != ARCHIVE_OK) {
return absl::InvalidArgumentError(absl::StrCat("not a readable archive: ", LastError(handle.get())));
}
const std::string_view wanted = NormalizedMemberName(member);
const std::string_view wanted = NormalizeMemberName(member);
struct ::archive_entry* entry = nullptr;
while (true) {
const int status = ::archive_read_next_header(handle.get(), &entry);
Expand All @@ -181,7 +167,7 @@ absl::StatusOr<std::string> ReadMemberOfFile(std::string_view path, std::string_
return absl::DataLossError(absl::StrCat("archive read failed: ", LastError(handle.get())));
}
const char* const stored = ::archive_entry_pathname(entry);
if (stored == nullptr || NormalizedMemberName(stored) != wanted) {
if (stored == nullptr || NormalizeMemberName(stored) != wanted) {
continue; // not this one; libarchive skips its data on the next header read
}
if (::archive_entry_filetype(entry) != AE_IFREG) {
Expand Down
Loading
Loading