99#include < algorithm>
1010#include < array>
1111#include < cstdio>
12+ #include < cstring>
1213#include < limits>
1314#include < mutex>
1415#include < stdexcept>
@@ -48,6 +49,19 @@ using ZipFileHandle = std::unique_ptr<zip_file_t, ZipFileDeleter>;
4849 " zip: " + operation + " : " + zip_file_strerror (file));
4950}
5051
52+ constexpr uint32_t kLocalHeaderSignature = 0x04034b50 ;
53+ constexpr size_t kLocalHeaderSize = 30 ;
54+ constexpr uint16_t kDataDescriptorFlag = 1 << 3 ;
55+
56+ uint16_t read_u16 (const uint8_t * p) {
57+ return static_cast <uint16_t >(p[0 ] | (p[1 ] << 8 ));
58+ }
59+
60+ uint32_t read_u32 (const uint8_t * p) {
61+ return static_cast <uint32_t >(read_u16 (p)) |
62+ (static_cast <uint32_t >(read_u16 (p + 2 )) << 16 );
63+ }
64+
5165ZipHandle open_path (const std::string& path) {
5266 int error_code = 0 ;
5367 ZipHandle archive (
@@ -91,9 +105,12 @@ ZipHandle open_memory(ByteSpan bytes) {
91105} // namespace
92106
93107struct ZipReader ::Impl {
94- explicit Impl (ZipHandle handle) : archive(std::move(handle)) {}
108+ explicit Impl (ZipHandle handle, ByteSpan bytes = {})
109+ : archive(std::move(handle)), memory(bytes) {}
95110
96111 ZipHandle archive;
112+ // The archive itself when memory-backed; empty when file-backed.
113+ ByteSpan memory;
97114 mutable std::mutex mutex;
98115};
99116
@@ -134,12 +151,55 @@ ZipReader::ZipReader(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {
134151 if (stat.size > std::numeric_limits<size_t >::max ()) {
135152 throw std::runtime_error (" zip: member is too large: " + name);
136153 }
137- if (!entries_. emplace (name, Entry{index, static_cast <size_t >(stat.size )})
138- .second ) {
154+ const Entry entry {index, static_cast <size_t >(stat.size ), std:: nullopt };
155+ if (!entries_. emplace (name, entry) .second ) {
139156 throw std::runtime_error (" zip: duplicate member name: " + name);
140157 }
141158 names_.push_back (name);
142159 }
160+ if (!impl_->memory .empty ()) {
161+ locate_member_data ();
162+ }
163+ }
164+
165+ // libzip does not expose where member data starts, so walk the local headers.
166+ // Accept only a simple layout: stored members contiguous from offset zero in
167+ // central-directory order. Otherwise leave offsets unset and read via libzip.
168+ void ZipReader::locate_member_data () {
169+ const ByteSpan archive = impl_->memory ;
170+ std::vector<size_t > offsets;
171+ offsets.reserve (names_.size ());
172+ size_t position = 0 ;
173+ for (const std::string& name : names_) {
174+ if (archive.size () - position < kLocalHeaderSize ) {
175+ return ;
176+ }
177+ const uint8_t * header = archive.data () + position;
178+ const size_t name_size = read_u16 (header + 26 );
179+ const size_t extra_size = read_u16 (header + 28 );
180+ if (read_u32 (header) != kLocalHeaderSignature ||
181+ (read_u16 (header + 6 ) & kDataDescriptorFlag ) != 0 ||
182+ read_u16 (header + 8 ) != ZIP_CM_STORE ) {
183+ return ;
184+ }
185+ const size_t name_start = position + kLocalHeaderSize ;
186+ if (archive.size () - name_start < name_size + extra_size ||
187+ std::string_view (
188+ reinterpret_cast <const char *>(archive.data () + name_start),
189+ name_size) != name) {
190+ return ;
191+ }
192+ const size_t data = name_start + name_size + extra_size;
193+ const size_t size = entries_.find (name)->second .size ;
194+ if (archive.size () - data < size) {
195+ return ;
196+ }
197+ offsets.push_back (data);
198+ position = data + size;
199+ }
200+ for (size_t i = 0 ; i < offsets.size (); ++i) {
201+ entries_.find (names_[i])->second .data_offset = offsets[i];
202+ }
143203}
144204
145205ZipReader::~ZipReader () = default ;
@@ -151,14 +211,22 @@ ZipReader ZipReader::open(const std::string& path) {
151211}
152212
153213ZipReader ZipReader::open (ByteSpan archive) {
154- return ZipReader (std::make_unique<Impl>(open_memory (archive)));
214+ return ZipReader (std::make_unique<Impl>(open_memory (archive), archive ));
155215}
156216
157217std::optional<size_t > ZipReader::member_size (std::string_view name) const {
158218 const Entry* entry = find_entry (name);
159219 return entry == nullptr ? std::nullopt : std::optional<size_t >(entry->size );
160220}
161221
222+ std::optional<ByteSpan> ZipReader::member_bytes (std::string_view name) const {
223+ const Entry* entry = find_entry (name);
224+ if (entry == nullptr || !entry->data_offset ) {
225+ return std::nullopt ;
226+ }
227+ return impl_->memory .subspan (*entry->data_offset , entry->size );
228+ }
229+
162230std::vector<uint8_t > ZipReader::read (std::string_view name) const {
163231 const Entry* entry = find_entry (name);
164232 if (entry == nullptr ) {
@@ -197,6 +265,13 @@ void ZipReader::read_entry_into(
197265 if (destination.empty ()) {
198266 return ;
199267 }
268+ if (entry.data_offset ) {
269+ std::memcpy (
270+ destination.data (),
271+ impl_->memory .data () + *entry.data_offset + offset,
272+ destination.size ());
273+ return ;
274+ }
200275
201276 const std::lock_guard<std::mutex> lock (impl_->mutex );
202277 ZipFileHandle file (
0 commit comments